MySQL多事件并行插入时的随机失败问题排查
问题原因与解决方案
核心原因
你遇到的问题是并发竞争导致的重复插入冲突,和自增主键无关,关键在于子查询的非原子性:
- 当5个事件同时执行时,多个数据库会话会在同一时刻执行子查询,此时所有会话都能看到同一个未被插入到
PublishRecord的ChapterKey(因为还没有任何会话完成插入操作)。 - 第一个会话成功插入后,后续会话再插入同一个
TierKey+ChapterKey组合时,会触发唯一约束冲突(大概率是PublishRecord表存在TierKey和ChapterKey的组合唯一约束,只是你没留意到),导致插入失败。 - 手动执行时没有并发竞争,子查询能正确拿到未被占用的
ChapterKey,所以不会冲突。
解决办法
1. 用INSERT ... SELECT加行锁避免重复选取(推荐)
把原SQL改成INSERT ... SELECT形式,并在子查询中添加FOR UPDATE SKIP LOCKED(MySQL 8.0+、PostgreSQL等主流数据库支持),这样在选取章节时会锁定该行,其他会话会直接跳过已锁定的行,避免重复选取:
INSERT INTO PublishRecord (TierKey, ProductId, PageKey, StoryKey, PublishDate, ChapterKey) SELECT 'liesijznoo', 'prod_oOcEiHmKEh04Ag', 'lies5yw6p3', 'liyy27t7t7', NOW(), cid FROM ( SELECT PublishingQueue.ChapterKey as cid FROM PublishingQueue LEFT JOIN Chapters ON Chapters.ChapterKey = PublishingQueue.ChapterKey LEFT JOIN PublishRecord ON PublishRecord.ChapterKey = PublishingQueue.ChapterKey AND PublishRecord.TierKey = 'liesijznoo' WHERE Chapters.StoryKey = 'liyy27t7t7' AND PublishRecord.TierKey IS NULL ORDER BY Chapters.ChapterOrder ASC LIMIT 1 FOR UPDATE SKIP LOCKED ) as c00;
2. 悲观锁+事务兜底
如果你的数据库不支持SKIP LOCKED,可以把插入逻辑放在事务中,先锁定目标章节行,确保只有当前会话能操作:
START TRANSACTION; -- 先锁定要选取的章节 SELECT PublishingQueue.ChapterKey as cid FROM PublishingQueue LEFT JOIN Chapters ON Chapters.ChapterKey = PublishingQueue.ChapterKey LEFT JOIN PublishRecord ON PublishRecord.ChapterKey = PublishingQueue.ChapterKey AND PublishRecord.TierKey = 'liesijznoo' WHERE Chapters.StoryKey = 'liyy27t7t7' AND PublishRecord.TierKey IS NULL ORDER BY Chapters.ChapterOrder ASC LIMIT 1 FOR UPDATE; -- 执行插入 INSERT INTO PublishRecord (TierKey, ProductId, PageKey, StoryKey, PublishDate, ChapterKey) VALUES ('liesijznoo', 'prod_oOcEiHmKEh04Ag', 'lies5yw6p3', 'liyy27t7t7', NOW(), @cid); COMMIT;
3. 添加明确的唯一约束并处理冲突
如果PublishRecord还没加组合唯一约束,先补上:
ALTER TABLE PublishRecord ADD UNIQUE INDEX idx_tier_chapter (TierKey, ChapterKey);
之后可以在插入时用ON DUPLICATE KEY UPDATE避免报错(比如更新PublishDate),或者捕获冲突后自动重试插入。
内容的提问来源于stack exchange,提问作者Sean Patnode
相关产品推荐
相关产品推荐

