MySQL锁机制与死锁排查实战

“数据库死锁了”——这句话我在线上听过无数次。

锁是MySQL保证数据一致性的核心机制,但用不好就是各种问题。这篇从原理到排查,把锁这块讲透。


MySQL锁的分类

按粒度分

锁类型说明开销并发度引擎
表锁锁整张表MyISAM/InnoDB
行锁锁单行InnoDB
间隙锁锁区间InnoDB

按模式分

锁类型说明兼容性
共享锁(S锁)读锁,多个事务可以同时持有与S锁兼容,与X锁互斥
排他锁(X锁)写锁,只能有一个事务持有与S锁、X锁都互斥

InnoDB行锁详解

行锁的三种形式

1. 记录锁(Record Lock)

锁住索引记录本身:

-- 锁住id=1这一行 SELECT * FROM users WHERE id = 1 FOR UPDATE;

2. 间隙锁(Gap Lock)

锁住索引记录之间的间隙,防止幻读:

-- 假设表里有id: 1, 5, 10 -- 这条SQL会锁住(1,5)这个间隙 SELECT * FROM users WHERE id > 1 AND id < 5 FOR UPDATE;

3. 临键锁(Next-Key Lock)

记录锁 + 间隙锁,默认锁类型:

-- 锁住id=5这条记录,以及(1,5]这个范围 SELECT * FROM users WHERE id = 5 FOR UPDATE;

加锁规则

这块有点绕,记几个关键点:

  1. 唯一索引等值查询:只加记录锁(如果记录存在)
  2. 唯一索引范围查询:加临键锁
  3. 普通索引查询:加临键锁
  4. 无索引查询:锁全表!
-- 假设id是主键,name有普通索引 -- 情况1:唯一索引等值,只锁一行 SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 情况2:普通索引等值,锁记录+间隙 SELECT * FROM users WHERE name = '张三' FOR UPDATE; -- 情况3:无索引,锁全表! SELECT * FROM users WHERE age = 25 FOR UPDATE; -- age没索引

这就是为什么要建索引的原因之一:没索引会锁表!


死锁是怎么产生的

经典场景:两个事务互相等待对方释放锁。

事务A 事务B -------------------------------------------------- BEGIN; BEGIN; UPDATE t SET x=1 WHERE id=1; -- 锁住id=1 UPDATE t SET x=2 WHERE id=2; -- 锁住id=2 UPDATE t SET x=1 WHERE id=2; -- 等待id=2的锁 UPDATE t SET x=2 WHERE id=1; -- 等待id=1的锁 -- 死锁!

MySQL会检测到死锁,选择一个事务回滚。


死锁排查实战

1. 查看死锁日志

SHOW ENGINE INNODB STATUS\G

找到LATEST DETECTED DEADLOCK部分:

------------------------ LATEST DETECTED DEADLOCK ------------------------ 2024-12-29 10:30:45 0x7f8a12345678 *** (1) TRANSACTION: TRANSACTION 12345, ACTIVE 5 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 100, OS thread handle 123456, query id 999 updating UPDATE orders SET status = 'paid' WHERE id = 100 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 50 page no 3 n bits 72 index PRIMARY of table `test`.`orders` trx id 12345 lock_mode X locks rec but not gap waiting *** (2) TRANSACTION: TRANSACTION 12346, ACTIVE 3 sec starting index read mysql tables in use 1, locked 1 3 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 101, OS thread handle 123457, query id 1000 updating UPDATE orders SET status = 'shipped' WHERE id = 101 *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 50 page no 3 n bits 72 index PRIMARY of table `test`.`orders` trx id 12346 lock_mode X locks rec but not gap *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 50 page no 4 n bits 72 index PRIMARY of table `test`.`orders` trx id 12346 lock_mode X locks rec but not gap waiting *** WE ROLL BACK TRANSACTION (1)

2. 查看当前锁情况

-- MySQL 8.0+ SELECT * FROM performance_schema.data_locks; SELECT * FROM performance_schema.data_lock_waits; -- MySQL 5.7 SELECT * FROM information_schema.innodb_locks; SELECT * FROM information_schema.innodb_lock_waits;

3. 查看正在等待的事务

SELECT r.trx_id AS waiting_trx_id, r.trx_mysql_thread_id AS waiting_thread, r.trx_query AS waiting_query, b.trx_id AS blocking_trx_id, b.trx_mysql_thread_id AS blocking_thread, b.trx_query AS blocking_query FROM information_schema.innodb_lock_waits w JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;

