直接用executemany()插入百万行仍慢,是因为默认自动提交模式导致每批执行都隐式触发事务提交和磁盘刷写;必须显式BEGIN开启单事务、禁用synchronous、合理分批(如2000行/批)并前置PRAGMA优化才能显著提速。

为什么直接用 executemany() 插入百万行仍很慢?
因为默认情况下,sqlite3 每次 execute() 或 executemany() 都会隐式开启并提交一个事务(auto-commit mode),相当于执行了百万次 INSERT + 百万次磁盘刷写。即使关掉 isolation_level,如果没显式 BEGIN,性能依然卡在 WAL 日志同步和 fsync 上。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 必须手动用
conn.execute("BEGIN")显式开启事务,且只开一次 - 避免在循环里反复
commit(),哪怕每万条 commit 一次,也会显著拖慢速度 - 关闭自动提交:初始化连接时设
isolation_level=None,否则BEGIN可能被忽略 - 插入前加
conn.execute("PRAGMA synchronous = OFF")(仅开发/导入场景,生产慎用)
如何正确组织批量插入的事务边界?
事务不是越长越好——太长会导致锁表时间久、内存占用高、崩溃后回滚代价大。对百万级数据,推荐单事务 + 分批次 executemany(),而不是单条循环或全量一次性塞入。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 每 1000–5000 行调用一次
executemany(),具体看单行数据大小;1KB 左右字段建议用 2000 行/批 - 整个百万行包裹在一个
BEGIN ... COMMIT内,不要分段COMMIT - 示例结构:
conn.execute("BEGIN")<br>for i in range(0, len(data), 2000):<br> batch = data[i:i+2000]<br> conn.executemany("INSERT INTO t(x,y) VALUES (?,?)", batch)<br>conn.commit() - 若中途出错,靠
try/except捕获后conn.rollback(),别依赖自动恢复
executemany() 的参数格式踩坑点
传给 executemany() 的必须是「可迭代对象,其每个元素是长度匹配占位符的序列」,常见错误是传了 list of dict、嵌套 tuple、或字段数不一致。
常见错误现象:
-
sqlite3.ProgrammingError: Incorrect number of bindings supplied—— 某批数据里某行字段数不对 -
TypeError: parameters are of unsupported type—— 传了dict却没用executemany(..., mapping=True)(但注意:原生sqlite3不支持mapping=True,得用conn.executemany("...", [tuple(d.values()) for d in data])) - 插入后查不到数据,但没报错 —— 实际是某批因类型不兼容静默跳过(如
None插入非空字段),需提前校验
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 插入前用
all(len(row) == expected_col_count for row in batch)快速校验 - 避免在循环中拼接字符串构造 SQL,始终用
?占位符 - 整数/浮点数直接传,字符串确保是
str,bytes要明确是否需 BLOB 类型
为什么开了事务还是比 MySQL / PostgreSQL 慢?还能怎么压?
SQLite 是文件数据库,没有服务端缓冲池和并行写入能力,瓶颈天然在磁盘 I/O 和单线程序列化写入。百万行插入快不快,不取决于“有没有事务”,而取决于「是否绕过了日志刷盘」和「是否用了内存页优化」。
可选优化项(按优先级排序):
-
conn.execute("PRAGMA journal_mode = MEMORY")—— 把 WAL 日志放内存,断电即丢,导入时极有效 -
conn.execute("PRAGMA temp_store = MEMORY")—— 临时表也走内存 -
conn.execute("PRAGMA cache_size = 10000")—— 增大 page cache,减少磁盘读(单位是页,默认 4KB/页) - 插入前建好索引?错。应该先插入,再
CREATE INDEX,否则每插一行都更新索引树
真正容易被忽略的是:这些 PRAGMA 设置必须在 BEGIN 之前执行,且对已打开的连接才生效;如果用完就 close,下次还得重设。


















