mysql在高并发写入时的事务与锁策略优化

来源:这里教程网 时间:2026-02-28 20:44:13 作者:

为什么
INSERT ... ON DUPLICATE KEY UPDATE
SELECT + INSERT/UPDATE
更适合高并发写入

因为前者是原子操作,避免了“读-判-写”窗口期引发的重复插入或覆盖丢失。在并发场景下,

SELECT
查不到记录 → 多个事务同时走
INSERT
→ 唯一键冲突;或者查到记录 → 多个事务都执行
UPDATE
→ 后写覆盖前写。而
ON DUPLICATE KEY UPDATE
由 MySQL 在引擎层加行锁(针对冲突的唯一索引值),天然串行化冲突路径。

实操建议:

确保冲突字段上有唯一索引(主键或
UNIQUE
约束),否则不触发更新逻辑
避免在
UPDATE
子句中引用未在
VALUES()
中提供的列,否则可能产生非预期空值
若需根据原值做计算(如计数器自增),用
col = col + 1
而非
col = VALUES(col) + 1
,后者会取本次插入值而非当前行值

INSERT INTO ... SELECT
在并发写入时为何容易锁表或死锁

该语句默认对源表(

SELECT
部分)加一致性读锁(MVCC),但目标表(
INSERT INTO
)会按实际写入顺序逐行加
INSERT intention lock
和行锁。当多个事务同时执行类似语句,且涉及相同主键/唯一键范围时,容易因加锁顺序不一致导致死锁;若源表无合适索引,还可能升级为间隙锁(gap lock),阻塞其他范围写入。

实操建议:

源表
SELECT
条件必须命中索引,避免全表扫描触发大范围 gap lock
目标表主键或唯一键字段务必有索引,否则
INSERT
过程无法准确定位冲突行,退化为更重的锁机制
考虑拆成小批量(如
LIMIT 1000
+ 循环),降低单次事务持有锁的时间

如何用
innodb_lock_wait_timeout
和死锁日志定位真实瓶颈

单纯调高

innodb_lock_wait_timeout
(默认 50 秒)只是掩盖问题,真正要解决的是锁等待源头。MySQL 的
SHOW ENGINE INNODB STATUS
输出中,
LATEST DETECTED DEADLOCK
段落会明确列出两个事务各自持有的锁、等待的锁、SQL 语句及加锁的索引记录 —— 这比应用层报错信息更精准。

实操建议:

在业务 SQL 中显式指定锁粒度:需要强一致性时用
SELECT ... FOR UPDATE
,仅防幻读可用
SELECT ... LOCK IN SHARE MODE
检查事务中是否有长耗时操作(如远程调用、大循环),它们会拉长锁持有时间,放大竞争概率 确认是否误用了
REPEATABLE READ
隔离级别下的间隙锁:若业务允许,可降级为
READ COMMITTED
(需关闭
binlog_format = 'STATEMENT'

批量写入时
INSERT
语句合并与事务大小的平衡点

单条

INSERT
开销大(网络往返 + 日志刷盘),但事务过大(如 10 万行)会导致 undo log 膨胀、锁持有时间过长、主从延迟加剧。经验值是每事务 1k–5k 行,具体取决于单行数据大小和磁盘 I/O 能力。

实操建议:

INSERT INTO t VALUES (...), (...), (...)
合并多行,而非多次单行
INSERT
禁用自动提交(
SET autocommit = 0
),手动控制
COMMIT
时机
避免在事务中混用 DDL(如
ALTER TABLE
),它会隐式提交当前事务并锁全表
INSERT INTO orders (user_id, amount, status) 
VALUES 
(1001, 99.9, 'paid'),
(1002, 129.5, 'paid'),
(1003, 59.0, 'pending');

真正难处理的不是锁本身,而是不同业务逻辑对“一致性”的定义差异——比如库存扣减要求绝对精确,而日志写入允许短暂重复。把锁策略和业务语义对齐,比调参重要得多。

相关推荐