MySQL的事务初探

参考于:

mp.weixin.qq.com/s/tNA_-_MoYt1fJT0icyKbMg

www.imooc.com/article/17290#

一、何为事务

  1. 事务就是一系列原子性的SQL查询;是一个独立的工作单元。
  2. 如果数据库引擎能够成功的对数据库应用该组查询的全部查询(SQL没有报错), 就可以执行该组查询。
  3. 如果该组查询中有其中一条失败、崩溃、或其他原因导致SQL无法执行,那么所有语句都不会执行。
  4. 也就是说, 事务内的语句, 要么全部执行成功, 要么全部执行失败。

二、事务的用法

事务的用法十分简单(3个语句):

    START TRANSACTION;//开启事务
        sql 1;
        sql 2;
        ROLLBACK
        sql 3;
    COMMIT; //提交事务

2.1 START TRANSACTION ———— 开启一个事务

开启一个事务,一组事务开启的标识语句。

2.2 ROLLBACK ———— 撤销所有修改

回滚,把数据状态回滚成事务开启之前的状态。
如:

    表结构(下面测试也使用此表):
        mysql> desc test;
        +--------+------------------+------+-----+---------+----------------+
        | Field  | Type             | Null | Key | Default | Extra          |
        +--------+------------------+------+-----+---------+----------------+
        | id     | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
        | num    | int(10)          | NO   |     | 0       |                |
        | data   | varchar(20)      | NO   |     | 0       |                |
        | status | tinyint(3)       | NO   |     | 1       |                |
        +--------+------------------+------+-----+---------+----------------+


        mysql> select * from test;
        +----+-----+------+--------+
        | id | num | data | status |
        +----+-----+------+--------+
        |  1 |  12 | 123  |      1 |
        +----+-----+------+--------+
        1 row in set (0.00 sec)

        mysql> start transaction;//开启一个事务
        Query OK, 0 rows affected (0.00 sec)

        mysql> update test set num = 13 where id = 1;//更新
        Query OK, 1 row affected (0.00 sec)
        Rows matched: 1  Changed: 1  Warnings: 0//提示数据已经改动

        mysql> rollback;//回滚
        Query OK, 0 rows affected (0.05 sec)

        mysql> select * from test where id = 1;//可以看到已经被回滚到原来状态
        +----+-----+------+--------+
        | id | num | data | status |
        +----+-----+------+--------+
        |  1 |  12 | 123  |      1 |
        +----+-----+------+--------+
        1 row in set (0.00 sec)

2.3COMMIT ———— 持久保存

提交一个事务,事务中的改变的数据状态将会被永久更改。

    如:
        mysql> select * from test where id = 1;
        +----+-----+------+--------+
        | id | num | data | status |
        +----+-----+------+--------+
        |  1 |  12 | 123  |      1 |
        +----+-----+------+--------+
        mysql> start transaction;//开启事务
        Query OK, 0 rows affected (0.00 sec)

        mysql> update test set num = 13 where id = 1;//事务中的更新操作
        Query OK, 1 row affected (0.00 sec)
        Rows matched: 1  Changed: 1  Warnings: 0

        mysql> update test set data = '测试' where id = 1;//事务中的更新操作
        Query OK, 1 row affected (0.01 sec)
        Rows matched: 1  Changed: 1  Warnings: 0

        mysql> commit;//提交事务
        Query OK, 0 rows affected (0.05 sec)

        mysql> select * from test;
        +----+-----+------+--------+
        | id | num | data | status |
        +----+-----+------+--------+
        |  1 |  13 | 测试 |      1 |
        +----+-----+------+--------+
        1 row in set (0.00 sec)

三、事务的特点

3.1 ACID

一个具备运行良好的事务处理系统, 必须具备这些标准特征————ACID。

A (atomicity) 原子性
所谓原子性, 就是一个事务就是一个不可分割的整体, 要么一次性全部成功, 要么全部失败 这就是事务的原子性。

C(consistency)一致性
所谓一致性, 可以这样理解 A账户要给B账户100 那么A账户少100元B必定要增加100元,它可以说是跟原子性相互互补的一个东西。

I (isolation) 隔离性
所谓隔离性, 就是当一个事务没有commit的时候,在这事务所做的数据修改在另一个事务中是不可见的。如在一个事务中修改了一个字段
的数据, 但在另一条事务这个字段的值还是未修改之前的值。但这只是通常情况下, 关系型数据库会存在隔离级别的功能, 不同隔离级别
下,需要做不用的讨论。

D (durability) 持久性
一旦事务提交,则其所做的修改就会永久保存到数据库中。

四、隔离级别

4.1隔离级别相关SQL

查看当前会话隔离级别 select @@tx_isolation;

查看系统当前隔离级别 select @@global.tx_isolation;

设置当前会话隔离级别 set session transaction isolation level REPEATABLE READ/ READ UNCOMMITTED/ repeatable read/;

设置系统当前隔离级别 set global transaction isolation level REPEATABLE READ/ READ UNCOMMITTED/ repeatable read/;

4.2隔离级别

MySQL的事务初探_第1张图片

READ UNCOMMITTED级别:事务中的修改, 即使没有提交, 对其他事务也都是可见的。事务可以读取未提交的数据, 这也被称为脏读(Dirty Read)。这个级别会导致很多问题,性能也不会比其他级别高多少,非特别情况不会使用到此级别。

测试脏读

窗口1:

mysql> start transaction;//开启事务
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test;
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |   1 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.01 sec)

mysql> update test set num = 2 where id = 1; //在事务中把num更新为2。--步骤1
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from test where id = 1;//可以看到在此条记录已被成功更新, 之后被窗口2查询-对应窗口2第一条查询。--步骤2
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |   2 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

mysql> rollback; //回滚。记录被回滚原来的状态, 步骤3时查询的数据为脏数据--步骤4
Query OK, 0 rows affected (0.03 sec)

窗口2:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
//查询1
mysql> select * from test where id = 1; //数据一致, 这条就成为了脏数据。--步骤3
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |   2 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

//之后不再查询。

说明:可以看到当事务1中未COMMIT之前的更新操作也会影响到事务2中的查询, 查出来的数据我们称之为脏数据, 这种现象称之为脏读。

测试不可重复读

窗口1:

mysql> start transaction;//开启事务
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test;
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |   1 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.01 sec)

mysql> update test set num = 2 where id = 1; //在事务中把num更新为2,之后窗口2查看此条记录-对应第一条查询。--步骤1
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update test set num = 3 where id = 1; //在事务中把num更新为3,之后窗口2查看此条记录-对应第二条查询。--步骤3
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

窗口2:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
//查询1
mysql> select * from test where id = 1; --步骤2
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |   2 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

//查询2
mysql> select * from test where id = 1; --步骤4
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |   3 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

说明:不可重复读的重点是修改, 可以看到窗口2中同样的查询确查出了不一样的结果,所以我们称为不可重复读。

测试幻读

窗口1:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test where data = '测试'; //符合此条件下的记录有一条, 之后窗口2做insert 操作 ---步骤1
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |   1 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.08 sec)

mysql> select * from test where data = '测试'; //符合此条件的记录有三条, 之后窗口2做rollback 操作 ---步骤3
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |   1 | 测试 |      1 |
|  2 |   0 | 测试 |      1 |
|  3 |   0 | 测试 |      1 |
+----+-----+------+--------+

3 rows in set (0.00 sec)

mysql> select * from test where data = '测试';//符合此条件下的记录有一条 ---步骤5
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |   1 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

窗口2:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into test (data) values('测试'), ('测试'); ---步骤2
Query OK, 2 rows affected (0.02 sec)
Records: 2  Duplicates: 0  Warnings: 0

rollback; ---步骤4

说明:幻读的重点是写, 可以看到 3次相同的查询 其中2次查询会多出2条记录, 多出来的记录我们称之为幻行, 这种现象我们称之为幻读。


