应选 microsoft.data.sqlite,它是微软官方维护的 ado.net 提供程序,支持 async/await、连接池、参数化查询及 ef core 深度集成;sqlite-net-pcl 仅为轻量 orm,不支持复杂关系与真正异步,仅适用于简单 crud 场景。

直接用 Microsoft.Data.Sqlite,别碰 System.Data.SQLite —— 后者已多年未更新,.NET 6+ 项目中容易在 ARM64(如 Windows on Snapdragon)或 macOS 上崩溃,且不支持现代异步模式。
选哪个 NuGet 包:Microsoft.Data.Sqlite 还是 sqlite-net-pcl?
Microsoft.Data.Sqlite 是微软官方维护的 SQLite ADO.NET 提供程序,与 Entity Framework Core 深度集成,支持 async/await、连接池、参数化查询原生语法;sqlite-net-pcl 是轻量 ORM,适合纯 CRUD 小工具,但不支持复杂关系映射,且其 InsertAsync 等方法底层仍是同步 I/O 封装,实际没提升并发能力。
如果你只是存配置、日志或用户本地数据,且不想引入 EF Core,sqlite-net-pcl 可用;但只要涉及事务、多线程写入、或未来可能升级为 EF Core,一律选 Microsoft.Data.Sqlite。
- 安装命令(包管理器控制台):
Install-Package Microsoft.Data.Sqlite - 连接字符串示例:
"Data Source=app.db;Cache=Shared;Mode=ReadWriteCreate;"——Cache=Shared是关键,否则多线程并发写会报SQLITE_BUSY - 避免用
Version=3这类过时参数,新版驱动自动识别
创建数据库和表时最容易漏掉的两件事
SQLite 不会自动建库文件,也不会自动建表——你得显式执行 CREATE TABLE,但更常见的是:忘了在首次连接时检查并初始化。
不要写 if (!File.Exists("app.db")) { SQLiteConnection.CreateFile("app.db"); } —— 这行代码在 Microsoft.Data.Sqlite 中根本不存在,它已被移除。正确做法是直接打开连接,再执行建表语句,并捕获 SqliteException 中的错误码 1(SQLITE_ERROR)来判断是否需建表。
- 建表语句必须带
IF NOT EXISTS,否则第二次运行会抛异常 - 主键推荐用
INTEGER PRIMARY KEY(即 ROWID 别名),它自动递增且高效;别用GUID或字符串主键,会拖慢插入 - 如果表字段含中文或特殊字符,建表时无需额外编码设置,UTF-8 是默认且唯一支持的编码
参数化查询为什么不是“可选”,而是必须?
拼接 SQL 字符串(如 "INSERT INTO Users VALUES ('" + name + "')" )在 SQLite 中照样触发 SQL 注入,哪怕只是本地应用。SQLite 不区分“服务端”和“客户端”,注入成功后可读取任意同目录下的 .db 文件,甚至通过 ATTACH DATABASE 加载其他数据库。
Microsoft.Data.Sqlite 的参数占位符统一用 @name,不支持问号 ? 位置参数(那是 sqlite-net-pcl 的写法)。
- 错误写法:
command.CommandText = $"INSERT INTO Logs (Msg) VALUES ('{msg}')"; - 正确写法:
command.CommandText = "INSERT INTO Logs (Msg) VALUES (@msg)"; command.Parameters.AddWithValue("@msg", msg); - 批量插入别用循环 ExecuteNonQuery —— 改用事务包裹,性能能提升 10 倍以上
事务和并发写入的实际表现
SQLite 默认是“序列化事务”,意味着写操作会锁整个数据库文件,不是单张表。所以高频率写入(比如每秒写 50 条日志)时,SQLITE_BUSY 错误非常常见,不是代码写错了,是设计使然。
缓解方式只有两个:一是用 PRAGMA journal_mode=WAL; 切换到 WAL 模式(支持读写并发),二是在连接字符串加 BusyTimeout=5000 自动重试。
- 启用 WAL 模式只需在首次打开连接后执行一次:
connection.Execute("PRAGMA journal_mode=WAL;"); -
BusyTimeout单位是毫秒,设太长会导致 UI 冻结;设太短(如 100ms)则重试失败率高 - WAL 模式下会产生
-wal和-shm临时文件,备份时必须三者一起拷贝,否则恢复失败
真正麻烦的不是语法,而是 WAL 文件生命周期、连接未释放导致的文件锁、以及跨进程访问时的权限问题——这些不会报错,只会让程序某天突然卡住或丢数据。










