如何在MySQL8.0中优雅处理热点账户高并发转账死锁

秋涛吖_5507

秋涛吖_5507

2026-09-22

832人浏览

原创

mysql 8.0热点账户转账死锁无法完全避免,但可通过按主键升序加锁(如order by id for update)、降级为read committed隔离级别、幂等重试等手段实现可预测回滚与资金一致性保障。

如何在mysql8.0中优雅处理热点账户高并发转账死锁

MySQL 8.0 中热点账户转账死锁无法“避免”,但可以精准控制谁被回滚、让失败可预测、不波及资金一致性——关键不是加锁更狠,而是让所有事务按同一物理顺序排队。

为什么SELECT ... FOR UPDATE在热点账户上反而加剧死锁

很多人第一反应是:给账户加行锁不就完了?于是写:

BEGIN;
SELECT balance FROM accounts WHERE user_id = 'A' FOR UPDATE;
-- 然后计算、更新...
UPDATE accounts SET balance = ? WHERE user_id = 'A';
COMMIT;

问题在于:user_id 若是非唯一索引或无索引,InnoDB 会走全表扫描,对**所有扫描到的行+间隙**加 X 锁;若 user_id = 'A' 匹配多条(比如历史分库分表残留),锁范围直接爆炸。更危险的是:两个事务同时执行该语句,但 MySQL 内部加锁顺序按聚簇索引(通常是 PRIMARY KEY)物理位置来,而你根本无法控制哪一行先被锁。

  • EXPLAIN 确认 user_id 是否走了唯一索引;没走 → 必须建唯一索引或改用主键查询
  • 哪怕走了索引,若事务 A 查 user_id = 'A',事务 B 查 user_id = 'B',但底层锁住的聚簇索引记录物理顺序不同(比如 A 在页 10,B 在页 5),仍可能因交叉加锁触发死锁
  • 真正安全的 FOR UPDATE 只有一种:明确按主键升序锁定,例如 SELECT id FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE

统一加锁顺序:用主键排序强制物理一致

转账必然涉及两个账户,死锁根源几乎全是“A→B”和“B→A”顺序冲突。解决方案不是靠运气,是让所有事务**强制按主键数值升序加锁**:

-- 应用层计算:假设转账从 account_id=1001 到 account_id=2005
-- 因为 1001 <p>这样无论哪个事务发起,只要双方都遵守该规则,加锁顺序永远是 1001 → 2005,不可能形成环。实操中需注意:</p><div class="aritcle_card flexRow artxards">
											<div class="artcardd flexRow">
												<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill4282" title="钓鱼热点推送"><img
														src="https://img.php.cn/upload/skill/000/000/081/178998305240105.jpg" alt="钓鱼热点推送" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
												<div class="aritcle_card_info flexColumn">
													<a rel="nofollow" href="/xiazai/skill4282" title="钓鱼热点推送" class="overflowclass">钓鱼热点推送</a>
													<p class="overflowclass">自动聚合钓鱼社区、搜索引擎和天气API数据,结合用户位置与和风天气钓鱼指数,推送周边最佳钓点及实时鱼情;支持配置管理、NLP信息提取、HTML可视化报告、历史记录。触发词:钓鱼热点、今日鱼情、附近钓点、哪里出鱼、钓鱼推送、钓鱼情报、fishing hotspot。</p>
												</div>
												<a rel="nofollow" href="/xiazai/skill4282" title="钓鱼热点推送" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
												</a>
											</div>
										</div>
  • 必须用 ORDER BY id,不能用 ORDER BY user_id —— id 是聚簇索引,排序即物理顺序
  • IN 子句最多 500 个 ID;超量需分批,且每批内部仍要 ORDER BY id
  • ORM 如 MyBatis 动态拼 SQL 时,<if></if> 分支可能导致 IN 列表顺序混乱,建议在业务代码里先 sort() 再传入
  • 不要依赖数据库自动优化器重排 —— InnoDB 的锁获取严格按 SQL 执行时扫描的物理顺序

隔离级别与间隙锁:RR 下的隐式陷阱

MySQL 8.0 默认 REPEATABLE READ,对 WHERE id = ? 这种等值查询只加 record lock(记录锁),看似安全。但一旦出现以下任一情况,间隙锁(gap lock)立即激活,死锁概率飙升:

  • 误写成 WHERE id > 100 AND id (范围查询 → 加 gap lock)
  • 表上有非唯一索引,且查询走了该索引(如 INDEX(user_id)),InnoDB 会对索引区间加 next-key lock
  • 执行 INSERT ... ON DUPLICATE KEY UPDATE,即使主键存在,也会先尝试插入,触发 gap lock

