前言
最近在阅读《MySQL技术内幕 InnoDB存储引擎》一文,发现文中对InnoDB中锁的描述和网上大多数博客相差甚远,因此查阅了大量资料,结合实际测试后,写下了这篇文章。
如有不同看法,欢迎在评论区留言讨论。
一、共享锁与排他锁
在介绍MySQL中的行锁之前,首先要了解什么是共享锁与排他锁。
排他锁(X Lock)
排他锁,也称写锁,允许获取锁的事务更新或删除数据。
当前事务获取到排他锁后,禁止其他事务获取该排他锁,禁止其他事务获取该数据的共享锁。
获取排他锁时,需要等待其他事务释放共享锁或排他锁。
共享锁(S Lock)
共享锁,也称读锁,允许获取锁的事务读取数据。
当前事务获得共享锁时,其他事务也可以获取该共享锁,但禁止其他事务获取该数据的排他锁。
获取共享锁时,需要等待其他事务释放排他锁。
二、获取行锁的方式
InnoDB
中,行锁支持共享锁和排他锁。
如果需要手动加锁或查询时加锁,使用如下代码:
-- 获取行级别的共享锁
-- 在结尾加上 lock in share mode 表示给该行加共享锁
select ... lock in share mode;-- 获取行级别的排他锁
-- 在结尾加上 for update 表示给该行加排他锁
select ... for update;
此外,对数据做新增、删除或修改时,自动会加上排他锁。
三、行锁的算法
InnoDB
中行锁有3种算法,值得注意的是,无论使用哪种锁算法,都支持共享锁和排他锁,获取锁的方式也都采用2.1
中介绍的方式。
记录锁(Record Lock):单个行记录上的锁。
间隙锁(Gap Lock):锁定一个范围,但不包含记录本身。
临键锁(Next-Key Lock):Gap Lock+Record Lock,锁定一个范围,并且锁定记录本身。
在不同的隔离级别下,MySQL
使用的锁算法不一定相同,下面主要说明可重复读隔离级别下使用的锁算法。
3.1 隔离级别为可重复读(REPEATABLE READ)
MySQL
的默认数据隔离级别为可重复读(REPEATABLE READ),在该隔离级别下,锁算法默认使用临键锁算法(Next-Key Lock)。
3.1.1 对唯一索引加锁
where
条件中存在唯一索引时,临键锁会退化成记录锁,只对满足条件的记录加锁,包括满足条件但不存在的记录。
3.1.2 对非唯一索引加锁
where
条件中不存在唯一索引时,对满足条件的记录加锁,也对直到下一个不满足条件的记录的范围区间加锁,不包含下一个不满足条件的记录。
3.2 隔离级别为读可提交(READ COMMITTED)
在隔离级别设置为读可提交(READ COMMITTED)时,所算法使用记录锁算法(Record Lock)。
四、实际案例
在 可重复读(REPEATABLE READ) 隔离条件下,创建一个表z,其中有唯一索引a,和非唯一索引b。