sqlite批量插入慢主因是默认配置过度保护:每条insert都fsync、锁文件、重编译sql;显式事务(begin/commit)为性能底线,可将10万次落盘压缩为1次;配合wal模式、预编译语句及合理pragma调优(如synchronous=normal、cache_size适度增大),性能可提升3–5倍。

SQLite 批量插入慢,八成不是数据量问题,而是默认配置在“认真干活”——每插一条都 fsync、都锁文件、都重编译 SQL。关掉这些默认保护,性能能提 3–5 倍,但得清楚每关一个意味着什么。
为什么显式事务是底线,不是可选项
SQLite 默认每条 INSERT 都是一个独立事务,触发完整日志写入 + 文件同步。10 万行 = 10 万次磁盘落盘。加一层 BEGIN TRANSACTION 和 COMMIT 就能把这 10 万次压缩成 1 次。
- 必须写在所有
INSERT语句之外,不能漏掉COMMIT,否则事务一直挂起,连接卡死 - 如果用
sqlite3_exec(),注意它不返回错误码,要用sqlite3_errmsg()主动查错,失败时调ROLLBACK - C# 中用
Microsoft.Data.Sqlite时,别用connection.Execute()循环,改用using var transaction = connection.BeginTransaction()包裹command.Transaction
PRAGMA 设置哪些能开,哪些要三思
这些 PRAGMA 不是“越关越快”,而是按风险分级:有的只影响崩溃后恢复能力,有的直接绕过持久化保证。
-
PRAGMA synchronous = OFF:跳过fsync(),写入速度翻倍,但断电或崩溃大概率丢最后几秒数据 -
PRAGMA journal_mode = WAL:启用写前日志模式,允许多读一写并发,且避免写操作阻塞读;比DELETE模式快,也更安全,推荐开启 -
PRAGMA cache_size = 10000:把页缓存从默认 2000 提到 10000(单位是页),减少磁盘读取,对批量插入帮助明显;但别设太大,超过物理内存会反拖慢 - 别碰
PRAGMA journal_mode = MEMORY:日志全放内存,崩溃即丢失整个事务,仅限测试环境
预编译语句为什么比拼字符串快
每次用字符串拼 INSERT INTO t(a,b) VALUES ('x','y'),SQLite 都要解析语法树、生成字节码、校验字段类型——重复 10 万次就是纯 CPU 浪费。
- 改用参数化语句:
INSERT INTO t(a,b) VALUES (?,?),只编译一次,后续只绑定值 - Python 用
cursor.executemany(),底层自动复用预编译句柄;C# 用SqliteCommand.Prepare()+ 循环Parameters.Clear()+Add() - 字段数多时(比如 10+ 列),预编译收益更明显;字段少但行数极多(百万级),差异会收窄,但仍有 5–10% 提升
executemany 和 INSERT SELECT 的适用边界
两者都省网络/驱动层开销,但来源和可控性完全不同。
- 用
executemany():数据来自内存(如 List、DataFrame 行),适合动态生成、需业务逻辑过滤的场景;注意单次传参别超 999 个占位符(SQLite 限制),拆成每批 500 行较稳 - 用
INSERT INTO t SELECT ...:数据源是另一张表、CTE 或 VALUES 子句;适合静态迁移、ETL 场景;VALUES 写法要小心嵌套层级,UNION ALL超过 500 个会报too many terms in compound SELECT - 别在
INSERT SELECT里调函数(如strftime()),计算压力会压到单次查询上,不如提前算好再进executemany()
真正卡住性能的,往往不是“怎么写更快”,而是“哪一步没关同步却以为开了事务”。WAL 模式 + 显式事务 + 预编译,这三项配齐,80% 的批量插入慢问题就消失了;剩下 20%,得看是不是在 SSD 上跑机械盘参数,或者表上堆了十几个没用的索引。