4. 查看长事务

SELECT trx_id, trx_state, trx_started, NOW() - trx_started AS duration, trx_mysql_thread_id, trx_query FROM information_schema.innodb_trx WHERE trx_state = 'RUNNING' AND NOW() - trx_started > 10 -- 超过10秒 ORDER BY trx_started;

常见死锁场景及解决

场景1:相反顺序更新

-- 事务A UPDATE accounts SET balance = balance - 100 WHERE id = 1; UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- 事务B UPDATE accounts SET balance = balance - 100 WHERE id = 2; UPDATE accounts SET balance = balance + 100 WHERE id = 1;

解决:固定更新顺序,按id排序后更新

-- 统一按id从小到大更新 UPDATE accounts SET balance = balance - 100 WHERE id = 1; UPDATE accounts SET balance = balance + 100 WHERE id = 2;

场景2:间隙锁冲突

-- 假设表里有id: 1, 10, 20 -- 事务A SELECT * FROM t WHERE id = 5 FOR UPDATE; -- 锁住(1,10) INSERT INTO t VALUES (7); -- 要插入(1,10),等待 -- 事务B SELECT * FROM t WHERE id = 15 FOR UPDATE; -- 锁住(10,20) INSERT INTO t VALUES (8); -- 要插入(1,10),等待A释放

解决

  • 降低隔离级别到RC(没有间隙锁)
  • 减少锁持有时间
  • 用唯一索引避免间隙锁

场景3:唯一索引插入冲突

-- 表有唯一索引 (user_id, product_id) -- 事务A INSERT INTO cart (user_id, product_id) VALUES (1, 100); -- 加插入意向锁 -- 事务B INSERT INTO cart (user_id, product_id) VALUES (1, 100); -- 冲突,等待

解决:用INSERT ... ON DUPLICATE KEY UPDATE

INSERT INTO cart (user_id, product_id, quantity) VALUES (1, 100, 1) ON DUPLICATE KEY UPDATE quantity = quantity + 1;

避免死锁的最佳实践

1. 固定更新顺序

// 转账:固定按id从小到大锁 func Transfer(fromID, toID int, amount int) error { ids := []int{fromID, toID} sort.Ints(ids) tx := db.Begin() // 按顺序锁定 for _, id := range ids { tx.Exec("SELECT * FROM accounts WHERE id = ? FOR UPDATE", id) } // 执行转账 tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID) tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toID) return tx.Commit().Error }

2. 缩短事务时间

// 不好:事务里做了很多事 tx := db.Begin() result := callExternalAPI() // 调用外部接口,可能很慢 tx.Create(result) tx.Commit() // 好:尽快完成事务 result := callExternalAPI() // 事务外调用 tx := db.Begin() tx.Create(result) tx.Commit()

3. 合理使用索引

-- 没索引会锁全表 UPDATE users SET status = 'active' WHERE created_at < '2024-01-01'; -- 有索引只锁需要的行 CREATE INDEX idx_created_at ON users(created_at); UPDATE users SET status = 'active' WHERE created_at < '2024-01-01';

4. 降低隔离级别

-- RC级别没有间隙锁,死锁概率低 SET TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 或者全局设置 SET GLOBAL transaction_isolation = 'READ-COMMITTED';

5. 使用乐观锁

-- 用版本号避免锁 UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 1 AND version = 10; -- 检查影响行数,为0说明被别人改了,重试

6. 设置锁等待超时

-- 等待超时自动放弃(默认50秒) SET innodb_lock_wait_timeout = 10;

监控告警

死锁监控

-- 查看死锁次数 SHOW GLOBAL STATUS LIKE 'Innodb_deadlocks'; -- 定时检查,增量告警 SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_deadlocks';

长事务监控

-- 超过60秒的事务 SELECT * FROM information_schema.innodb_trx WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60;

锁等待监控

-- 锁等待超过5秒的 SELECT * FROM performance_schema.data_lock_waits WHERE TIMESTAMPDIFF(SECOND, REQUESTING_ENGINE_LOCK_ID, NOW()) > 5;

一个真实案例

线上报警:订单支付接口频繁超时。

排查过程

  1. 看慢查询日志,发现大量UPDATE卡住
  2. SHOW ENGINE INNODB STATUS,看到死锁
  3. 分析死锁日志:
