业务:点赞操作需原子性更新文章热度。
START TRANSACTION;
INSERT INTO user_praise(uid, plan_id, stage_id) VALUES (123456, 14456, 10023);
UPDATE plan_hot SET hot = hot + 1 WHERE plan_id = 14456;
COMMIT;
上线后报错:
Deadlock found when trying to get lock; try restarting transaction
SQL: UPDATE plan_hot SET hot = hot + 1 WHERE plan_id = ?
CREATE TABLE `coupon` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`spec_id` CHAR(20) NOT NULL,
`coup_usr` CHAR(11) DEFAULT NULL,
`coup_num_usr` INT UNSIGNED NOT NULL DEFAULT 0,
KEY `coup_usr_idx` (`coup_usr`),
KEY `spec_idx` (`spec_id`)
) ENGINE=InnoDB;
UPDATE ... WHERE coup_usr=? AND spec_id=?,命中二级索引 coup_usr_idx,加 X 锁锁定二级索引记录。spec_idx,先锁二级索引 spec_idx,再请求主键锁。coup_usr_idx 锁等主键锁,事务 B 持有 spec_idx 锁等主键锁 → 循环等待 → 死锁。核心规律:InnoDB 行锁加锁顺序 = 索引扫描顺序。不同索引扫描顺序不一致,极易形成交叉等待。
-- 步骤 1:仅查主键,无锁(快照读)
SELECT id FROM coupon
WHERE coup_usr = ? AND spec_id = ? AND coup_num_usr < ?;
-- 步骤 2:按主键逐条更新,加锁顺序固定为主键顺序
UPDATE coupon SET coup_num_usr = coup_num_usr + 1
WHERE id = ? AND coup_num_usr < ?;
SELECT 默认 MVCC 快照读,不加锁,消除第一阶段锁竞争。UPDATE ... WHERE id=? 直接走聚集索引,所有事务按相同主键顺序加锁,彻底杜绝死锁。IN (id1,id2...)。ALTER TABLE coupon ADD COLUMN version INT UNSIGNED NOT NULL DEFAULT 0;
UPDATE coupon
SET coup_num_usr = coup_num_usr + 1, version = version + 1
WHERE coup_usr = ? AND spec_id = ? AND coup_num_usr < ? AND version = ?;
rowcount=0,应用层重试。UPDATE coupon FORCE INDEX (coup_usr_idx)
SET coup_num_usr = coup_num_usr + 1
WHERE coup_usr = ? AND spec_id = ? AND coup_num_usr < ?;
FORCE INDEX,不够稳健。WHERE 中,导致优化器选择不确定。Innodb_deadlocks 指标,定期 SHOW ENGINE INNODB STATUS 分析死锁日志。