解决电商系统中MySQL死锁

一、背景描述

背景描述

一个订单系统和库存系统的死锁问题。

比如用户下单时,需要同时更新订单表库存表,而不同的操作顺序可能导致死锁。这种情况下,事务A先更新订单表再更新库存表,而事务B先更新库存表再更新订单表,这样可能会形成循环等待,导致死锁。

背景知识

对MySQL锁机制的理解,包括共享锁、排他锁、行锁、间隙锁、Next-Key锁,以及不同隔离级别对锁的影响。

MySQL的锁与隔离级别及其应用

解决问题的系统方法:复现问题、分析日志、定位原因、实施解决方案、验证效果。

通用步骤

分析死锁日志、理解事务执行顺序锁获取顺序、调整事务逻辑数据库设计测试验证,以及监控预防

同时强调预防死锁的最佳实践,如保持事务简短、按顺序访问资源、合理设计索引等。

二、解决MySQL死锁的案例及全面分析

在电商系统中,用户下单时需同时操作订单表(orders)和库存表(inventory)。两个事务因操作顺序不同导致死锁。以下是具体场景:

订单表结构orders

CREATE TABLE orders (
    order_id INT PRIMARY KEY,
    user_id INT,
    item_id INT,
    quantity INT,
    status VARCHAR(20)
);

库存表结构orders

CREATE TABLE inventory (
    item_id INT PRIMARY KEY,
    stock INT
);

死锁发生过程

事务A:用户下单操作

BEGIN;
-- 1. 插入新订单
INSERT INTO orders VALUES (1001, 1, 101, 2, 'pending');
-- 2. 扣减库存
UPDATE inventory SET stock = stock - 2 WHERE item_id = 101;
COMMIT;

事务B:后台库存调拨操作

BEGIN;
-- 1. 增加调拨库存
UPDATE inventory SET stock = stock + 5 WHERE item_id = 101;
-- 2. 标记调拨订单完成
UPDATE orders SET status = 'completed' WHERE order_id = 1000;
COMMIT;

死锁原因分析


1.锁竞争顺序不同

事务A先锁orders,再锁inventory。

事务B先锁inventory,再锁orders。

形成循环等待:A等B释放inventory,B等A释放orders。

2.间隙锁(Gap Lock)的隐形作用
在REPEATABLE READ隔离级别下,事务A的INSERT操作会隐式加间隙锁,事务B的UPDATE可能被阻塞


排查与诊断

查看死锁日志


执行 SHOW ENGINE INNODB STATUS;,关注 LATEST DETECTED DEADLOCK 部分:

*** (1) TRANSACTION: TRANSACTION 12345, ACTIVE 2 sec updating
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 100, OS thread handle 0x7f8a1c0b6700, query id 2000 updating
UPDATE inventory SET stock = stock + 5 WHERE item_id = 101;

*** (1) HOLDS THE LOCK(S): 
-- 事务B持有 inventory.item_id=101 的排他锁(X锁)

*** (2) TRANSACTION: TRANSACTION 12346, ACTIVE 3 sec inserting
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 101, OS thread handle 0x7f8a1c0b6800, query id 2001 inserting
INSERT INTO orders VALUES (1001, 1, 101, 2, 'pending');

*** (2) HOLDS THE LOCK(S): 
-- 事务A持有 orders 表的间隙锁(Gap Lock)

关键结论

事务A和B以相反顺序竞争锁,形成循环依赖。

间隙锁在插入新订单时隐式加锁,加剧了阻塞。

解决方案


1.统一资源访问顺序

强制所有事务按固定顺序操作:先操作inventory,再操作orders。

调整事务A:

BEGIN;
-- 先操作库存表
UPDATE inventory SET stock = stock - 2 WHERE item_id = 101;
-- 再操作订单表
INSERT INTO orders VALUES (1001, 1, 101, 2, 'pending');
COMMIT;

2.降低隔离级别

从REPEATABLE READ改为READ COMMITTED,减少间隙锁的使用:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

3.优化索引设计

orders表的item_id添加索引,避免全表扫描:

CREATE INDEX idx_orders_item_id ON orders (item_id);

4.重试机制


在应用层捕获死锁错误(MySQL错误码1213),自动重试事务:

# Python伪代码示例
max_retries = 3
for _ in range(max_retries):
    try:
        execute_transaction()
        break
    except MySQLdb.OperationalError as e:
        if 'Deadlock found' in str(e):
            continue
        else:
            raise

覆盖的其他常见死锁场景


批量更新中的锁升级

场景:两个事务批量更新同一组数据的不同子集。

解决:将批量操作拆分为单条处理,或按固定顺序排序。

不同索引导致的锁冲突

场景:事务A通过索引A锁定行,事务B通过索引B锁定同一行。

解决:统一使用主键或唯一索引访问数据。

长事务与锁堆积

场景:长事务持有锁时间过长,导致后续事务堆积。

解决:缩短事务执行时间,避免在事务中处理业务逻辑。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值