READ COMMITTED级别 : 大多数数据库隔离机制使用此模式(MYSQL例外), 此级别比较符合前面定义I (isolation) 隔离性,一个事务开始时,(2条事务查询同一条记录)查看到的数据只会是commit之后的数据,换句话说, 一个事务从开始到提交前, 所做的任何操作对其他事务是不可见的, 这个级别有时候也叫做不可重复读(nonrepeattableread), 因为执行2次相同的查询会得到不相同的数据。

测试脏读

窗口1:

mysql> select * from test where id = 1; //先查看此条件下的记录 --步骤1
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |   1 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.02 sec)

mysql> select * from test where id = 1; //再次查看此条件下的记录,级别一脏读的情况不会发生 --步骤3
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |   1 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.02 sec)

窗口2:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update test set num = 11 where id = 1; //更新此条记录 --步骤2
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

说明:可以看到在此模式下,事务2的数据操作对事务1是不可见的,脏读的情况不会发生。

测试不可重复读

窗口1:

mysql> select * from test where id = 1; 先查看此条记录。 --步骤1
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |   1 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

mysql> select * from test where id = 1; 再次查看记录。 --步骤4
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  11 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

窗口2:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update test set num = 11 where id = 1; 更新。 --步骤2
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit; 提交。--步骤3
Query OK, 0 rows affected (0.07 sec)

说明:可以看到此模式下,不可重复读的情况还是会发生(窗口1同样条件还是会查出不同的数据)。

测试幻读

窗口1:

mysql> select * from test where data = '测试'; //此条件下的记录, --步骤1
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  11 | 测试 |      1 |
|  6 |   0 | 测试 |      1 |
|  7 |   0 | 测试 |      1 |
|  8 |   0 | 测试 |      1 |
|  9 |   0 | 测试 |      1 |
| 14 |   0 | 测试 |      1 |
| 15 |   0 | 测试 |      1 |
+----+-----+------+--------+
7 rows in set (0.12 sec)

mysql> update test set num = 11 where data ='测试'; //更新次条件下的记录,可以看到6条匹配 --步骤3
Query OK, 6 rows affected (0.00 sec)
Rows matched: 7  Changed: 6  Warnings: 0

mysql> select * from test where data = '测试'; //再次按相同条件查找,发现多了2条记录。 --步骤5

+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  11 | 测试 |      1 |
|  6 |  11 | 测试 |      1 |
|  7 |  11 | 测试 |      1 |
|  8 |  11 | 测试 |      1 |
|  9 |  11 | 测试 |      1 |
| 14 |  11 | 测试 |      1 |
| 15 |  11 | 测试 |      1 |
| 16 |   0 | 测试 |      1 |
| 17 |   0 | 测试 |      1 |
+----+-----+------+--------+
9 rows in set (0.00 sec)

窗口2:

mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into test (data) values ('测试'),('测试');//插入2条记录,未提交 --步骤2
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> commit;//提交 --步骤4
Query OK, 0 rows affected (0.04 sec)

说明:可以看到,明明是7条match, 但是事务2的insert操作导致事务1只更新到7 多出来的2条并没有被更新到(就像幻觉一般), 这也是幻读。


REPEATABLE READ可重复读解决了脏读的问题。该级别保证了在同一个事务中多次读取是的数据是一样的。但是理论上无法解决幻读的问题。上面实验我们可以知道, 只要事务1在2次范围查询得到的记录数不一样,我们就可以称它们为幻读。InnoDB存储引擎通过多版本并发控制(MVCC)解决了幻读的问题。

测试脏读

窗口1:

mysql> select * from test where id  = 1; //步骤1
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  11 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.08 sec)

mysql> select * from test where id  = 1; //步骤4
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  11 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

窗口2:

mysql>  select * from test where id  = 1;
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  11 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

mysql> update test set num = 12 where id = 1; //步骤2
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> rollback; //步骤3
Query OK, 0 rows affected (0.06 sec)

可以看到此模式下不会发生脏读。

测试不可重复读

窗口1:

mysql> select * from test where id = 1; //步骤1
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  11 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

mysql> select * from test where id = 1;//步骤3
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  11 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

mysql> select * from test where id = 1; //步骤6
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  11 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

窗口2:

