Innodb锁相关总结

一、InnoDB共有七种类型的锁:
(1)共享/排它锁(Shared and Exclusive Locks)
(2)意向锁(Intention Locks)
(3)插入意向锁(Insert Intention Locks)
(4)记录锁(Record Locks)
(5)间隙锁(Gap Locks)
(6)临键锁(Next-key Locks)
(7)自增锁(Auto-inc Locks)

二、共享/排它锁(Shared and Exclusive Locks)

  参考博客:https://blog.csdn.net/u014292162/article/details/83271299
  共享锁(Share Locks,记为S锁),读取数据时加S锁,也叫读锁。
  排他锁(eXclusive Locks,记为X锁),修改数据时加X锁,也叫写锁。
    (1)事务拿到某一行记录的共享S锁,才可以读取这一行;
    (2)事务拿到某一行记录的排它X锁,才可以修改或者删除这一行;
  其兼容互斥表如下:
   S X
    S 兼容 互斥
    X 互斥 互斥
   即:
    (1)共享锁之间不互斥,多个事务可以拿到一把S锁,读读可以并行;
    (2)排他锁与其他任何锁互斥,只有一个事务可以拿到X锁,写写/读写必须互斥;
  因此,一旦写数据的事务没有完成,数据是不能被其他事务读取的,这对并发度有较大的影响。写事务没有提交,读相关数据的select也会被阻塞。  
  MyISAM引擎只有表级的共享锁、排他锁,而Innodb还有行级的共享锁、排他锁。

  由于数据加排他锁后,读相关数据也会被阻塞,因此并发性很低,由此引出了MVCC多版本并发控制:
  参考博客:https://www.cnblogs.com/dongqingswt/p/3460440.html
  数据多版本是一种能够进一步提高并发的方法,它的核心原理是:
    (1)写任务发生时,将数据克隆一份,以版本号区分;
    (2)写任务操作新克隆的数据,直至提交;
    (3)并发读任务可以继续读取旧版本的数据,不至于阻塞;
  MVCC就是通过“读取旧版本数据”来降低并发事务的锁冲突,提高任务的并发度。

三、意向锁(Intention Locks)

  意向锁是指,未来的某个时刻,事务可能要加共享/排它锁了,先提前声明一个意向。
  意向锁有这样一些特点:
    (1)首先,意向锁,是一个表级别的锁(table-level locking);
    (2)意向锁分为:
      意向共享锁(intention shared lock, IS),它预示着,事务有意向对表中的某些行加共享S锁
      意向排它锁(intention exclusive lock, IX),它预示着,事务有意向对表中的某些行加排它X锁
      举个例子:
        select ... lock in share mode,要设置IS锁;
        select ... for update,要设置IX锁;
    (3)意向锁协议(intention locking protocol)并不复杂:
      事务要获得某些行的S锁,必须先获得表的IS锁
      事务要获得某些行的X锁,必须先获得表的IX锁
    (4)由于意向锁仅仅表明意向,它其实是比较弱的锁,意向锁之间并不相互互斥,而是可以并行,其兼容互斥表如下:
    IS IX
     IS 兼容 兼容
     IX 兼容 兼容
    (5)额,既然意向锁之间都相互兼容,那其意义在哪里呢?它会与表级的共享锁/排它锁互斥,其兼容互斥表如下:
           S X
      IS 兼容 互斥
      IX 互斥 互斥
  意向锁的作用就是:
    当一个事务在需要获取资源的锁定时,如果该资源已经被排他锁占用,则数据库会自动给该事务申请一个该表的意向锁。
    如果自己需要一个共享锁定,就申请一个意向共享锁。如果需要的是某行(或者某些行)的排他锁定,则申请一个意向排他锁。
    注:意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。
  例如下面的例子:
    (1)事务A锁住了表中的一行,让这一行只能读,不能写,即行级S锁。
    (2)之后,事务B申请整个表的写锁,及表级X锁。
  如果没有意向锁,如果事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。
  数据库需要避免这种冲突,就是说要让B的申请被阻塞,直到A释放了行锁。
  数据库要怎么判断这个冲突呢?
