MySQL通过MVCC和锁机制实现事务的隔离性。对于MVCC之前的文章MySQL MVCC详解介绍过了,今天来讲讲MySQL的锁。
锁的分类
根据加锁思想可以分为悲观锁、乐观锁;
根据兼容性可以分为共享锁、排它锁;
按照粒度可以分为行锁、页锁、表锁。InnoDB支持行锁和表锁。
悲观锁和乐观锁
悲观锁
悲观锁基于一种悲观的态度来防止一切数据冲突,当要对数据库中的一条数据进行修改时会先对该数据进行加锁,避免被其它事务修改。MySQL中的行锁、表锁、读锁、写锁等等都是悲观锁。
乐观锁
乐观锁是对于数据冲突保持一种乐观态度,每次去拿数据的时候都认为不会被其它事务修改,所以不会上锁,但是在提交修改的时候会判断一下在此期间该数据有没有被修改过,一般使用版本号机制或CAS算法来实现。
应用场景
乐观锁适用于多读少写的场景,因为省去了加锁的开销,能够提高并发的性能。
多写的场景下用悲观锁比较合适,多写时会经常产生冲突,如果使用乐观锁会导致应用不断的进行重试,反而降低了性能。
共享锁和排它锁
共享锁
共享锁也叫读锁或S锁,当前事务获得S锁后,不会阻塞其它事务获得S锁,但会阻塞其它事务获得X锁。
排它锁
排它锁也叫独占锁、写锁或X锁,当前事务获得X锁后,会阻塞其它事务获得S锁和X锁。
隐式锁定和显式锁定
锁定类型和对应的SQL语句
- 显式读锁:select…lock in share mode,lock table…read
- 显式写锁:select…for update,lock table…write
- 隐式写锁:insert/update/delete
行锁和表锁
行锁
InnoDB引擎中有三种行锁的算法,如下:
Record Lock
Record Lock:记录锁,锁定的是单个索引记录。
Gap Lock
Gap Lock:间隙锁,锁定的是索引记录之间的间隙。给索引记录加Gap Lock时,会锁定该记录前面的间隙。
Next-key Lock
Next-key Lock:临键锁,Record Lock + Gap Lock 的组合,既锁定索引记录,又锁定前面的间隙。
使用场景
在RR事务隔离级别下,进行等值查询时,分为以下这些情况:
- 有匹配的索引记录
- 主键索引
i. 给唯一匹配的索引记录加上Record Lock。 - 唯一索引
i. 给唯一匹配的索引记录加上Record Lock;
ii. 给对应的主键索引记录加上Record Lock。 - 普通索引
i. 给所有匹配的索引记录都加上Next-key Lock,给下一个不匹配索引记录加上Gap Lock(给前后间隙都加上锁了,这样就可以防止插入相同索引值的记录,从而防止幻读 );
ii. 给对应的主键索引记录都加上Record Lock。 - 没走索引
i. 给所有主键索引记录和间隙都加锁。
- 主键索引
- 无匹配索引
- 聚集索引、唯一索引、普通索引
i. 给大于匹配值的第一个索引记录加上Gap Lock。 - 没走索引
ii. 给所有主键索引记录和间隙都加锁。
- 聚集索引、唯一索引、普通索引
表锁
表锁的加锁命令是lock tables … read/write,释放锁命令是unlock tables,在会话退出时也会自动释放表锁。因为表锁的粒度太大,会影响并发性能,所以应该尽量避免在 InnoDB 引擎中使用表锁。
MySQL中的其它表级锁还有意向锁,自增锁,元数据锁。
意向锁
当一个事务想要获得一个表锁时,需要先确认该表的所有记录都没有被其它事务加锁,如果一行行检测的话效率很低,这时就出现了意向锁。在给获得行上的锁之前必须先获得该表的意向锁,这样的话要获得表锁时只需要检测对应的意向锁就可以了。
意向锁分为意向共享锁、意向排它锁:
- 意向共享锁也叫IS锁,一个事务要获取行上的共享锁之前,必须先获取该表的IS锁或IX锁。
- 意向排它锁也叫IX锁,一个事务要获取行上的排它锁之前,必须先获取该表的IX锁。
意向锁的兼容性矩阵如下:
X | IX | S | IS |
---|---|---|---|
X | Conflict | Conflict | Conflict |
IX | Conflict | Compatible | Conflict |
S | Conflict | Conflict | Compatible |
IS | Conflict | Compatible | Compatible |
元数据锁
元数据锁就是MDL(metadata lock),元数据锁主要解决DDL和DML同时执行的冲突问题。执行DML操作时会先在对应表上加一个metadata读锁,执行DDL操作时会先在对应表上加一个metadata写锁,这样DDL和DML就无法同时执行了。
自增锁
自增锁(AUTO-INC LOCK)的三种模式如下:
- 传统模式:如果一个事务向包含AUTO_INCREMENT列的表中插入数据会先去获得自增锁,语句执行完之后会释放,从而保证自增值是连续的。
- 连续模式(MySQL 8.0 之前的默认模式):如果 INSERT 语句可以提前确定插入的数据量,则不必获取自增锁,只是使用了mutex来防止ID重复分配,例如 INSERT INTO… 这种语句;如果 INSERT 语句不能提前确认插入的数据量,则还是会去获取自增锁,例如 INSERT INTO … SELECT … 这种语句。
- 交叉模式(MySQL 8.0 的默认模式):所有的 INSERT 语句都不会使用自增锁,而是使用较为轻量的 mutex 锁。
参考
MySQL :: MySQL 5.7 Reference Manual :: 14.7.1 InnoDB Locking
MySQL :: MySQL 5.7 Reference Manual :: 14.6.1.6 AUTO_INCREMENT Handling in InnoDB