读写锁
从锁的粒度分,mysql的锁分为表锁和行锁,而无论表锁还是行锁,都有读写锁之分,读锁又称共享锁,写锁又称排它锁。
一般来说,读锁与读锁兼容,与写锁冲突;而写锁与写锁、读锁冲突。
表锁
表锁是由Mysql服务器实现的。
表锁的类型有以下三种:
- 读写锁:LOCK_S(读锁),LOCK_X(写锁)
- 读写意向锁: LOCK_IS(读意向锁),LOCK_IX(写意向锁)
- 自增锁:LOCK_AUTO_INC
读写锁
读写锁使用一次封锁技术,某个会话使用lock tables命令对要用到的表加锁以后,在释放这些锁之前,该会话只能访问那些加锁的表,不能访问其他表。
1 | LOCK 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
控制分配自增值时的并发策略
表锁兼容矩阵
下面是各个表锁之间的兼容矩阵:
这个矩阵看上去有点眼花缭乱,其实很简单,因为是斜对称的,所以我们用一条斜线把表格分割成两个部分,只需要看左下角的一半即可。总结起来有下面几点:
- 意向锁之间互不冲突;
- 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)。
插入意向锁的作用是为了提高并发插入的性能,因为普通的间隙锁不允许在同一区间范围内插入数据,而插入意向锁允许在同一区间范围内插入数据。
行锁兼容矩阵
第一行表示已有的锁,第一列表示要加的锁
- 插入意向锁不影响其他事务加其他任何锁。也就是说,一个事务已经获取了插入意向锁,对其他事务是没有任何影响的。
- 插入意向锁与间隙锁和 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事务中,行锁是在需要的时候才加上的,但是并不是不需要了就立刻释放,而是要等到事务结束时才释放。
如果事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。