技本功丨浅谈MySQL的七种锁

技本功丨浅谈MySQL的七种锁_第1张图片

 作   者 

宋丹琪(花名:三思)

袋鼠云云服务部DBA团队  

数据库工程师

时常会有开发的同学突然紧张兮兮地找我,

然后丢给我一个代码层面的

CannotAcquireLockException的报错,

一脸无辜地问我是不是自己搞出了一个死锁。

技本功丨浅谈MySQL的七种锁_第2张图片

好像大家看到LOCK的字眼

总会第一时间想到死锁而忽略了锁,

难道我们锁没有面子的嘛,

我们锁的大家族可足足有七种呢?

那么到底什么是锁,有哪些锁,

请听我娓娓道来。。


01

共享锁(S锁)和排它锁(X锁)

事务拿到某一行记录的共享S锁,才可以读取这一行,并阻止别的事物对其添加X锁
事务拿到某一行记录的排它X锁,才可以修改或者删除这一行
共享锁的目的是提高读读并发
排他锁的目的是为了保证数据的一致性



02

意向锁

1)意向共享锁

预示事务有意向对表中的某些行加共享S锁

2)意向排他锁

预示着事务有意向对表中的某些行加排他X锁

3) IS、S、IX、X锁之间的兼容性比较

兼容性 IS IX S X
IS 兼容 兼容 互斥 互斥
IX 兼容 兼容 互斥 互斥
S 兼容 互斥 兼容 互斥
X 互斥 互斥 互斥 互斥


4)意向锁的意义在哪里?

1.IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的X,S发生冲突
2.意向锁是在添加行锁之前添加。
3.如果没有意向锁,当向一个表添加表级X锁时,就需要遍历整张表来判断是否存行锁,以免发生冲突
4.如果有了意向锁,只需要判断该意向锁与表级锁是否兼容即可。

03

插入意向锁(insert intention looks)

插入意向锁是间隙锁的一种,针对insert操作产生。
目的是提高插入并发。
多个事物,在同一个索引,同一个范围区间进行插入记录的时候,如果 插入的位置不冲突,不会阻塞彼此。

示例:          

技本功丨浅谈MySQL的七种锁_第3张图片

由于事物一和事物二都是对表的同一索引范围进行insert,使用的插入意向锁,由于插入的记录并不冲突,所以并不会阻塞事物二。如果事物二插入的记录与事物一冲突,会被X锁阻塞。


04

记录锁

对单条索引记录进行加锁,锁住的是索引记录而非记录本身,即使表中没有任何索引,MySQL会自动创建一个隐式的row_id作为聚集索引来进行加锁。

技本功丨浅谈MySQL的七种锁_第4张图片

05

间隙锁(gap锁)


封锁记录中的间隔,防止间隔中被其他事务插入。
间隙锁主要出现在RR隔离级别,避免出现幻读。

MVCC(多版本并发)

1.MVCC的作用

避免脏读、写不阻塞读、实现可重复读、多版本控制

2.在MVCC下,读操作可以分为两种:快照读、当前读

1)快照读

select * from tbl_name where ...

2)当前读

select * from tbl_name where ... for update;
update
delete
insert

3)为什么delete/update也是一种当前读?(如一个update操作)

a.在进行update的时候,MySQL会根据where条件得到过滤出来的第一条记录,并进行加锁(currenet read)
b.对该条记录进行update
c.再次读取下一条记录,直到没有满足条件的记录为止
d.delete原理与之类似

4)为什么insert也是一种当前读?
insert操作可能会触发Unique Key的冲突检查,也会进行一个当前读。

隔离级别

1.Read Uncommitted

可以读取到未提交的事物。

2.Rrad Committed(RC)

针对当前读,RC隔离级别保证对读取到的记录加锁 (记录锁),存在幻读现象。

3.Repeatable Read (RR)

针对当前读,RR隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),不存在幻读现象。

4.Serializable

所有的读操作均为当前读,读加读锁 (S锁),写加写锁 (X锁)。
读写冲突,并发行很差。

几种触发间隙锁的情况

1.id非唯一索引+RR

技本功丨浅谈MySQL的七种锁_第5张图片

SQL:delete from t1 where id = 10;

加锁流程如下:

a.通过id索引定位到第一条满足查询条件的记录,加记录上的X锁,加GAP上的GAP锁,

b.然后加主键聚簇索引上的记录X锁,然后返回;

c.然后读取下一条,重复进行。

d.直至进行到第一条不满足条件的记录[11,f],此时,不需要加记录X锁,但是仍旧需要加GAP锁,最后返回结束。

2.id无索引+RR

SQL:delete from t1 where id = 10;

a.由于id字段无索引,进行全表扫描的当前读,

b.聚簇索引上的所有记录,都被加上了X锁。其次,聚簇索引每条记录间的间隙都被加上了GAP锁。

3.针对id无索引+RR MySQL性能上做的一些优化

semi-consistent read

semi-consistent read开启的情况下,对于不满足查询条件的记录,MySQL会提前放锁。

