zhenlanghuo's Blog

Mysql学习笔记——锁

2020/01/22

读写锁

从锁的粒度分,mysql的锁分为表锁和行锁,而无论表锁还是行锁,都有读写锁之分,读锁又称共享锁,写锁又称排它锁。

一般来说,读锁与读锁兼容,与写锁冲突;而写锁与写锁、读锁冲突。

表锁

表锁是由Mysql服务器实现的。

表锁的类型有以下三种:

  • 读写锁:LOCK_S(读锁),LOCK_X(写锁)
  • 读写意向锁: LOCK_IS(读意向锁),LOCK_IX(写意向锁)
  • 自增锁:LOCK_AUTO_INC

读写锁

读写锁使用一次封锁技术,某个会话使用lock tables命令对要用到的表加锁以后,在释放这些锁之前,该会话只能访问那些加锁的表,不能访问其他表。

1
2
3
4
5
LOCK TABLES 
tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}
[, tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}] ...

UNLOCK TABLES
  • 对于读锁
    • 持有读锁的会话可以读表,但不能写表;
    • 允许多个会话同时持有读锁;
    • 其他会话就算没有给表加读锁,也是可以读表的,但是不能写表;
    • 其他会话申请该表写锁时会阻塞,直到锁释放。
  • 对于写锁
    • 持有写锁的会话既可以读表,也可以写表;
    • 只有持有写锁的会话才可以访问该表,其他会话访问该表会被阻塞,直到锁释放;
    • 其他会话无论申请该表的读锁或写锁,都会阻塞,直到锁释放。
  • 锁的释放规则如下:
    • 使用 UNLOCK TABLES 语句可以显示释放表锁;
    • 如果会话在持有表锁的情况下执行 LOCK TABLES 语句,将会释放该会话之前持有的锁;
    • 如果会话在持有表锁的情况下执行 START TRANSACTION 或 BEGIN 开启一个事务,将会释放该会话之前持有的锁;
    • 如果会话连接断开,将会释放该会话所有的锁。

读写意向锁

读写意向锁是为了方便检测表锁和行锁的冲突而引入的。

读写意向锁为表级锁,分为读意向锁(IS)和写意向锁(IX)。当事务意图读或写一条记录时,会先在表上加上意向锁,然后才在要操作的记录上加上读锁或者写锁。这样判断表中是否有记录加锁就很简单了,只要看下表上是否有意向锁就行了。

意向锁之间是不会产生冲突的,也不和 AUTO_INC 表锁冲突,它只会阻塞表级读锁或表级写锁,另外,意向锁也不会和行锁冲突,行锁只会和行锁冲突。

自增锁

自增锁(AUTO_INC)是一种特殊的表锁。

当插入表中有自增列时,数据库需要自动生成自增值,在生成之前,它会先为该表加 AUTO_INC 表锁,其他事务的插入操作阻塞,这样保证生成的自增值肯定是唯一的。AUTO_INC 锁具有如下特点:

  • AUTO_INC 锁互不兼容,也就是说同一张表同时只允许有一个自增锁;
  • 自增锁不遵循二段锁协议,它并不是事务结束时释放,而是在 INSERT 语句执行结束时释放,这样可以提高并发插入的性能。
  • 自增值一旦分配了就会 +1,如果事务回滚,自增值也不会减回去,所以自增值可能会出现中断的情况。

AUTO_INC 表锁会导致并发插入的效率降低,为了提高插入的并发性,MySQL 从 5.1.22 版本开始,引入了一种可选的轻量级锁(mutex)机制来代替 AUTO_INC 锁,我们可以通过参数 innodb_autoinc_lock_mode 控制分配自增值时的并发策略

表锁兼容矩阵

下面是各个表锁之间的兼容矩阵:

image_1dv5ulai81n1vekpg2me8j1ies9.png-11.9kB

这个矩阵看上去有点眼花缭乱,其实很简单,因为是斜对称的,所以我们用一条斜线把表格分割成两个部分,只需要看左下角的一半即可。总结起来有下面几点:

  • 意向锁之间互不冲突;
  • S 锁只和 S/IS 锁兼容,和其他锁都冲突;
  • X 锁和其他所有锁都冲突;
  • AI 锁只和意向锁兼容;

行锁

行锁一般是有数据库引擎来实现的,下面讨论的是innodb引擎的行锁。

行锁的类型有以下四种:

  • 记录锁 Record Locks
  • 间隙锁 Gap Locks
  • Next-Key Locks
  • 插入意向锁 Insert Intention Locks

以上类型的锁(除插入意向锁)都有读写(共享排他)之分。

四种类型的锁

记录锁

记录锁是最基本最简单的行锁,记录锁是对符合条件的记录都加上锁。

记录锁是加在索引上的(就算一个表没有建索引,数据库也会隐式的创建一个索引),如果查询(优化器)使用的二级索引,那么记录锁不仅会加在二级索引上的位置,也会加在相应的主键索引(聚簇索引)上。

间隙锁

间隙锁是一种加在两个索引之间(或第一个索引之前,或最后一个索引之后)的锁,是innodb为了解决幻读问题而引入的锁。

使用间隙锁可以防止其他事务在这个范围内插入或修改记录,保证两次读取这个范围内的记录不会变,从而不会出现幻读现象。

间隙锁在RR级别的隔离级别才会出现。

间隙锁和间隙锁之间是互不冲突的,间隙锁唯一的作用就是为了防止其他事务的插入,所以加间隙 S 锁和加间隙 X 锁没有任何区别。

Next-Key Locks

Next-Key Locks 是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁,通常用左开右闭区间来表示 Next-key 锁。

插入意向锁

插入意向锁是一种特殊的间隙锁(所以有的地方把它简写成 II GAP)。

插入意向锁的作用是为了提高并发插入的性能,因为普通的间隙锁不允许在同一区间范围内插入数据,而插入意向锁允许在同一区间范围内插入数据

行锁兼容矩阵

image_1dv64eiji1db2eu814em116jcpn9.png-13.5kB

第一行表示已有的锁,第一列表示要加的锁

  • 插入意向锁不影响其他事务加其他任何锁。也就是说,一个事务已经获取了插入意向锁,对其他事务是没有任何影响的。
  • 插入意向锁与间隙锁和 Next-key 锁冲突。也就是说,一个事务想要获取插入意向锁,如果有其他事务已经加了间隙锁或 Next-key 锁,则会阻塞。
  • 间隙锁不和其他锁(不包括插入意向锁)冲突。
  • 记录锁和记录锁冲突,Next-key 锁和 Next-key 锁冲突,记录锁和 Next-key 锁冲突。

加锁规则

  • 原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。
  • 原则 2:查找过程中访问到的对象才会加锁。
  • 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
  • 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
  • 一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

加锁例子

(转)解决死锁之路 - 常见 SQL 语句的加锁分析

其他

两阶段锁协议:在InnoDB事务中,行锁是在需要的时候才加上的,但是并不是不需要了就立刻释放,而是要等到事务结束时才释放。

如果事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。

参考

解决死锁之路 - 了解常见的锁类型
MySQL实战45讲:用动态的观点看加锁

CATALOG
  1. 1. 读写锁
  2. 2. 表锁
    1. 2.1. 读写锁
    2. 2.2. 读写意向锁
    3. 2.3. 自增锁
    4. 2.4. 表锁兼容矩阵
  3. 3. 行锁
    1. 3.1. 四种类型的锁
      1. 3.1.1. 记录锁
      2. 3.1.2. 间隙锁
      3. 3.1.3. Next-Key Locks
      4. 3.1.4. 插入意向锁
    2. 3.2. 行锁兼容矩阵
    3. 3.3. 加锁规则
    4. 3.4. 加锁例子
    5. 3.5. 其他
  4. 4. 参考: