单条INSERT高并发卡顿源于完整事务流程(加锁→redo→binlog→刷盘→释放锁)引发锁冲突、IO堆积与主从延迟;应改用分批+显式事务+合理batch size(500–2000行)的批量插入,并配置rewriteBatchedStatements=true等参数优化。

为什么单条 INSERT 在高并发下会卡住
因为每条 INSERT 都要走完整事务流程:加锁 → 写 redo log → 写 binlog → 刷盘(或等待刷盘)→ 释放锁。并发一高,行锁/间隙锁冲突、磁盘 I/O 队列堆积、主从延迟连带恶化,INSERT 延迟就从毫秒级跳到几百毫秒甚至秒级。
常见错误现象:SHOW PROCESSLIST 里大量 query end 或 updating 状态;慢查询日志中出现大量 INSERT ... VALUES (...) 耗时 >100ms;监控看到 Innodb_row_lock_time_avg 突增。
- 别在循环里拼接
INSERT INTO t VALUES (a),(b),(c)手动凑批量——容易超max_allowed_packet,且没控制批次大小 - 用 ORM(如 MyBatis、SQLAlchemy)时,默认单条提交,必须显式启用批量模式,否则
session.flush()或sqlSession.commit()仍按单条执行 - MySQL 5.7+ 的
bulk_insert_buffer_size默认才 8MB,大批量插入前建议调到 64–128MB(需配合LOAD DATA INFILE或原生批量接口才生效)
怎么安全地做批量 INSERT(不丢数据、不爆内存)
核心是「分批 + 显式事务 + 合理 batch size」。batch size 不是越大越好:太小起不到吞吐提升作用,太大易触发 OOM 或锁持有时间过长。
推荐值:MySQL 单次 INSERT ... VALUES (...),(...),... 控制在 500–2000 行之间(视单行数据大小调整);应用层缓冲队列(如用 ConcurrentLinkedQueue)建议上限设为 10000 条,满即触发 flush。
- 用 JDBC 时,必须开启
rewriteBatchedStatements=true参数,否则addBatch()+executeBatch()仍是模拟批量,实际发 N 条独立语句 - MyBatis 中,
<insert>标签需配合useGeneratedKeys="false"和keyProperty置空,否则每条都会查LAST_INSERT_ID(),彻底失去批量意义 - 避免在批量中混用主键冲突策略:若表有唯一索引,
INSERT IGNORE或ON DUPLICATE KEY UPDATE会显著拖慢速度,优先考虑前置去重或用REPLACE INTO(注意会触发 DELETE + INSERT)
缓冲队列该放哪一层:应用内?还是独立服务?
缓冲本质是用内存换响应时间,但必须守住「不丢数据」底线。应用进程内缓冲最简单,但进程挂掉就丢;独立服务(如 Redis List + 消费者)更可靠,但引入额外延迟和运维复杂度。
使用场景判断:日均写入 ArrayBlockingQueue);超过 50 万条/天,或要求 99.99% 数据不丢,必须落盘中转——哪怕只是写入本地 /tmp/batch_20240520.log 再异步导入。
- 别用
ConcurrentHashMap当缓冲容器:无容量限制,GC 压力大,且无法按时间/大小触发 flush - Redis 做缓冲时,禁用
LPUSH + BRPOP组合——消费者崩了,消息就卡在 list 里,要用XRANGE/XADD流(Stream)并配GROUP消费者组,支持 ACK - 本地文件缓冲需注意:写入用
O_SYNC或定期fsync(),否则断电可能丢失最后几 KB;文件名带上时间戳和 PID,避免多实例覆盖
INSERT 延迟突然升高,先查这三处
不是所有延迟都来自 SQL 本身。高并发插入场景下,瓶颈常藏在配套机制里。
- 检查
innodb_flush_log_at_trx_commit是否为1:线上若允许最多丢 1 秒数据,可设为2,redo log 刷盘压力直降 5–10 倍 - 看
Threads_connected是否接近max_connections:连接池配置不当会导致新建连接排队,表现像 INSERT 慢,实则是连接获取卡住 - 确认主库 binlog 格式是不是
ROW+binlog_row_image=FULL:如果插入字段很多但只改其中 1 个,FULL会让 binlog 体积暴涨,拖慢写入和主从同步
缓冲和批量只是手段,真正难的是平衡「吞吐」「一致性」「运维成本」——比如为了省事全用 Redis 缓冲,结果某天 Redis 内存打满,整个写入链路雪崩,这种坑比 SQL 慢更难定位。

















