mysql 8.0“不锁表”本质是避免全表扫描、间隙锁扩散和长事务,load data local infile因绕过sql解析层、不触发约束/触发器、使用专用load event,成为最接近不锁表的方案;分批insert需控制事务粒度、禁用非必要索引、避免on duplicate key等加间隙锁操作。

能,但“不锁表”不是靠关开关实现的,而是靠控制事务粒度、避开索引争用、绕过SQL解析层来达成。MySQL 8.0 的 InnoDB 默认行级锁,只要不扫全表、不触发间隙锁扩散、不长时间持有大事务,就不会实质阻塞其他读写。
用 LOAD DATA LOCAL INFILE 绕过 SQL 层最稳
这是唯一真正接近“不锁表”的方案:它不走查询解析器,不生成普通 binlog event(用的是专用 LOAD event),不逐行触发约束检查或触发器,对在线业务影响最小。
- 客户端必须启用
PDO::MYSQL_ATTR_LOCAL_INFILE => true(PHP)或allowLoadLocalInfile=true(JDBC) - 服务端要确认
local_infile=ON(SHOW VARIABLES LIKE 'local_infile') - CSV 文件字段必须严格匹配:比如含逗号就得用
ENCLOSED BY '"',换行符得是LINES TERMINATED BY ' ' - 目标表不能有
BEFORE/AFTER INSERT触发器——LOAD DATA 会跳过它们,这不是 bug 是设计 - 文件路径是客户端本地路径,Windows 下用
C:/data.csv或C:\data.csv,别用单反斜杠
分批 INSERT VALUES + 显式事务,控制锁范围
如果数据在内存里、没法落盘成文件,就只能走 SQL 路线。关键不是“插多少”,而是“每次提交多大”,否则事务一开几万秒,别的连接等锁等到超时。
- 单次
INSERT最多拼 1000–2000 行,字段越长行数越要往下压(受max_allowed_packet限制) - 必须显式写
START TRANSACTION和COMMIT,别依赖 autocommit=0 —— 某些 ORM 会悄悄重置它 - 每插入 5000 行左右
COMMIT一次,避免 undo log 膨胀引发ERROR 1205 (HY000): Deadlock found - 插入前执行
ALTER TABLE t DISABLE KEYS(仅 MyISAM 有效);InnoDB 没这指令,但可删掉非必要二级索引,插完再建 - 别用
INSERT IGNORE或ON DUPLICATE KEY UPDATE——唯一键检查会加 GAP 锁,放大锁范围
存储过程循环插入?现实里基本等于自锁
哪怕你写了 START TRANSACTION 包住整个 WHILE,MySQL 8.0 仍会在每次 INSERT 后隐式刷新日志、更新索引 B+ 树页、检查外键——这不是“一个事务”,而是“一个事务里套 N 个微型事务”。实测 10 万条简单 int 字段也要 90 秒以上,且期间 SELECT ... FOR UPDATE 容易被堵住。
- 只适合造极简测试码(比如纯 ID + 随机字符串),总量别超 1 万
- 如果真要用,必须关掉 autocommit:
SET autocommit = 0,否则COMMIT在存储过程里无效 -
DISABLE KEYS对 InnoDB 无效,别白费劲;想提速,不如先把UNIQUE索引干掉 - 别信“加了事务就快”——没禁用索引、没分批、没调
innodb_flush_log_at_trx_commit,速度照样断崖下跌
最容易被忽略的一点:锁表现象常不在插入语句本身,而在后续的 SELECT 或 UPDATE 上。比如你刚插完百万行,立刻跑 SELECT COUNT(*),InnoDB 得扫聚簇索引,这时别的写操作反而卡在插入的间隙锁上。所以“不锁表”的完整理解,是插入期间不影响其他连接的常规读写,而不是插入动作本身完全无感知。











