秒懂INNODB的锁

1,736 阅读5分钟

今天我们来聊聊MySQL中InnoDB存储引擎的锁。

锁是数据库系统系统区别于文件系统的一个关键特性。

lock和 latch

latch

latch在MySQL中是用来保证并发多线程操作操作临界资源的,锁定的对象线程,是和咱们使用的Java等传统语言中的锁意义相近,而且没有死锁检测的机制。

lock

lock是MySQL中在事务中使用的,锁定的对象是事务,来锁定数据库中表、页、行;通常只有在事务commit或者rollback后进行释放。lock是有死锁机制的,当出现死锁时,lock有死锁机制来解决死锁问题:超时时间(参数innodb_lock_wait_timeout)、wait-for graph

我们通常讲的MySQL的“锁”,一般就是说的lock。

以下就是InnoDB中“锁”的大分类:

lock的种类

MySQL Lock大体上可以分为:表锁、行锁、意向锁三种。

共享/排他锁

行锁分为:S LockX LockS Lock :读锁;X Lock:写锁。 两锁之间的兼容性如下。

       X         S
X     不兼容     不兼容
S     不兼容     兼容

简单总结为:读锁可以读,读锁不可写;写锁不可读也tm不可写。

意向锁

InnoDB支持多粒度的锁,即:允许表锁和行锁同时存在。 但是,假如表锁覆盖了行锁的数据,所以表锁和行锁也会产生冲突。如:

trx1 BEGI

trx1 给 T1 加X锁修改数据。

trx2 BEGIN

trx2 给 T1 加表锁修改表结构

这样,表锁和行锁之间就产生了冲突,为了解决这种表锁和行锁共存的问题,就产生了意向锁这个东西。 意向锁:从字面意思也很好理解,就是提前表明一个“意向”。

意向锁分为:

  • 意向共享锁。它预示着,事务有意向对表中的"某些行"加S锁。select xxxx lock in share mode,要设置IS锁。
  • 意向排他锁。它预示着,事务有意向表中的“某些行”加X锁。select xxx for update,要设置IX锁。

但意向锁仅仅是表明意向,它其实非常弱,意向锁之间可以相互并行,并不是排斥的: 意向锁之间的兼容性问题:

      IS     IX

IS   兼容    兼容

IX   兼容    兼容 

但是,意向锁可以和行锁互斥。

        S                X

IS     兼容           互斥不兼容

IX    互斥不兼容        互斥不兼容

于是,上述现象就变为了:

trx1 BEGIN

trx1 给 T1 先加IX ,然后在某一行记录加X锁。

trx2 BEGIN

trx2 给 T1 加表锁(事务被阻塞,等待加锁成功)

trx2 修改表结构

主键自增锁

自增锁(auto-inc Locks)是一种特殊的表级锁,专门针对事务插入AUTO_INCREMENT类型的列,往往就是主键列。可以保证主键的值自增是“原子操作”,不会出现一致性、唯一性问题。

行锁的具体分类

InnoDB存储引擎有以上3种行锁算法。以上3种,都是实现在索引上的。

记录锁(Record Lock)

记录锁(Record Lock)总是会去锁住索引记录。 假如没有任何一个索引,那么InnoDB会锁住隐形创建的那个主键。

注意:这里锁的是索引,不一定只是主键索引哦,还可能是二级普通索引。

间隙锁(Gap Lock)

顾名思义,它会封锁索引记录中的“缝隙”,让制其他事务在“缝隙”中插入数据。 它锁定的是一个不包含索引本身的范围。

例如以下索引数据:

间隙锁(Gap Lock)可以锁的将是以下范围

具体的范围还要根据查询条件不同而定。 间隙锁开启的事务隔离级别是 Repeatable Read,如果把数据库事务级别降为Read Committed(默认是 Repeatable Read),间隙锁则会自动失效。

临键锁(Next-Key Lock)

Next-Key Lock可以说是记录锁(Record Lock)和间隙锁(Gap Lock)的组合,既封锁了"缝隙",又封锁了索引本身。

还是上面的索引数据:

临键锁(Next-Key Lock)锁住的范围将是:

Next-key Lock在索引具有唯一性的时候,例如主键索引的数据,将会降级为记录锁(Read Lock),以增加并发性。 例如:

        T1                                     T2
        BEGIN;                   |            
   select * From T               |           
   where id = 5 for update       |
---------------------------------------------------------------
                                 |            BEGIN; 
                                 |          Insert into t (4, xx);
---------------------------------------------------------------              
                                 |          COMMIT
                                 |             
----------------------------------------------------------------
     COMMIT                      |                            
                                 |
                                 |
-----------------------------------------------------------------

以上情况,就会把Next-key Lock降级为记录锁(Read Lock)

再谈不可重复读(No Reaptable Read)和幻读(Phantom Problem)

有些很权威的书中认为这俩是同一个概念,例如:<<MySQL技术内幕 InnoDB存储引擎>>。 但是就目前网络上的众多总结和个人看法,认为区别如下:

不可重复读:修改。在同一个事务中,主要是说多次读取一条记录, 发现该记录中某些列值被修改过。 幻读:增加或者删除。在同一个事务中,同一条完全相同的查询语句返回的结果集行数不同。

参考:stackoverflow.com/questions/1…

认真的说,多版本并发控制 MVCC(读)和 临键锁 Next-Key Lock(写)共同解决了幻读问题。

关于MVCC的原理,就是每份数据会有快照,事务中读取数据(简单的select xxx fromselect xx from xx for update或者select xx from xxx in share mode不行)的时候,如果数据被锁住了,就读以前留下的快照数据。在此不过多赘述了。

以下为多版本并发控制原理图

MVCC只在Read CommittedRepeatable Read下会开启。但是在这两种隔离级别下对于快照指定的数据定义不同。

Read Committed下,MVCC读取的是被锁定数据的最新的一份数据。 在Repeatable Read下,MVCC读取的是事务刚开始时候的数据。

更多精彩内容,请关注我的微信公众号 互联网技术窝 或者加微信共同探讨交流: