zhenlanghuo's Blog

Mysql学习笔记——锁

字数统计: 2.1k阅读时长: 7 min
2020/01/22 Share

读写锁

从锁的粒度分,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. 读写锁
  2. 表锁
    1. 读写锁
    2. 读写意向锁
    3. 自增锁
    4. 表锁兼容矩阵
  3. 行锁
    1. 四种类型的锁
      1. 记录锁
      2. 间隙锁
      3. Next-Key Locks
      4. 插入意向锁
    2. 行锁兼容矩阵
    3. 加锁规则
    4. 加锁例子
    5. 其他
  4. 参考: