mysql隔离级别及锁测试

mysql中有下面几种类型的锁
share lock 读行的时候会有这个锁
exclusive (X) lock 在写的时候会有这个锁
innodb允许行锁和表上的锁共存,所以有了意向锁,意向锁是一种表级别锁,来表明事务在表上稍后要用什么类型的锁来访问行,有共享意向锁,和排他意向锁
SELECT … LOCK IN SHARE MODE 设置了共享意向锁,SELECT … FOR UPDATE 设置了排他意向锁
在事务获取s之前要先获取is,在事务获取x之前要先获取ix,下面是锁的兼容性

X	  IX	   S	     IS

X Conflict Conflict Conflict Conflict
IX Conflict Compatible Conflict Compatible
S Conflict Conflict Compatible Compatible
IS Conflict Compatible Compatible Compatible
记录锁,记录锁是在索引记录上的锁,如果表上没有索引,那么innodb会创建一个隐藏的聚簇索引。
gap锁是在索引记录间隔上的锁, 或是第一个索引记录之前或最后一个索引记录之后的锁,间隔锁是并发性和性能之间的妥协,在某些事物隔离级别中有,在另外的级别中没有,在下面的实验中可以看得到。
间隔锁在唯一索引的查询上是不存在的,可以设置级别为读提交或使用innodb_locks_unsafe_for_binlog 系统参数来禁用
Next-Key Locks
next-key lock是索引记录上记录锁和索引记录之前间隔锁的组合,当innodb在查询索引记录的时候,为了实现行锁,会在符合条件的记录上设置锁,因此实际上的行锁是索引记录锁,next-key锁是索引记录锁加上索引记录之前的gap上的gap锁,如果一个会话在索引的记录上设置了共享或排他锁,那么另外的会话不能再这个索引记录之前的gap里插入索引记录。
innodb 使用next-keylock ,防止幻想读,为什么药使用next-key lock来阻止幻想读?
在官网上的例子中SELECT * FROM child WHERE id > 100 FOR UPDATE;如果child中有记录90,102,那么语句加的记录锁就是在102开始的,记录锁就没有阻止insert 101记录,这样下次的select 语句就看到了101,出现了幻想读,所以要在加间隔锁,这个例子中间隔是90到102,所以间隔锁阻止了insert记录,此处的间隔锁也是索引记录的顺序
next-key是为了阻止幻想读的,所以在列上有唯一索引的情况下,next-key的gap锁也会存在,不会消失

插入意向锁
这个锁是insert操作在插入之前设置的gap 锁

数据有下面几个可视度
脏读:可以读取到未提交的数据
不可重复读:在不同时间不能读取相同的内容,数据被更新了
幻想读:在T1时间执行了一个查询,T2再次执行的时候有了更多的数据
幻想读是可重复读,区别是:已读取的数据是不变的,只是看到了更多的数据。
sql标准定义的隔离级别下的可见性

mysql隔离级别及锁测试_第1张图片
REPEATABLE READ(MySQL默认的隔离级别)
对于select for update,lock in share mode的读取,update,delete语句,锁取决于查询是否用了唯一索引,对于唯一索引查询,只是锁定行,对于其他类型的查询,锁定的是范围,使用gap锁或next-key 锁来阻塞其他会话对被查询范围覆盖的间隔的插入。
READ COMMITTED
这个隔离级别上,对于select for update,lock in share mode,update,delete语句使用的行锁,
READ UNCOMMITTED
是在非锁定方式工作
SERIALIZABLE
select被转换成了select …lock in share mode
mysql使用的是可重复读的隔离级别,这个级别中,在相同事务中的 一致性读都是读取的第一个select建立的快照。下面的例子不准确,例子中的select是读取的快照,不是当前读,(如果是非锁定读,MySQL通过LSN返回回滚段中的数据。与读提交隔离级别不同的是,Read Committed总是返回最新的版本,而REPEATABLE-READ返回查询开始时LSN那个版本的数据。)所以看不到会话B中的改变。

mysql隔离级别及锁测试_第2张图片
默认级别下锁测试,d表无索引
Session A
set autocommit=off;
Begin;
update d set table_type=‘aa’ where table_name=‘test’;

Session B
set autocommit=off;
Begin;
update d set table_type=‘aa’ where table_name=‘d’;–被阻塞,同样insert,delete也会被阻塞,没有索引,相当于添加了全表锁

表上有普通索引的测试
sessionA
Begin
Select * from t5 where id=11 for update;

session
Begin
Do nothing
Insert into t5 values(12);–被阻塞
Insert into t5 values(5);–正常
表上有普通索引,存在间隔锁。上面的表情况中,有记录2,10,11,13所以,它的间隔区间是 (负无穷,2],(2,10],(10,11],(11,13],(13,正无穷],我们对11加了锁,导致11后面的一个间隔区间(11,13]被锁定,所以插入12被阻塞,

在创建唯一索引后,没有了gap锁,插入12是正常的,这个就不测试了
下面看下next-key lock的存在
t5 | CREATE TABLE t5 (
id int(11) DEFAULT NULL,
UNIQUE KEY idx_id (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
select * from t5;
±-----+
| id |
±-----+
| 2 |
| 11 |
| 13 |
±-----+
session A:
select * from t5 where id>7 for update;
±-----+
| id |
±-----+
| 11 |
| 13 |

sessionB:
insert into t5 values(6);–等待,此处的等待就是为了防止幻想读,是gap锁导致的,锁定了2到11区间,这个区间内的值的插入都会被阻塞。验证了上面的结论。

补充一个在rr下主键上的范围操作导致的next key lock
[email protected]>select * from t1;
±—±---±—±---+
| c1 | c2 | c3 | c4 |
±—±---±—±---+
| 0 | 0 | 0 | 0 |
| 1 | 1 | 1 | 0 |
| 3 | 3 | 3 | 0 |
| 4 | 4 | 4 | 0 |
±—±---±—±---+
session1
[email protected]>begin;
Query OK, 0 rows affected (0.01 sec)

[email protected]>select * from t1 where c1<=1 for update;
±—±---±—±---+
| c1 | c2 | c3 | c4 |
±—±---±—±---+
| 0 | 0 | 0 | 0 |
| 1 | 1 | 1 | 0 |
±—±---±—±---+
2 rows in set (0.00 sec)

session2,被阻塞

[email protected]>begin;
Query OK, 0 rows affected (0.00 sec)

[email protected]>delete from t1 where c1=3;
CC – query aborted

rr级别下的主键操作也是next key lock,rc下好点不是next key lock了

你可能感兴趣的:(MYSQL)