对策很直接:

  • 热点转账场景,**显式降级为 READ COMMITTED**:在事务开头执行 SET TRANSACTION ISOLATION LEVEL READ COMMITTED;,InnoDB 会禁用 gap lock,只保留 record lock 和 insert intention lock,锁粒度最小
  • 确认所有转账 SQL 都是主键等值查询,杜绝任何 LIKEBETWEEN、函数包裹字段(如 WHERE ABS(id) = 1001
  • 检查 SHOW CREATE TABLE accounts,确保没有多余非唯一索引干扰执行计划

应用层兜底:幂等 + 重试 + 监控

即便锁策略完美,InnoDB 仍可能因极端并发选中你的事务做牺牲者(回滚并报 Deadlock found when trying to get lock)。此时重点不是防止它发生,而是让它不造成业务损失:

  • 所有转账接口必须带幂等键(如 transfer_id),数据库唯一约束,重复请求直接返回成功
  • 重试逻辑必须有退避(如指数退避),且**重试前重新 SELECT 当前余额校验**,防止原事务其实已成功提交
  • 监控告警不只看死锁次数,要聚合分析 SHOW ENGINE INNODB STATUS\G 中被回滚事务的 mysql thread id,关联应用 trace,确认是不是总在某个特定入口(如优惠券核销+转账合并)高频触发
  • 禁止在事务内调用外部 HTTP 或 RPC —— 长事务是死锁放大器,网络延迟会让锁持有时间不可控

最易被忽略的一点:**死锁日志里的 WAITING FOR THIS LOCK TO BE GRANTED 行,往往暴露了未走索引的隐藏 SQL**。别只盯着报错的那条 UPDATE,顺着它等的锁,往上翻看另一个事务的 HOLDS THE LOCK(S),常能挖出某条被遗忘的、没加索引的统计查询正在悄悄锁住整张表。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
mysql修改数据表名
mysql修改数据表名

MySQL修改数据表:1、首先查看数据库中所有的表,代码为:‘SHOW TABLES;’;2、修改表名,代码为:‘ALTER TABLE 旧表名 RENAME [TO] 新表名;’。php中文网还提供MySQL的相关下载、相关课程等内容,供大家免费下载使用。

2023.06.20

1913

6

MySQL创建存储过程
MySQL创建存储过程

存储程序可以分为存储过程和函数,MySQL中创建存储过程和函数使用的语句分别为CREATE PROCEDURE和CREATE FUNCTION。使用CALL语句调用存储过程智能用输出变量返回值。函数可以从语句外调用(通过引用函数名),也能返回标量值。存储过程也可以调用其他存储过程。php中文网还提供MySQL创建存储过程的相关下载、相关课程等内容,供大家免费下载使用。

2023.06.21

1179

5

mongodb和mysql的区别
mongodb和mysql的区别

mongodb和mysql的区别:1、数据模型;2、查询语言;3、扩展性和性能;4、可靠性。本专题为大家提供mongodb和mysql的区别的相关的文章、下载、课程内容,供大家免费下载体验。

2023.07.18

695

5

mysql密码忘了怎么查看
mysql密码忘了怎么查看

MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS 应用软件之一。那么mysql密码忘了怎么办呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.19

2492

5

mysql创建数据库
mysql创建数据库

MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS 应用软件之一。那么mysql怎么创建数据库呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.25

4068

4

mysql默认事务隔离级别
mysql默认事务隔离级别

MySQL是一种广泛使用的关系型数据库管理系统,它支持事务处理。事务是一组数据库操作,它们作为一个逻辑单元被一起执行。为了保证事务的一致性和隔离性,MySQL提供了不同的事务隔离级别。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

2023.08.08

979

3

sqlserver和mysql区别
sqlserver和mysql区别

SQL Server和MySQL是两种广泛使用的关系型数据库管理系统。它们具有相似的功能和用途,但在某些方面存在一些显著的区别。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.11

4331

4

mysql忘记密码
mysql忘记密码

MySQL是一种关系型数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。那么忘记mysql密码我们该怎么解决呢?php中文网给大家带来了相关的教程以及其他关于mysql的文章,欢迎大家前来学习阅读。

2023.08.14

3942

7

mysql事务隔离级别
mysql事务隔离级别

mysql规范中定义了四种事务隔离级别,不同的隔离级别对事务的处理有所不同。本专题为大家提供mysql事务隔离级别相关的文章内容,大家可以免费体验。

2023.08.16

5054

11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL vs MySQL
PostgreSQL vs MySQL

共1课时 | 169人学习

使用phpenv集成环境安装极致CMS
使用phpenv集成环境安装极致CMS

共2课时 | 273人学习