针对上面的这个用例,就是除了记录[d,10],[g,10]之外,所有的记录锁都会被释放,同时不加GAP锁。

4.semi-consistent read如何触发? 

1)隔离级别是read committed;

2)隔离级别是Repeatable Read,同时设置了innodb_locks_unsafe_for_binlog 参数。 

示例一

技本功丨浅谈MySQL的七种锁_第6张图片

技本功丨浅谈MySQL的七种锁_第7张图片

示例二

技本功丨浅谈MySQL的七种锁_第8张图片

技本功丨浅谈MySQL的七种锁_第9张图片

示例三

技本功丨浅谈MySQL的七种锁_第10张图片

技本功丨浅谈MySQL的七种锁_第11张图片

06

临键锁(Next-Key Locks)


临键锁是记录锁和间隙锁的组合,既锁住了记录也锁住了范围。

临键锁的主要目的,也是为了避免幻读。

如果把事务的隔离级别降级为RC,临键锁就也会失效。

通常情况下,InnoDB在搜索或扫描索引的行锁机制中使用“临键锁(next-key locking)”算法来锁定某索引记录及其前部的间隙(gap),以阻塞其它用户紧跟在该索引记录之前插入其它索引记录。

innodb_locks_unsafe_for_binlog默认为OFF,意为禁止使用非安全锁,也即启用间隙锁功能。将其设定为ON表示禁止锁定索引记录前的间隙,也即禁用间隙锁,InnoDB仅使用索引记录锁(index-record lock)进行索引搜索或扫描,不过,这并不禁止InnoDB在执行外键约束检查或重复键检查时使用间隙锁。

innodb_locks_unsafe_for_binlog的效果:

(1)对UPDATE或DELETE语句来说,InnoDB仅锁定需要更新或删除的行,对不能够被WHERE条件匹配的行施加的锁会在条件检查后予以释放。这可以有效地降低死锁出现的概率;
(2)执行UPDATE语句时,如果某行已经被其它语句锁定,InnoDB会启动一个“半一致性(semi-consistent)”读操作从MySQL最近一次提交版本中获得此行,并以之判定其是否能够并当前UPDATE的WHERE条件所匹配。如果能够匹配,MySQL会再次对其进行锁定,而如果仍有其它锁存在,则需要先等待它们退出。
(3)innodb_locks_unsafe_for_binlog可能会造成幻读

示例一

innodb_locks_unsafe_for_binlog=off的情况下:


技本功丨浅谈MySQL的七种锁_第12张图片

技本功丨浅谈MySQL的七种锁_第13张图片

示例二

innodb_locks_unsafe_for_binlog=on的情况下:

技本功丨浅谈MySQL的七种锁_第14张图片

技本功丨浅谈MySQL的七种锁_第15张图片

   查看binlog日志:

技本功丨浅谈MySQL的七种锁_第16张图片

因此,当innodb_locks_unsafe_for_binlog=on的情况下,会让你容易造成数据的不一致

07

自增长锁

自增长锁是一种表级锁,专门针对auto_increment类型的列。

自增长列锁各模式分析:

innodb_autoinc_lock_mode:自增长长锁模式

0:

不管是insert into values (simple insert)还是insert into select (bulk insert),都是:持有锁、读取/修改、执行SQL、释放,不需要等到事务提交就释放锁,但是需要SQL执行完成,并且不能保证连续。

持有latch ---> 读取和修改auto锁 ---> 执行insert ---> 释放

注意:不需要等待insert所在的事务是否提交

缺点:可能出现数字不连续

持有时间相对过长:SQL执行完毕,不需要事务提交

1:

默认值,对于回滚是不能保证自增长列连续的。

对于simple insert (insert into values):持有锁、读取、释放、执行SQL,最快,不需要执行完SQL就释放,不需要等待insert执行完毕就可以释放锁。

对于bulk insert (insert into select):持有锁、读取、执行SQL、释放,需要执行完SQL才释放。(对于批量insert来说等同于0)

优点:

     对于simple insert 来说,性能比0好些,对于批量来说,性能等同于0

缺点:

     数字不连续

     对于批量来说持有锁的时间相对过长

2:

经常改为2,主要是为了唯一,不是为了连续,在批量insert时或者批量insert并发的时候用

优点:速度最快

缺点:只能保证唯一,不能保证递增和连续。持有、读取和修改、释放、执行SQL

建议修改成2,对于批量的insert可以提升性能

示例


技本功丨浅谈MySQL的七种锁_第17张图片

技本功丨浅谈MySQL的七种锁_第18张图片


由于innodb_autoinc_lock_mode=1,所以事物一并不会阻塞事物二的simple insert,保证了id字段的唯一性。

参考引用:

何登成的技术博客——《MySQL 加锁处理分析》
微信公众号:架构师之路

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/31556433/viewspace-2219082/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/31556433/viewspace-2219082/

你可能感兴趣的:(技本功丨浅谈MySQL的七种锁)