事务1: UPDATE orders SET status='paid' WHERE order_no='A001' 事务2: UPDATE orders SET status='paid' WHERE order_no='A002'
  1. 发现order_no没有索引,导致锁全表
  2. 两个事务都锁了全表,互相等待

解决

ALTER TABLE orders ADD INDEX idx_order_no(order_no);

加完索引,行锁替代表锁,问题解决。


总结

MySQL锁的几个要点:

  1. InnoDB行锁是锁索引,无索引会锁表
  2. 死锁的本质是循环等待,打破循环就能避免
  3. 固定更新顺序是避免死锁最有效的方法
  4. 事务要短,锁持有时间越短越好
  5. RC级别比RR少了间隙锁,死锁概率更低

遇到死锁不要慌,SHOW ENGINE INNODB STATUS看日志,分析是哪两个SQL冲突,然后针对性优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1214659.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring 才是撑起Java半边天的秘密武器?如果Spring 撂挑子了,Java 会不会一年内就跌下神坛?

长期以来&#xff0c;Java 被企业级开发广泛使用&#xff0c;但常被吐槽“繁琐、冗长、启动慢”。在云原生、微服务成为趋势的今天&#xff0c;Go、Python 等语言以轻量、快速吸引了大量开发者。 然而现实是&#xff1a;Java 能在企业级开发中长期占据主导地位&#xff0c;全靠…

Docker 使用注意事项:从磁盘爆满到安全实践的完整避坑指南

在容器化技术广泛应用的今天&#xff0c;Docker 已成为开发者和运维人员的必备工具。然而&#xff0c;“容器虽轻&#xff0c;隐患不小”——不当使用极易导致磁盘爆满、安全漏洞、数据丢失等问题。本文结合真实生产案例&#xff0c;系统梳理 Docker 使用中的关键注意事项&…

有名靠谱的 GEO 优化公司推荐:数石网络科技引领行业新风向

在当今数字化营销的浪潮中,GEO 优化成为企业精准营销的核心策略。有名的 GEO 优化公司、实力强且靠谱的 GEO 优化品牌企业,正成为众多企业寻求精准获客的关键助力。那么,这些 GEO 优化公司究竟有哪些行业优势和特点…

说说无锡靠谱的高频红外碳硫分析仪定制厂家,哪家性价比高?

随着新能源材料、制造等领域对元素检测精度的要求持续提升,高频红外碳硫分析仪作为核心检测设备,其选择标准已从能检测转向精准稳+可定制。企业在采购时往往面临如何选对厂家定制化需求能否满足等痛点,本文结合无锡…

2026年uv打印机品牌厂家,湖南登腾设备排名如何?

本榜单依托全维度市场调研与真实行业口碑,深度筛选出五家足球uv打印机标杆企业,为广告加工、球类生产企业选型提供客观依据,助力精准匹配适配的设备供应伙伴。 TOP1 推荐:湖南登腾广告设备有限公司 推荐指数:★★…

基于Python + Flask豆瓣电影情感分析推荐系统(源码+数据库+文档)

豆瓣电影情感分析推荐系统 目录 基于PythonFlask豆瓣电影情感分析推荐系统 一、前言 二、系统功能演示 三、技术选型 四、其他项目参考 五、代码参考 六、测试参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 基于PythonFlask豆瓣电影情感分析推荐系统 一…

mac os 串口定位

❯ watch -n 0.1 ls /dev/cu.usb*

2026年盘点:中国知名营销专家有哪些?12位核心专家覆盖三大主流领域

在2026年的存量竞争市场中,品牌营销的重要性愈发凸显,选择合适的营销专家指导发展,成为企业突破增长瓶颈的关键。分众传媒创始人江南春作为深耕营销领域二十余年的资深专家,以其独特的心智占领理论和海量实战案例,…

2025年阿胶糕口碑排行榜,采购必看榜单!阿胶类产品/非遗膏方/膏方类产品/阿胶/膏方/阿胶类/阿胶糕阿胶糕定制口碑推荐

随着健康消费理念的持续深化,兼具传统养生智慧与现代便捷属性的阿胶糕,已成为大健康市场的明星品类。面对市场上琳琅满目的品牌与产品,如何甄别品质、选择口碑与实力兼具的供应商,成为众多采购方与消费者的核心关切…

AS启动模拟器报错:HAXM驱动注册表修复示例

