MySQL 常用存储引擎的锁机制

  • MyISAM 和 MEMORY 采用表锁;

  • BDB 采用页面锁或表锁,默认为页锁;

  • InnoDB 支持行锁和表锁,默认为行锁;

InnoDB 中的行锁和表锁

InnoDB 行锁是通过给索引上的索引项加锁来实现的,这一点 MySQL 与 Oracle 不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB 这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB 才使用行锁,否则,InnoDB 将使用表锁。

InnoDB 中的行锁有三种方式:

  1. Record Lock:记录锁,针对单个行记录添加锁。

  2. Gap Lock:间隙锁,可以帮我们锁住一个范围(索引之间的空隙),但不包括记录本身。采用间隙锁的方式可以防止幻读情况的产生。

    1
    2
    /* 其他事务不能在 t.c 中插入 15 */
    SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
  3. 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 一般都可以检测到死锁的出现,并使一个事务释放锁回退,另一个获取锁完成事务。

参考

  1. MySQL中的行级锁,表级锁,页级锁
  2. Innodb 锁机制:Next-Key Lock 浅谈