编辑
2026-01-23
MySQL
00
请注意,本文编写于 49 天前,最后修改于 49 天前,其中某些信息可能已经过时。

目录

两阶段锁
死锁和死锁检测

07 | 行锁功过:怎么减少行锁对性能的影响?

MySQL 的行锁是在引擎层由各个引擎自己实现的。但并不是所有引擎都支持行锁,MyISAM 就不支持行锁,MyISAM 做并发控制的时候只能用表锁实现,这意味着同一张表在某个时刻只能有一个更新在执行。

两阶段锁

image.png

在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。

所以,如果一个事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。

比如有这样一个例子:

一个电影票在线交易业务,顾客 A 要在影院 B 购买电影票。这个业务需要涉及到以下操作:

  1. 从顾客 A 账户余额中扣除电影票价;
  2. 给影院 B 的账户余额增加这张电影票价;
  3. 记录一条交易日志。

要完成这个交易,我们需要 update 两条记录,并 insert 一条记录。当然,为了保证交易的原子性,我们要把这三个操作放在一个事务中。问题是要怎样安排这三个语句在事务中的顺序呢?

如果同时有另外一个顾客 C 要在影院 B 买票,那么这两个事务冲突的部分就是语句 2 了

所以,如果你把语句 2 安排在最后,比如按照 3、1、2 这样的顺序,这样执行完 2 就 commit 提交事务,然后就释放行锁了。那么影院账户余额这一行的锁时间就最少。

死锁和死锁检测

上图是死锁的例子。事务 A 在等待事务 B 释放 id=2 的行锁,而事务 B 在等待事务 A 释放 id=1 的行锁。 事务 A 和事务 B 在互相等待对方的资源释放,就是进入了死锁状态。

对死锁有两种处理策略:

  • 一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置,默认值是 50s
  • 另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。默认值是 on

如果有很多线程(比如 1000)需要同时更新同一行数据,这时候死锁检测会消耗大量 CPU 资源。解决的思路是控制并发度,比如同一行同时最多只有 10 个线程在更新,那么死锁检测的成本很低。

本文作者:菜宝熊

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!