1. 重新认识SQLite:它不是迷你版MySQL,而是文件的升级版
这几年帮朋友做技术选型,被问得最多的一句话是:“我的项目规模不大,用SQLite还是用MySQL?”我一开始还会按照数据量、并发数、功能需求一条条去比,后来发现这个问法本身就走偏了。SQLite和MySQL压根不是同一个维度的东西。拿它们做对比,就像问“我要买个笔记本,选Word还是选打印机?”——一个是文件形态的数据库,一个是服务形态的数据库,背后的架构、部署方式、适用场景完全不同。
用一句话概括我的观点:SQLite不是“轻量级MySQL”,而是“文件格式的升级版”。你用Excel、CSV现在能做的事,用SQLite都可以做,而且做得更好;但MySQL能做的事,比如多用户高并发写入、完善的账号权限体系、主从复制和灾备,SQLite大多数做不了,也不该硬做。把这个定位想清楚,选型就不纠结了。
为什么很多人会把SQLite误当成“迷你版MySQL”?因为从外部看,它们都支持SQL、都有表结构、都有索引和事务,日常的增删改查写法几乎一样。但SQLite走的是完全不同的技术路线:它是一个嵌入式数据库,不是一个独立的数据库服务器。安装MySQL要下载安装包、初始化数据目录、配置端口、创建账号、设置远程访问权限;使用SQLite只需要在你的程序里引入一个库,调用几个API,数据库文件就自动创建出来了,全程没有服务进程、没有端口监听、没有账号密码。
1.1 嵌入式数据库的本质:数据库不是服务,而是一个文件
不理解SQLite的人,可能连“它的数据到底存在哪儿”都搞不清。SQLite的全部数据内容,包括表结构、索引、数据记录、触发器、视图,都保存在一个普通的磁盘文件里,通常后缀是.db或.sqlite。这个文件你可以复制、压缩、发邮件,别人拿到这个文件就能用SQLite工具打开查看里面的数据。
放到应用角度看,SQLite是一个C语言编写的库,它被直接链接(或动态加载)进你的应用程序进程,应用调用SQLite的API时,读写操作发生在当前进程内部,不走网络协议,不经过socket,不需要认证握手。这就是它“零配置”的根本原因:没有独立的服务进程需要启动,也就没有配置文件、端口号、账号授权这些东西。
这种形态和Excel、CSV很相似:你“打开”一个工作簿文件,编辑里面的Sheet。SQLite只是把这个过程做得更严谨——它有原子事务、崩溃恢复、字段类型约束、索引加速查询、多表关联,这些是普通文件格式给不了的。所以我说它是“文件的升级版”,而不是“数据库的降级版”。
一个很直观的例子:以前很多桌面软件用INI或XML文件保存配置,项目多了、数据量大了之后,读写效率低、容易写坏文件、没法做复杂查询。换用SQLite之后,虽然还是一个文件,但内部变成了规范化的数据表,程序里直接写SQL查询,性能和数据完整性都有了质的提升。
1.2 SQLite和MySQL的三大根本差异
把SQLite和MySQL放在一起比参数,是很多人选型痛苦的根源。为了避免这种错误对比,我把二者最核心的差异拆成三点:
| 维度 | SQLite | MySQL |
|---|---|---|
| 部署形态 | 嵌入式库,随应用进程运行 | 独立服务进程,需要安装、配置、启动 |
| 数据存储 | 单文件(.db),整库在一个文件里 | 数据文件由服务实例管理,路径和结构对客户端透明 |
| 并发能力 | 同一时刻只允许一个写事务,写锁是全局的 | 支持行级锁、MVCC,多客户端并发写入成熟稳定 |
| 权限模型 | 无用户概念,文件权限即数据库权限 | 完善的账号、权限、角色体系 |
| 网络访问 | 不支持网络协议,只能本进程访问 | 原生支持TCP/IP网络连接,远程访问无缝 |
| 适用数据量 | 单个数据库文件建议TB以下,实际几十GB到几百GB内体验良好 | 海量数据,分库分表、读写分离都能扩展 |
第一个差异是运行形态。SQLite是“进程内数据库”,应用和数据库在同一进程空间里;MySQL是“客户端/服务器数据库”,应用通过网络连接到独立的数据库进程。这意味着SQLite不需要安装和运维,但也意味着它天生不擅长跨进程、跨机器共享数据。你用MySQL,可以让上海、北京两个机房的服务器同时读写一份数据;用SQLite,数据文件放在共享目录里让两台机器同时写,大概率会把库搞坏。
第二个差异是并发模型。MySQL的行锁和MVCC让几十上百个连接同时读、同时写都不成问题;SQLite的写锁是数据库级别的,一个事务一旦开始写,整个数据库文件就被锁住,其他写操作要么等待要么直接报database is locked。SQLite的读并发很漂亮,多个连接可以同时读,但写并发上,它压根就不是为高并发在线事务设计的。像电商秒杀、订票系统、在线评论这种每秒钟几十上百次写入的场景,SQLite完全扛不住,也不该让它扛。
第三个差异是运维和可管理性。MySQL有专门的账号密码体系,可以做到库级、表级甚至字段级的权限控制;有binlog可以做数据恢复和主从复制;有丰富的监控工具和调优参数。SQLite呢,谁拿到这个文件谁就能看数据,没有任何口令保护(文件级加密需要额外扩展),没有主从同步基础能力,备份还得自己写脚本。权限、审计、灾备这类企业级需求,SQLite天生满足不了。
1.3 为什么说它是“文件的升级版”而非“轻量MySQL”
“轻量MySQL”这个说法其实害了很多人。它给人一个暗示:你只需要SQLite的一部分功能,或者你的数据量很小,所以用个简配版MySQL。但SQLite不是任何数据库的简配版本,它是一个走另一条技术路线的完整实现。它的SQL语法覆盖了常用标准SQL,支持事务ACID,支持递归CTE、窗口函数、UPSERT这些不少现代数据库才有的特性。在功能完整度上,SQLite并不“轻”。
我更喜欢拿Excel做比喻。Excel本质上是一个“文件格式 + 数据表格 + 公式函数”的组合体。SQLite的出现,相当于把Excel这个封闭的桌面文件变成了一种通用的、可编程的、可查询的数据格式。从一个普通文件升级到结构化数据库文件,这个跨越,才是SQLite真正的价值。它解决的痛点是:你需要用SQL来管理数据,但不想为此安装和维护一个数据库服务。
这个定位决定了SQLite的使用边界:凡是“数据在一个进程内,或在少数几个只读进程间共享”的场景,SQLite都极其好用;凡是“数据要被多个进程、多台机器、大量用户并发写入”的场景,SQLite就力不从心。承认这一点,选型思路就清晰了。接下来我从实际使用场景切入,聊聊哪些地方该用它、哪些地方该换MySQL。
2. 真正适合SQLite的场景:从桌面软件到边缘计算的实用边界
搞清楚SQLite的本质之后,它的适用场景就很好画了。我见过不少项目把SQLite用在非常合适的位置上,效果极好;也见过有人硬要用它扛在线订单系统,最后被锁库折磨得欲哭无泪。下面的场景分类是我在实际项目中验证过的,可以作为参考。
2.1 桌面软件、移动端与嵌入式设备的内置存储
这是SQLite最经典的阵地。手机App里,iOS和Android系统本身就把SQLite作为内置的数据库组件,微信聊天记录、浏览器历史、应用缓存,底层很多都依赖SQLite。这类场景的特点是:数据只属于当前设备,不需要多台设备并发写同一个库,而且应用必须离线可用——网络不好时先写本地,等网络恢复再同步服务器。
桌面软件同样适合。我做过一个单机版门店收银系统,客户现场就一台电脑,服务员要记商品、打小票、查库存。早期版本用的是MySQL,结果每次给客户部署都要现场安装数据库服务、配置账号、处理防火墙,光环境问题就能折腾半天。后来换成SQLite,部署就是把一个.exe和一个.db文件复制到桌面上,双击就能跑。数据备份也简单,每天营业结束把data.db拷贝到U盘就行。客户满意,我也省事。
这里要注意一个细节:桌面软件如果用了SQLite,别图省事让所有模块共享同一个数据库连接,特别是涉及界面刷新的场景。一个长时间未提交的事务很容易把整个库锁住,导致界面操作直接卡在“等待锁”状态。更合理的做法是短事务、快提交,读多写少时考虑开启WAL模式。
2.2 工控数据记录、边缘计算与本地采集
热词里出现了kingscada连接sqlite,说明工控和组态软件领域也在大量使用SQLite。我以前接触过一个工业数据采集项目,现场的PLC通过网关每秒钟上报几十个点位的数据,采集终端需要先把数据缓存下来,定期整理后再上传到中心平台。整个环境比较特殊:现场网络不稳定,经常断线重连;每台采集终端性能有限,装一个完整的MySQL服务既不现实也没必要。
最终方案就是终端本地用SQLite作为缓存池,采集程序把数据写入本地库,同步程序每隔几分钟把积压数据批量推送到服务器的MySQL。跑了一年多非常稳定。这个场景里SQLite的价值在于:不需要网络服务、不怕断网、单文件好备份。工控现场往往有多个后台服务同时读取采集数据,比如组态软件实时展示、统计分析程序做报表,SQLite的读并发完全够用,只要控制好写入来源,一般不会出现锁冲突。
我建议在类似工控、边缘计算项目里,把SQLite当作“边缘侧的数据集散地”来用:数据先落本地,统一格式,再通过自己写的同步逻辑上传到中心端。千万别让边缘端多个不可控的程序同时写同一个SQLite文件,特别是网络同步服务和采集服务并发写时,最好由一个专门的写入口负责。
2.3 数据分析、原型验证与只读分发场景
数据分析师以前最常用CSV存中间结果,但CSV一多、一乱,管理起来就是灾难:字段类型丢失、编码混乱、文件巨大就不说了,想按条件筛选数据还得写一堆脚本。SQLite做数据分析的中间格式非常合适。把不同来源的数据清洗后导入SQLite,用SQL做关联查询、聚合统计,比写Python脚本处理CSV高效得多。Jupyter Notebook里也有成熟的sqlite3模块,直接连库查询,体验很顺滑。
原型验证是另一个被我反复推荐的使用方式。新产品要做MVP(最小可行产品),不确定性很高,功能随时可能改。这时候如果一上来就搭MySQL、设计主从结构、配权限体系,光环境成本就把迭代拖慢了。先用SQLite把核心业务逻辑跑通,等产品形态稳定了、并发上来了,再迁移到MySQL,这是很成熟的演进路径。SQLite做原型还有一个好处:整个项目就是一个文件,发给同事演示“数据库”时,不用让对方装任何东西,打开就能看数据。
只读分发场景也很常见。比如每周导出一份全量商品目录、一份配置清单,以前用Excel,文件大了打开卡顿,多人修改还会冲突。改成SQLite文件后,接收方既可以用工具查阅,也可以通过程序做精准查询,而且SQLite支持多进程同时只读打开同一个文件,比Excel的共享工作簿可靠得多。数据以SQLite文件形态分发的场景,本质上就是把数据库文件当成一种高价值的交付物。
3. 哪些场景不要硬上SQLite,尽快换MySQL
说完了适合用的地方,再说说什么情况该坚决换掉SQLite。选型最怕的就是“手里有把锤子,看什么都是钉子”——SQLite确实灵活方便,但有些业务场景它就是撑不住,硬上用只会把项目拖垮。
3.1 高并发写入、多用户在线事务必须换MySQL
SQLite的写锁机制决定了它的写并发上限很低。两个连接同时执行写事务时,只有一个能成功获得写锁,另一个要么等待busy_timeout超时,要么直接报错。你可以把这个锁想象成厕所的蹲位:一个人在里面,其他人只能排队,而且这个“蹲位”是整个数据库共用的,不是某个表、某行数据。所以一旦业务出现多用户频繁下单、评论、点赞这类写操作,SQLite就会成为系统瓶颈,而且是不可调和的架构瓶颈。
我见过一个团队用SQLite做了个内部工单系统,用户量不大,十几个人用。刚开始没事,后来大家习惯同时提工单、修改状态,系统开始频繁报“database is locked”,页面一直转圈。他们想过调大busy_timeout、开启WAL、优化事务,但这些都是缓解,不是根治。根治的办法只能是把数据库替换成MySQL或者PostgreSQL这类专业的在线事务数据库。现在回头看,如果当初直接上MySQL,反而能省掉后面两个星期的返工。
读多写少的场景SQLite可以很舒服,但一旦写入频率上来,不要试图用“优化”来续命,换数据库才是正路。这不是SQLite的缺陷,而是它的设计取舍:它是给单进程、低并发写入设计的嵌入式数据库,不是给在线事务服务设计的。
3.2 权限体系、集中运维与容灾要求较高的系统
SQLite没有用户和权限的概念。GRANT和REVOKE这种SQL在SQLite里压根不支持,拿到data.db文件的人就等于拿到了全部数据,没有任何口令保护。如果你的系统涉及敏感数据、需要区分不同角色的访问权限、需要审计谁在什么时间改了哪条记录,SQLite天然给不了这些能力。
MySQL在这方面的成熟度完全不在一个级别。账号体系可以精确控制到客户端IP、数据库、表、字段,还有SSL传输加密、操作日志等机制。对于后台管理系统、金融系统、订单系统这类需要严格权限控制的业务场景,MySQL是更稳妥的底座。
容灾和备份上,SQLite也要弱很多。MySQL有binlog可以实现时间点恢复,有主从复制可以构建高可用架构;SQLite呢,热备份需要靠VACUUM INTO或者在线备份API,全量备份也基本上是“停止写入后拷贝文件”的思路。如果你的业务等级要求“数据库宕了五分钟之内自动切换”,那SQLite完全不在考虑范围内。
3.3 一个容易忽视的坑:别把SQLite放共享目录或网络磁盘上
这个坑我踩过,现在每次写SQLite教程都会提。有人想,“既然SQLite是文件数据库,那我把文件放到NAS或者共享文件夹里,不就能让几台电脑同时访问了吗?”理论上行得通,实践上极其痛苦。网络文件系统(比如SMB、NFS)的文件锁机制和本地磁盘不一样,锁的获取和释放并不可靠,SQLite在远程目录上很容易出现database is locked的误报,甚至会因为网络抖动导致数据库文件损坏。
真实案例:一个项目组把SQLite文件放在公司文件服务器上,让三台办公电脑共用。结果每天都有同事遇到读取异常,严重的时候数据库文件直接报file is not a database,不得不拿备份恢复。后来我让他们把数据库改成MySQL,放在内网一台服务器上,问题彻底消失。SQLite文件必须放本地磁盘,而且建议放SSD上,这也是一个需要牢记的实践。
如果需求是“多台电脑共享同一个数据库”,无论用什么理由,都应该选客户端/服务器架构的数据库,也就是MySQL这一类,而不是强行用网络磁盘共享SQLite文件。
4. 实操血泪经验:乱码、锁库与备份是三个避不开的坑
选型定了之后,实际用起来还会碰到不少细节问题。热搜词里出现了“delphi sqlite 亂碼”“sqlite查看工具”这类词,说明很多人已经在实战中遇到问题了。我挑三个出现频率最高、影响也最大的问题展开讲讲,都是我自己或者身边人踩过坑之后的总结。
4.1 Delphi连接SQLite乱码:编码问题的根源与解法
Delphi老程序员对SQLite乱码应该不陌生。SQLite内部存储文本默认使用UTF-8编码,而老版本的Delphi默认的字符串类型AnsiString在中文Windows环境下是GBK编码。两边编码不一致,写入数据库时是GBK字节流,查询出来又当作UTF-8解析,自然全是乱码。
这个问题和SQLite本身关系不大,MySQL乱码也是同样的原理,本质是“数据库端与客户端字符集没对齐”。解决思路也简单:要么让Delphi端统一使用UTF-8,要么在连接层做编码转换。
我现在的习惯是,用FireDAC或者ZeosLib连接SQLite时,直接在连接参数里指定编码。FireDAC的SQLite驱动需要把SQLiteForceUnicode设置为True;ZeosLib则是在连接字符串里加codepage=UTF8。如果是非常老的项目实在改不动,可以考虑写一个数据访问层,在读写字段时统一做AnsiToUtf8和Utf8ToAnsi转换,但这样做的性能损耗和维护成本都不小,能升级还是升级。
另外提醒一句,DB Browser for SQLite这类查看工具默认按UTF-8展示数据,如果Delphi写入的数据是GBK编码,用工具打开可能又是乱码。这时候先不要急着怀疑工具坏了,先检查一下自己程序里的编码设置。
4.2 锁库和“database is locked”:理解锁,更要理解WAL模式
“database is locked”是SQLite新手最常遇到的报错,尤其在使用Navicat或多个程序连接同一个库的时候。报错原因分几种:最常见的是一个长事务没提交,写锁一直被持有;其次是多个进程同时尝试写,只有一个成功;还有一种情况是程序中忘了关闭某些读连接,导致旧快照一直不释放。
先说一下最直接的缓解办法:连接字符串里加上busy_timeout参数,比如Data Source=app.db;Busy Timeout=5000;,这样当锁被占用时,SQLite会等待最多5秒而不是立刻报错。很多零星的操作冲突,靠这个参数就能平滑过去。
更推荐的做法是开启WAL模式。执行PRAGMA journal_mode=WAL;之后,SQLite的写入不再直接覆盖主数据库文件,而是先写入一个-wal日志文件,读操作可以继续读主库旧版本,读写并发能力大幅提升。开启WAL后,原本“读不能同时写、写不能同时读”的尴尬局面会有很大缓解。
但WAL模式不是万能的,它依然只有一个写者。我见过有人开了WAL之后放松警惕,让五个线程同时写同一个库,结果照样锁冲突。经验是:多线程应用里,尽量保证同一时刻只有一个写连接做写入,其他连接只负责读;写操作保持短事务,做完立刻提交。如果并发写仍然绕不开,就回到第三部分说的结论:该换MySQL了。
这里还要提一个备份相关的小细节:WAL模式下,数据除了在主库文件里,可能还有一部分在-wal文件里没合并回去。这时候直接拷贝.db文件,可能会丢失最新数据,必须同时考虑-wal和-shm文件,或者用下一节的方法做一致性备份。
4.3 备份别直接拷贝文件,用对工具和命令
很多人以为SQLite备份就是复制粘贴.db文件,这个习惯很危险。没有写入操作的时候,直接拷贝通常没问题;但应用还在运行、WAL模式又开了的情况下,直接拷贝主文件可能漏掉-wal里尚未合并的数据,导致备份里的内容不是最新的。
有几种靠谱的备份方式。一是使用SQLite官方自带的sqlite3命令行工具,执行.backup命令做在线热备,这种方式能保证一致性:
sqlite3 demo.db ".backup 'backup.db'"二是用VACUUM INTO命令(SQLite 3.27及以上版本支持),把当前库的一致性快照导出到新文件:
sqlite3 demo.db "VACUUM INTO 'backup_20250101.db';"这两种方案的原理都是让SQLite在内部完成一致性复制,而不是在文件系统层面对正在使用中的文件做“粗糙拷贝”。在Windows环境下,可以写一个简单的批处理脚本配合任务计划程序定时执行:
@echo off set today=%date:~0,4%%date:~5,2%%date:~8,2% sqlite3 D:\apps\data.db "VACUUM INTO 'D:\backup\data_%today%.db';"除了操作命令,顺便聊聊查看工具。DB Browser for SQLite是免费开源的选择,功能足够日常开发调试;SQLite Expert的功能更专业一些,但免费版和付费版的差异比较大;Navicat也支持SQLite,如果公司正好有Navicat的使用成本,那它的SQLite管理体验也很流畅。我的建议是别用网上流传的破解版本,SQLite相关工具大多有免费替代方案,用正版工具省心也安全。
5. SQLite还是MySQL?一张选型决策清单
技术选型到最后,拼的不是对某个数据库功能的了解程度,而是对业务场景的判断能力。我见过太多团队用SQLite硬扛在线服务,也见过有人杀鸡用牛刀地为一个300行的配置文件搭建一套MySQL集群。这里给出一套我自己常用的判断逻辑和决策清单,希望对你有用。
5.1 快速判断:你的项目属于哪一侧
| 判断维度 | 偏向SQLite | 偏向MySQL |
|---|---|---|
| 部署方式 | 应用内嵌,随程序安装,不用单独装服务 | 独立部署,需要安装客户端和服务端 |
| 数据访问方式 | 单机访问,本进程读写为主 | 多台设备、多个应用通过网络连接 |
| 并发写入 | 单写者,或极低频率的并发写 | 多用户、高频率在线写入 |
| 权限与安全 | 不敏感,文件即数据,无权限区分 | 需要账号密码、角色权限、审计 |
| 容灾与备份 | 简单备份即可,可承受少量数据丢失 | 需要主从切换、binlog恢复、高可用 |
| 数据量级 | 几十GB以内体验良好 | 海量数据,可持续水平扩展 |
| 典型场景 | 桌面软件、移动App、边缘采集、分析中间格式 | Web服务、后台系统、订单库、业务主库 |
这张表不要机械地按行打分,要整体看业务模式。你会发现,凡是“数据库本质上还是一个文件,数据跟着文件走”的场景,SQLite都是最佳选择;凡是“数据库是一个需要常驻运行的系统服务,数据要在多个节点之间流动”的场景,MySQL才是正解。
5.2 我的决策逻辑:问四个问题就够了
在实际项目中,我一般让团队回答四个问题,答案基本就清楚了。
第一,这份数据需要多台机器或多人同时写入吗?需要,就直接考虑MySQL。第二,数据量级预计会到多大?如果预期单表数据过亿或者总容量要按TB级去规划,SQLite就不适合了。第三,有没有复杂的用户权限和审计需求?有,SQLite给不了。第四,你的团队有没有精力维护一套数据库服务?如果项目很小、没有专职DBA、不想装服务端,SQLite的零运维优势就非常珍贵。
多数项目的答案并不是“从来只用一种数据库”,而是“混合使用”。我最欣赏的架构形态是:客户端本地用SQLite做缓存和离线数据存储,服务端用MySQL做业务主库;边缘设备采集数据先落SQLite,中心平台汇总时再导入MySQL。这种“边缘SQLite + 中心MySQL”的组合,既发挥了SQLite轻量、离线可用、零维护的优势,又在核心业务层保留了MySQL的并发能力和可运维性,是我在实际项目中验证过的最佳实践。
最后再分享一点个人体会:很多技术争论的根源,是双方在拿一个东西的优点去攻击另一个东西的缺点。SQLite和MySQL有各自的位置,它们不是替代关系,而是互补关系。选型时先把定位想清楚,你是在找一个“文件格式的升级版”,还是在找一个“需要专人维护的数据库服务”——这个问题有了答案,其他的技术细节都不难解决。