MySQL 常用存储引擎的锁机制
MyISAM 和 MEMORY 采用表锁;
BDB 采用页面锁或表锁,默认为页锁;
InnoDB 支持行锁和表锁,默认为行锁;
InnoDB 中的行锁和表锁
InnoDB 行锁是通过给索引上的索引项加锁来实现的,这一点 MySQL 与 Oracle 不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB 这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB 才使用行锁,否则,InnoDB 将使用表锁。
InnoDB 中的行锁有三种方式:
Record Lock:记录锁,针对单个行记录添加锁。
Gap Lock:间隙锁,可以帮我们锁住一个范围(索引之间的空隙),但不包括记录本身。采用间隙锁的方式可以防止幻读情况的产生。
1
2/* 其他事务不能在 t.c 中插入 15 */
SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;Next-Key Lock:帮我们锁住一个范围(左必右开),同时锁定记录本身,相当于间隙锁 + 记录锁。该锁主要用来解决幻读的问题。例如范围中包含 10, 11, 13, 20,那么就需要锁定一些区间:
1
2
3
4
5(-∞, 10]
(10, 11]
(11, 13]
(13, 20]
(20, +∞)
InnoDB 行锁和表锁的使用主要包含一下几种情况:
- 在不通过索引条件查询时,InnoDB 使用的是表锁;
- 行锁是对索引加锁。所以虽然是访问不同的行记录,但是如果使用相同的索引键,这时会出现锁冲突;
- MySQL 会通过判断不同执行计划的代价决定是否使用索引,如果全表扫描效率更高(例如比较小的表),就不会使用索引,这是讲使用表锁;
行锁和死锁
MylSAM 总是一次性获得所需的全部锁,要么全部满足,要么全部等待,所以不存在发生死锁。
InnoDB 和 MylSAM 不同,它遵循两段式协议,锁是逐步获取的,所以有可能出现死锁的情况。前文提到过,InnoDB 的行锁是直接锁住索引,同时索引又分为主键索引和非主键索引两种。如果一条 sql 语句操作了主键索引,MySQL 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL 会先锁定该非主键索引,再锁定相关的主键索引。 当两个事务同时进行时,一个锁锁住了主键索引,在等待其他相关索引;另一个锁锁定了非主键索引,在等待主键索引,这时就会发生死锁。InnoDB 一般都可以检测到死锁的出现,并使一个事务释放锁回退,另一个获取锁完成事务。