SQLite写入性能优化咨询:单线程C#插入50万条记录过慢
我之前也踩过SQLite批量插入的坑——一开始数据量小的时候飞一样,结果数据涨到几十万条,单线程插入直接卡成狗。结合C#的使用场景,给你几个亲测有效的优化手段,按优先级排序:
1. 用事务把所有插入包起来(最关键的一步)
SQLite默认每条语句自动开启一个事务,每次插入都要刷盘到磁盘,50万次刷盘操作简直是性能杀手。把所有插入逻辑放在一个事务里,只需要在最后提交时刷一次盘,性能能提升几十甚至上百倍。
C#里的实现很简单:
using var connection = new SqliteConnection("你的连接字符串"); connection.Open(); using var transaction = connection.BeginTransaction(); // 这里放所有插入逻辑 transaction.Commit(); connection.Close();
如果担心单个事务太大(比如内存不够),也可以分批次提交事务,比如每1万条提交一次——不过50万条的话,只要单条数据不是特别大,一个事务完全hold住。
2. 批量参数化插入,别单条插
单条参数化插入还是慢,改成一次插入多条记录的方式。SQLite支持INSERT INTO table (col1, col2) VALUES (v1,v2), (v3,v4), ...的语法,配合参数化避免SQL注入,同时减少SQL解析的开销。
注意SQLite默认有参数数量限制(默认999个),所以要分批次插入,比如每次插100条(每条2个参数的话,总共200个参数,远低于限制)。示例代码:
const int batchSize = 100; var valuesSegments = new List<string>(); var paramIndex = 0; var command = connection.CreateCommand(); command.Transaction = transaction; foreach (var dataItem in your500kRecords) { // 定义参数名 var paramId = $"@id{paramIndex}"; var paramName = $"@name{paramIndex}"; valuesSegments.Add($"({paramId}, {paramName})"); // 添加参数值 command.Parameters.AddWithValue(paramId, dataItem.Id); command.Parameters.AddWithValue(paramName, dataItem.Name); paramIndex++; // 达到批次大小就执行一次插入 if (paramIndex >= batchSize * 2) // 每条2个参数,batchSize条对应2*batchSize个参数 { command.CommandText = $"INSERT INTO YourTable (Id, Name) VALUES {string.Join(", ", valuesSegments)}"; command.ExecuteNonQuery(); // 重置批次数据 command.Parameters.Clear(); valuesSegments.Clear(); paramIndex = 0; } } // 处理最后一批不足batchSize的记录 if (valuesSegments.Count > 0) { command.CommandText = $"INSERT INTO YourTable (Id, Name) VALUES {string.Join(", ", valuesSegments)}"; command.ExecuteNonQuery(); }
3. 调整连接字符串参数,榨干SQLite性能
修改连接字符串的几个关键参数,平衡性能和安全性:
Journal Mode=WAL:使用写前日志模式,这是SQLite性能提升的大杀器——它允许多个读操作和一个写操作同时进行,比默认的DELETE模式快很多。Synchronous=NORMAL:降低同步级别,默认是FULL(每次事务都强制刷盘),NORMAL只在关键节点刷盘,性能提升明显,数据安全性也能满足大部分场景(除非遇到突然断电这种极端情况)。Cache Size=-20000:设置缓存大小,负数表示KB,这里是20MB——增大缓存可以减少磁盘IO,根据你的内存情况调整,比如可以设到-100000(100MB)。Pooling=true:开启连接池,避免频繁创建销毁连接的开销(不过单线程场景下影响不大,但开着没坏处)。
最终的连接字符串类似:
Data Source=your_database.db;Journal Mode=WAL;Synchronous=NORMAL;Cache Size=-20000;Pooling=true;
4. 避免不必要的连接操作
单线程场景下,全程保持一个数据库连接打开,不要每次插入都打开关闭连接——连接的创建销毁本身就有开销,50万次的话累积起来很可观。
5. 其他小技巧
- 禁用约束和索引:如果插入完成后再建立索引/约束,性能会提升很多。比如插入前执行
PRAGMA foreign_keys = OFF;关闭外键约束,插入完成后再打开;或者先删除索引,插入后重建。不过这一步要谨慎,确保插入的数据是合法的,不然会导致数据不一致。 - 使用
PRAGMA temp_store = MEMORY;:把临时表放在内存里,减少磁盘IO。
这些优化组合起来,50万条记录的插入时间应该能从“极长”降到几分钟甚至更短,亲测有效!
内容的提问来源于stack exchange,提问作者SineR




