1. 隔离级别
1. read uncommitted
事务可以看见其他未提交事务的修改。会导致脏读。
2. read committed
事务只能看见其他提交事务的修改。但是如果事务A读取一批数据set,其他事务之后修改了这个数据set并提交(此时事务A没有提交),这时事务A再读取数据set就跟第一次读取的结果不一致。会导致不可重复读。
3. repeatable read
在事务执行过程中,重复读到的数据是一致的
4. serializable
2. 问题
1. 脏读
事务A读到了事务B未提交的数据。(read uncommitted)
2. 不可重复读
事务A第一次读取行num,此时事务B修改行num并提交,事务A再读行num,数据会发生变化。(read uncommitted, read committed)
3. 幻读
事务A第一次查询范围query_range,返回n行,此时事务B在该查询范围内插入了一行数据并提交,事务A再次查询范围query_range会看到B插入的数据。(read uncommitted, read committed, repeatable read)
The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.
在同一个事务执行过程中,两次相同的查询,但是查询的返回数据行数不同。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
read uncommitted | yes | yes | yes |
read committed | no | yes | yes |
repeatable read | no | no | yes |
serializable | no | no | no |
3. Innodb 行锁算法
1. record lock 锁住某一行
2. gap lock 锁住两行之间的间隙(不包括行本身)
3. next key lock 同时应用1,2
4. 实例
0. current read, snapshot read
1. 实例1
- 隔离级别 repeatable read
- 数据库 mysql innodb
CREATE TABLE `test_lock` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
mysql> select * from test_lock;
+----+------+
| id | a |
+----+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
| 6 | 6 |
+----+------+
6 rows in set (0.00 sec)
开启sessionA, sessionB
sessionA> begin;
Query OK, 0 rows affected (0.01 sec)
sessionA> delete from test_lock where a=3;
Query OK, 1 row affected (0.00 sec)
sessionA>
此时锁(行锁、gap锁)的情况如下图,标红为加锁,采用next key lock。(不光在索引a上加锁,索引a中3所对应的主键索引也会加锁,只画了索引a)
注意:这里是主键索引的顺序与a索引的顺序一致的情况。一致的情况下,新插入的4会插入在原来的4之后。我们定义函数index_key(x), 表示获取x所对应的主键索引,new(x)表示新插入的x,old(x)表示已经存在的x。如果index_key(old(4))>index_key(new(4)),那新的4是插不进去的,因为新的4会被放在老的4的前面。同理,新插入的2也有可能插入进去(只要index_key(new(2))<index_key(old(2)))。具体的例子整理后发出
对于sessionB,插入2,3失败,插入4成功。(如图lock.png,新插入的2会被2与3之间gap锁阻止,新插入的3肯定失败,但是新插入的4就没问题)
sessionB> insert into test_lock(a) values(2);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
sessionB> insert into test_lock(a) values(3);
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
sessionB> insert into test_lock(a) values(4);
Query OK, 1 row affected (0.00 sec)
2. 实例2
- 隔离级别 repeatable read
- 数据库 mysql innodb
验证实例1的另一种情况。
CREATE TABLE `test_lock` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
mysql> select * from test_lock2;
+----+------+
| id | a |
+----+------+
| 1 | 1 |
| 5 | 3 |
| 3 | 5 |
| 7 | 7 |
+----+------+
6 rows in set (0.00 sec)
开启sessionA:
sessionA> begin;
sessionA> delete from test_lock2 where a=5;
这个时候情况如下图所示:
如果按照实例1的分析,不看主键索引id,应该插入(id=4,a=3)应该会被gap锁锁定,但是事实是可以插入成功。因为插入的(i4=d,a=3)放在了(id=5,a=3)的上面。
同样的道理,不看主键索引id,通过实例1的分析,插入(id=6,a=7)应该可以插入,但是事实不行,因为(id=6,a=7)被a=5与a=7之间的gap锁阻止了。
新插入的(id=4, a=3),需要判断会插入在(id=5, a=3)之前还是之后,很明显之前(4<5),那么(id=3, a=5)与(id=5, a=3)之间的gap锁不会阻止。
新插入的(id=6, a=7),需要判断会插入在(id=7, a=7)之前还是之后,很明显之前(6<7),那么(id=6, a=7)会被(id=3, a=5)与(id=5, a=3)与(id=7, a=7)之间的gap锁阻止。