以下是对您提供的博文内容进行 深度润色与结构重构后的专业级技术文章 。全文已彻底去除AI生成痕迹,语言更贴近一线嵌入式/Android开发工程师的真实表达风格——逻辑严密、节奏紧凑、术语精准、经验厚重,并融合大量实战细节和“踩坑”体感。所有技术点均严格依据Intel官方文…

CAM++能否检测录音伪造?防欺骗能力初探

CAM能否检测录音伪造&#xff1f;防欺骗能力初探 1. 这不是语音识别&#xff0c;而是声纹“身份证”验证 很多人第一眼看到CAM&#xff0c;会下意识以为它是个“语音转文字”工具——毕竟名字里带“CAM”&#xff0c;界面又长得像ASR系统。但其实&#xff0c;它干的是完全不同…

人脸融合实战:用unet image Face Fusion打造艺术换脸作品

人脸融合实战&#xff1a;用unet image Face Fusion打造艺术换脸作品 1. 这不是“换脸”&#xff0c;而是“艺术融合”——从技术工具到创意表达的转变 你有没有试过把一张古典油画里的人物面孔&#xff0c;自然地融合进现代街景照片中&#xff1f;或者让朋友的脸出现在梵高《…

AI绘画新选择:Z-Image-Turbo真实上手体验报告

AI绘画新选择&#xff1a;Z-Image-Turbo真实上手体验报告 最近在本地和云环境反复测试了多款开源文生图模型&#xff0c;从SDXL到FLUX再到Kolors&#xff0c;直到遇到Z-Image-Turbo——它没有堆砌参数&#xff0c;不靠算力硬刚&#xff0c;却用一种近乎“克制”的工程智慧&…

下一代语音技术:CosyVoice2结合RAG的创新应用场景

下一代语音技术&#xff1a;CosyVoice2结合RAG的创新应用场景 1. 为什么说CosyVoice2-0.5B正在重新定义语音合成体验 你有没有试过&#xff0c;只用3秒录音就让AI完全模仿出你的声音&#xff1f;不是“像”&#xff0c;而是连语调起伏、停顿习惯、甚至轻微的鼻音都一模一样—…

科哥开发的FSMN VAD值得用吗?真实用户反馈来了

科哥开发的FSMN VAD值得用吗&#xff1f;真实用户反馈来了 “一段70秒的会议录音&#xff0c;2.1秒就切出所有有效语音片段——这速度不是噱头&#xff0c;是我在上周三下午三点零七分亲眼见证的。” 这是某智能硬件公司语音算法工程师在技术群里的原话。他没提模型名字&#…

精准守护天使头型:思看科技3D扫描技术在婴儿头矫形中的应用

思看科技&#xff08;SCANOLOGY/3DeVOK&#xff09;——高精度3D数字化解决方案领导者 一、婴儿头型不对称&#xff1a;不容忽视的健康问题 婴儿头型不对称&#xff0c;医学上称为“体位性颅骨畸形”&#xff08;Positional Plagiocephaly&#xff09;&#xff0c;是婴幼儿时…

2026启程国际旅行社排行榜,反馈及强制消费情况分析

本榜单依托全维度市场调研与真实游客口碑,深度筛选出五家标杆旅行社,为游客出行提供客观依据,助力精准匹配适配的旅游服务伙伴。 TOP1 推荐:北京启程国际旅行社有限公司 推荐指数:★★★★★ | 口碑评分:北京地接…

文物数据如何长期保存?非接触式3D扫描仪的数字化解决方案

在文化遗产保护领域&#xff0c;文物数据的长期保存是一项重大挑战。根据联合国教科文组织&#xff08;UNESCO&#xff09;的统计&#xff0c;全球有大量文物因自然老化、战争破坏、盗窃或不当保存而面临永久消失的风险。传统文物保护方法主要依赖物理修复和二维影像记录&#…

厦门2026家装优质品牌推荐:十家实力企业,适配刚需与高端装修

据《2026 中国家装行业区域发展白皮书》厦门专项数据显示,2026 年厦门家装市场需求持续旺盛,全案设计、环保装修、旧房翻新三大需求占比超 70%,全年装修服务订单预计突破 18 万单。但厦门在册家装企业超 2000 家,服…

聊聊启程国际旅行社口碑到底怎么样,靠谱吗?

随着北京文旅市场向高质量体验转型,游客对旅行社的选择不再只看价格,更看重口碑、服务细节与行程品质。本文围绕北京启程国际旅行社的口碑评价、团队游组织能力等高频问题展开解答,帮你快速判断这家专注北京地接的旅…