mysql>  select * from test where id = 1;
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  11 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

mysql> update test set num = 13 where id = 1; //步骤2
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit; //步骤4
Query OK, 0 rows affected (0.05 sec)

mysql> select * from test where id = 1; //步骤5
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  13 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

可以看到不管提交前还是提交后窗口1获取的数据都是一样的。

测试幻读

窗口1:

mysql> select count(*) from test where data = '测试'; //步骤1
+----------+
| count(*) |
+----------+
|        9 |
+----------+
1 row in set (0.04 sec)

mysql> select count(*) from test where data = '测试'; //步骤3
+----------+
| count(*) |
+----------+
|        9 |
+----------+
1 row in set (0.00 sec)

mysql> select count(*) from test where data = '测试'; //步骤6
+----------+
| count(*) |
+----------+
|        9 |
+----------+
1 row in set (0.00 sec)

窗口2:

mysql> insert into test (data) values('测试'), ('测试'); //步骤2
Query OK, 2 rows affected (0.02 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> commit; //步骤4
Query OK, 0 rows affected (0.04 sec)

mysql> select count(*) from test where data = '测试'; //步骤5
+----------+
| count(*) |
+----------+
|       11 |
+----------+
1 row in set (0.00 sec)

可以看到不管提交前还是提交后窗口1获取的记录条数都是一样的。

补充:mysql中这个隔离级别下默认使用了MVCC解决了幻读问题。

SERIALIZABLE可串行化是最高的隔离级别。它通过强制事务串行执行, 避免了前面说的幻读的问题。简单来说SERIALIZABLE会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题, 只有要求数据一致性特别高和接受没有并发的情况下才使用此模式。

测试脏读

窗口1:

mysql> select * from test where id = 1;//步骤1
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  13 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

窗口2:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update test set num = 20 where id = 1; //步骤2
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

可以看到窗口2执行update语句后不能成功。

测试不可重复读

窗口1:

mysql> select * from test where id = 1;//步骤1
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  13 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)

窗口2:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update test set num = 20 where id = 1; //步骤2
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

可以看到窗口2执行update语句后也不能成功。

测试幻读

窗口1:

mysql> select * from test where id = 1; //步骤1
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  13 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.08 sec)

mysql> select * from test where data ='测试'; //步骤3
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

窗口2:

mysql> select * from test where id = 1;
+----+-----+------+--------+
| id | num | data | status |
+----+-----+------+--------+
|  1 |  13 | 测试 |      1 |
+----+-----+------+--------+
1 row in set (0.00 sec)
mysql> insert into test (data) values ('测试'),('测试'); //步骤2
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

可以看到窗口2执行insert语句后 窗口1执行查询语句不能成功。

五、多版本并发控制(MVCC)

5.1 MVCC介绍 (这为<高性能mysql>中原话,但是其中说得太过浅,会导致人混乱,详情情况5.3)

书上所说, MVCC的实现, 是通过保存数据在某个时间点的快照来实现的。也就是说, 不管需要执行多长时间,每个事务看到的数据都是一致的。根据事务开始的时间不同, 每个事务对同一张表, 同一时刻看到的数据都是一致的。根据事务开始的时间不同, 每个事务对同一张表, 同一个时刻看到的数据可能是不一样的。

InnoDB的MVCC, 是通过在每行记录后面保存两个隐藏的列来实现的。这两个列, 一个保存了行的创建时间,一个保存行的过期时间值, 但是存储的并不是实际的时间戳, 而是系统版本号。每开始一个新的事务, 系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号, 用来和查询到的每一行记录的版本号进行比较

5.2 MVCC规则

SELECT

InnoDB会根据以下两个条件检查每行记录:

  • a:InnoDB只查找版本早于单前面事务版本的数据行(也就是, 行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行, 要么是在事务开始前已经存在, 要么是事务自身插入或者修改过的

  • b:行的删除版本要么未定义, 要么大于当前版本事务号。这可以确保事务读取到的行, 在事务开始之前未被删除

  • 只有符合上述两个条件的记录, 才能返回作为查询结果。

INSERT

InnoDB为新插入的每一行保存当前系统版本号作为行版本号。

DELETE

InnoDB为删除的每一行保存当前系统版本号作为行删除标识。

UPDATE

InnoDB为插入一行新记录, 保存单前系统版本号作为行版本号, 同时保存当前系统版本号到原来的行作为行删除标识。

5.3 MVCC原理

相关概念:

  • DB_TRX_ID 事务指针
  • DB_TRX_ID 事务指针 InnDB中每一行的隐藏列, 用来记录单前版本事务ID,也就是上面所说的行的创建时间。
  • DB_ROLL_PTR 事务回滚指针 InnDB中每一行的隐藏列, 用来指向UNDO_LOG的标识符, 也就是上面所说的过期时间。
  • UNDO_LOG(撤销日志) InnoDB里,会将修改前的旧数据(事务开启前)存储在UNDO_LOG中, 当用户执行rollback或者事务不成功的时候,用于把数据恢复到事务开启前的状态。
  • REDU_LOG(重做日志) 和Undo Log相反,Redo Log记录的是新数据的备份。在事务提交前,只要将Redo Log持久化即可,不需要将数据持久化。
  • DELETE BIT 位用于标识该记录是否被删除,这里的不是真正的删除数据,而是标志出来的删除。真正意义的删除是在commit的时候.

过程:

update应该有的步骤

  1. 记录先加载到内存中。

  2. 用排他锁锁定该行

  3. 此时.记录num = 7这条记录到undo log的内存buffer。

  4. 在内存中修改此条记录的num = 6。

  5. redo_log 记录num=6这条记录到undo log的内存buffer。

  6. 将undo log的buffer写到磁盘。

  7. 将redo log的buffer写到磁盘。

  8. 事务提交。

undo_log的形成过程

MySQL的事务初探_第2张图片

MySQL的事务初探_第3张图片

  • 注意:箭头锁指, 一个行的undo_log 是靠id关联的。

则判断可见性算法:

在innodb中,创建一个新事务的时候,innodb会将当前系统(但不包括这条事务)中的活跃事务列表(trx_sys->trx_list)创建一个副本(read view),副本中保存的是系统当前不应该被本事务看到的其他事务id列表。当用户在这个事务中要读取该行记录的时候,innodb会将该行当前的版本号与该read view进行比较。
具体的算法如下:

1.设该行的当前事务id为 trx_id_0 ,read view中最早的事务id为 trx_id_1 , 最迟的事务id为 trx_id_2, 事务创建ID** m_creator_trx_id **。

2.如果 trx_id_0 < trx_id_1 的话,那么表明该行记录所在的事务已经在本次新事务创建之前就提交了,所以该行记录的当前值是可见的, 如果trx_id_0 = m_creator_trx_id, 认为该行由这条事务创建, 所以也是可见的。跳到步骤6.

3.如果 trx_id_0>trx_id_2 的话,那么表明该行记录所在的事务在本次新事务创建之后才开启,所以该行记录的当前值不可见.跳到步骤5。

4.如果 trx_id_1<=trx_id_0<=trx_id_2 , 那么表明该行记录所在事务在本次新事务创建的时候处于活动状态,从 trx_id_1trx_id_2 进行遍历,如果 trx_id_0 等于他们之中的某个事务id的话,那么不可见。跳到步骤5.

5.从该行记录的 DB_ROLL_PTR 指针所指向的回滚段中取出最新的undo-log的版本号,将它赋值该trx_id_0,然后跳到步骤2.

6.将该可见行的值返回。

可以去查看一下mysql的判断可见性源码,这里不做细表(可参考上边资料)

  • 这里需要说明一点 不同的事务隔离级别,可见性的实现也不一样:

  • READ-COMMITTED

  • 事务内的每个查询语句都会重新创建Read View,这样就会产生不可重复读现象发生

  • REPEATABLE-READ

  • 事务内开始时创建Read View , 在事务结束这段时间内 每一次查询都不会重新重建Read View ,从而实现了可重复读。

你可能感兴趣的:(MySQL的事务初探)