step1:判断表是否已被其他事务用表锁锁表
step2:判断表中的每一行是否已被行锁锁住。
注意step2中通过遍历查询,这样的判断方法效率实在不高,因为需要遍历整个表。
于是就有了意向锁。在意向锁存在的情况下,事务A必须先申请表的意向共享锁IS,成功后再申请一行的行锁。
在意向锁存在的情况下,上面的判断可以改成
step1:不变
step2:发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,因此,事务B申请表的写锁会被阻塞。
最终结论:
(1)申请意向锁的动作是数据库完成的,就是说,事务A申请一行的行锁的时候,数据库会自动先开始申请表的意向锁,不需要我们程序员使用代码来申请。
(2)IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的X,S发生冲突。行级别的X和S按照普通的共享、排他规则即可。
所以之前的示例中第2步不会冲突,只要写操作不是同一行,就不会发生冲突。
四、插入意向锁(Insert Intention Locks)

  ---InnoDB并发插入,居然使用意向锁? https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651961461&idx=1&sn=b73293c71d8718256e162be6240797ef&chksm=bd2d0da98a5a84bfe23f0327694dbda2f96677aa91fcfc1c8a5b96c8a6701bccf2995725899a&scene=21#wechat_redirect
  插入意向锁,是间隙锁(Gap Locks)的一种(所以,也是实施在索引上的),它是专门针对insert操作的。
  多个事务,在同一个索引,同一个范围区间插入记录时,如果插入的位置不冲突,不会阻塞彼此。
  例如:在MySQL,InnoDB,RR下:
    t(id unique PK, name);
    数据表中有数据:
      10, shenjian
      20, zhangsan
      30, lisi
    事务A先执行,在10与20两条记录中插入了一行,还未提交:
      insert into t values(11, xxx);
    事务B后执行,也在10与20两条记录中插入了一行:
      insert into t values(12, ooo);
    (1)会使用什么锁?
    (2)事务B会不会被阻塞呢?
    回答:虽然事务隔离级别是RR,虽然是同一个索引,虽然是同一个区间,但插入的记录并不冲突,故这里:
使用的是插入意向锁
并不会阻塞事务B

五、记录锁(Record Locks)

  记录锁即行锁,只有Innodb引擎才有,它封锁索引记录,例如:
    select * from t where id=1 for update;
    它会在id=1的索引记录上加X锁,以阻止其他事务查询、插入,更新,删除id=1的这一行,即与其他任何锁互斥。
    select * from t where id=1 in share mode;
    它会在id=1的索引记录上加S锁,以阻止其他事务插入,更新,删除id=1的这一行,但是其他事务也可以获取该行的S锁,即读读不互斥。
  需要说明的是:
    select * from t where id=1;
  则是快照读(SnapShot Read),它并不加锁,根据MVCC查询。

六、间隙锁(Gap Locks)

  间隙锁,它封锁索引记录中的间隔,或者第一条索引记录之前的范围,又或者最后一条索引记录之后的范围。
  依然是上面的例子,InnoDB,RR:
    t(id PK, name KEY, sex, flag);
  表中有四条记录:
    1, shenjian, m, A
    3, zhangsan, m, A
    5, lisi, m, A
    9, wangwu, f, B
  这个SQL语句
    select * from t where id between 8 and 15 for update;
  会封锁区间,以阻止其他事务id=10的记录插入。
  画外音:为什么要阻止id=10的记录插入?
    如果能够插入成功,头一个事务执行相同的SQL语句,会发现结果集多出了一条记录,即幻影数据。
    间隙锁的主要目的,就是为了防止其他事务在间隔中插入数据,以导致幻读。
    如果把事务的隔离级别降级为读提交(Read Committed, RC),间隙锁则会自动失效。因为RC隔离级别不能阻止幻读。而RR级别根据间隙锁防止了幻读。

七、临键锁(Next-key Locks)

  临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间。
  更具体的,临键锁会封锁索引记录本身,以及索引记录之前的区间。
  如果一个会话占有了索引记录R的共享/排他锁,其他会话不能立刻在R之前的区间插入新的索引记录。
  依然是上面的例子,InnoDB,RR:
    t(id PK, name KEY, sex, flag);
  表中有四条记录:
    1, shenjian, m, A
    3, zhangsan, m, A
    5, lisi, m, A
    9, wangwu, f, B
  PK上潜在的临键锁为:
    (-infinity, 1]
    (1, 3]
    (3, 5]
    (5, 9]
    (9, +infinity]
  执行查询语句:
    select * from t where id between 8 and 15 for update;
  则会在(5,9)和(9, +infinity]加上临建锁,此时如果执行插入语句:
    insert into t values(10, xxx);
  则会阻塞。
  临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。
  参考:InnoDB,select为啥会阻塞insert? https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651961471&idx=1&sn=da257b4f77ac464d5119b915b409ba9c&chksm=bd2d0da38a5a84b5fc1417667fe123f2fbd2d7610b89ace8e97e3b9f28b794ad147c1290ceea&scene=21#wechat_redirect

八、自增锁(Auto-inc Locks)  

  参考:插入InnoDB自增列,居然是表锁? https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651961455&idx=1&sn=4c26a836cff889ff749a1756df010e0e&chksm=bd2d0db38a5a84a53db91e97c7be6295185abffa5d7d1e88fd6b8e1abb3716ee9748b88858e2&scene=21#wechat_redirect
  自增锁是一种特殊的表级别锁(table-level lock),专门针对事务插入AUTO_INCREMENT类型的列。
  最简单的情况,如果一个事务正在往表中插入记录,所有其他事务的插入必须等待,以便第一个事务插入的行,是连续的主键值。
  例如:t(id AUTO_INCREMENT, name);,其中id为自增

原文地址:https://www.cnblogs.com/aiqiqi/p/10512051.html