InnoDB 锁的兼容性:
- IS 锁与 S 锁兼容
- IX, IS 锁与 X 锁不兼容
- IX 锁与 S 锁不兼容
要想查看当前数据库中的锁请求, 可以在三张表上执行 SELECT * :
information_schema.INNODB_TRX
,information_schema.INNODB_LOCKS
,information_schema.INNODB_WAITS
二. innoDB 锁应用
一致性非锁定读 (consist nonblocking read)
一致性非锁定读,指通过行多版本控制(MVCC)方式读取数据库中的行数据。如果读取的行正在执行DELETE
或UPDATE
,该读操作也不会阻塞等待锁释放,而是直接读取行的快照数据。快照是指当前行之前版本的数据,通过 undo 段
实现。因为 undo 段用来事务回滚数据,所以快照本身并不会带来额外开销
。
在事务隔离级别 READ COMMITTED
和 REPEATABLE READ
下,InnoDB 使用一致性非锁定读。
- 前者总是读取行的最新一份快照数据, 这份快照数据可能是其他事务刚刚提交的,所以叫
读已提交
- 后者总是读取事务开始时的行数据版本,因此在一个事务内多次读取的数据相同,所以叫
可重复读
一致性锁定读
某些情况下,用户需要显式的对数据库读操作的行进行加锁来保证逻辑上的一致性,就要执行 SELECT ... FOR UPDATE
或 SELECT ... LOCK IN SHARE MODE
。对于一致性非锁定读,即使行上由其它事务进行锁定读,也可以正常进行读取。
自增列与锁
主键经常采用自增长方式,每个含有自增长值的表都有一个自增长计数器
,当对含有自增长计数器的表插入时,会进行 AUTO-INC Locking
。是一种表级别锁
。只是这个锁不是在事务提交后才释放,而是完成对自增长值的插入操作后立刻释放。所以,对于像INSERT ... SELECT
的大数据量插入会影响插入性能,因为另一个事务中的插入会阻塞。但从 Mysql 5.1.22 后,提供了参数 innodb_autoinc_lock_mode
来控制自增长模式。
三. InnoDB 锁算法
InnoDB 有三种行锁算法
Record Lock
: 单个行记录上的锁, 锁主键Gap Lock
: 间隙锁,锁定一个范围,但不锁行本身Next-Key Lock
:Record Lock + Gap Lock
锁定一个范围,且锁定行本身(左闭右开区间)
比如一个表里有4个索引(10,11,13,20),则可能被 Next-Key Locking 的索引区间是(负无穷,10]
,(10,11]
,(11,13]
,(13,20]
,(20,正无穷]
。因为每个索引上的 Next-Key Lock 都是之前索引的区间
+索引本身
InnoDB 对于行的锁定都是采用 Next-Key Lock
,这种锁锁定的不仅是单个行,而且包括一个范围,它的设计目的是为了解决幻读问题。但是当查询的索引是唯一索引时,InnoDB 会把 Next-Key Lock 降级为 Record Lock,即只锁索引本身,而不锁区间范围。比如:
- 主键也是唯一索引, 锁降级
-- 创建一个表, 主键a, 有1,2,5 三个值
Create Table t (a INT Primary Key);
insert into t SELECT 1;
insert into t SELECT 2;
insert into t SELECT 5;
-- 事务1
select \* from t where a = 5 for update; -- 锁定主键5
-- 事务2
insert into t select 4; -- 可以成功,因为主键是唯一索引,降级为 record lock, 只索索引
- 辅助索引除了在索引上加 next-key lock,还会在与下一个索引的区间上加 gap lock。导致下列操作失败
create table t (a INT primary key, b int key);
insert into t SELECT 1,1;
insert into t SELECT 3,1;
insert into t SELECT 5,3;
insert into t SELECT 7,6;
-- 事务1
-- b 是辅助索引,在副主索引上的锁区间为 (1,3] 和 (3,6) , Next-Key Lock + gap lock
-- b = 3 对应 a = 5, a 是主键, 索引加的是 record lock,只锁 a= 5 这个值
select \* from t where b = 3 for update;
-- 事务2
select \* from t where a = 5 lock in share mode; -- 不能执行,因为行锁 a = 5 存在
insert into t select 4,2; -- 不能执行,a = 4 可以,但 b = 2 在 (1,3] Next-Key Lock 内
insert into t select 6,5; -- 不能执行, a = 6 可以,但 b = 5 在 (3,6) gap lock 内
四. Phantom Problem
默认的事务隔离级别 REPEATABLE READ
下, InnoDB 采用 Next-Key Lock 避免产生幻读。所谓幻读,是指同一事务下,连续两次 select 返回的数据不同,第二次 select 可能返回之前不存在的行。 比如对于上面的表 t, 由 1,2,5 三个值组成 :
- 事务1执行
select * from t where a>2 for update;
只返回5一个值。 - 此时事务2执行
insert into t select 4
如果执行成功的话, 事务1再次执行select ... a>2
会返回4,5两个值,造成幻读。
因此 innoDB 采用 Next-Key Lock,在 a>2
的条件上的锁范围是 (2,正无穷) , 这样事务2的插入不会成功。但是在 READ COMMITED 级别下,对于 a>2
扫描到的索引 2 和 5 加的是 record lock, 只锁行本身,这会导致事务2插入成功,事务1产生幻读。
五. 锁带来的问题
1. 脏读
脏数据是指事务未提交的数据,读到脏数据,指一个事务读到了另一个事务未提交的数据,违反了数据库的隔离性。发生脏读的条件是事务隔离级别设置成了 READ UNCOMMITED
,这种情况不常发生, 因为现在的数据库至少设置成 READ COMMITED
2. 不可重复读
同一个事务内。两次读到的数据不一致叫做不可重复读。脏读是读到了其他事务未提交的数据,不可重复读是读到了其他事务已提交的数据,这都违反了数据库的事务一致性要求。READ COMMITED
隔离级别会导致不可重复读。mysql 又把不可重复读称作幻读,如上文所说,innoDB 采用 next-key lock 避免幻读
3. 丢失更新
丢失更新,指一个事务的更新操作被另一个事务所覆盖,从而导致数据不一致。比如业务层的转账操作,2个事务分别先后转走5000元和2000元,但转账之前均要判断余额是否大于 5000 和 2000 。由于检查 select 和转走 update 操作是分别进行的,所以要给 select 也加上锁, 变为 select ... for update
检查, 让两个事务的检查和更新操作串行化,避免更新丢失。
六. 死锁
死锁是指两个事务都持有对方等待获取的锁而进入的死锁状态。innoDB 一旦发现死锁,就会让当前事务失败,进行 undo,并推出事务,让另一个事务可以获取锁