MySQL案例分析

先交代下背景:我们做的是个电商促销系统,高峰期每秒得有上百个下单请求。数据库用的是MySQL 5.7,InnoDB引擎,事务隔离级别是默认的REPEATABLE-READ。订单表结构大概长这样:

问题出在用户抢购时,系统会同时执行两个操作:一是更新订单状态(比如从待支付改为已完成),二是减少商品库存。这两步放在一个事务里,用到了行锁。但诡异的是,监控日志里总出现"Deadlock found when trying to get lock"的报错,平均每分钟能触发两三次。

排查过程挺折腾的。首先用抓取了死锁详情,发现冲突集中在这个普通索引上。事务A先锁住了product_id=100的记录准备更新状态,同时事务B锁住了product_id=101的记录要减库存,但接着事务A试图去获取product_id=101的锁,事务B又反过来抢product_id=100的锁——典型的循环等待。根因在于更新语句走了非唯一索引,导致间隙锁(gap lock)和临键锁(next-key lock)互相阻塞。

具体来说,问题语句是:

虽然看着是操作同一条产品记录,但MySQL在REPEATABLE-READ级别下,对非唯一索引的更新会锁住索引区间。比如product_id=100附近有间隙锁保护,其他事务要更新相邻product_id时就会被阻塞。更麻烦的是,库存表更新时也会对产品ID加锁,两个事务交叉等待就死锁了。

解决方案从三方面入手:第一是优化索引,把改成覆盖索引,让更新操作直接走索引覆盖,避免回表锁冲突;第二是调整事务顺序,统一先更新库存再处理订单状态,减少锁持有时间;第三是加入重试机制,用Spring的注解在死锁时自动重试3次。改完后的核心代码类似:

另外还把事务隔离级别降到了READ-COMMITTED,虽然可能引发幻读,但对我们这个业务场景影响不大,毕竟促销活动都是短时爆发,数据一致性要求没那么严苛。

上线后观察了一周,死锁频率从每天几十次降到零。这个案例给我的教训是:MySQL的锁机制远比想象中复杂,特别是在非主键索引上操作时,间隙锁容易成为性能杀手。开发时不能光盯着业务逻辑,还得结合数据访问模式来设计索引和事务。建议大家在设计阶段就用分析执行计划,必要时用视图监控锁竞争。毕竟数据库调优就是个不断填坑的过程,多积累点实战经验总没坏处。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值