广告位 · 页头(后台-模块管理-新建模块后替换此区域)

SQLite是什么数据库?一文吃透轻量级嵌入式数据库的原理与实战

SQLite是一个用C语言编写的、轻量级的嵌入式关系型数据库管理系统,它不需要独立的服务器进程,整个数据库被存放在一个单独的跨平台磁盘文件里。你手机里的通讯录、浏览器的历史记录、微信的本地聊天缓存,背后大概率都有SQLite的身影。它也是目前全球部署量最大的数据库引擎,没有之一。这篇文章从架构原理讲到实际操作,再把使用中最容易踩的坑一次性说清楚。

SQLite是什么数据库?一文吃透轻量级嵌入式数据库的原理与实战

SQLite到底是什么:一个文件就是一整个数据库

理解SQLite的关键在于理解它与MySQL、PostgreSQL这类传统数据库在架构上的根本差异。MySQL采用的是客户端服务器架构,数据库本身作为一个常驻后台的服务进程运行,应用程序通过网络或本地套接字与它通信,DBMS进程负责解析SQL、管理缓存、控制并发。而SQLite走的是完全不同的路线:它本质上是一个链接进应用程序的函数库,你的进程直接调用sqlite3_open这类C接口去读写磁盘上的数据库文件,SQL的解析、执行、索引维护全部发生在你自己的进程空间里。

这种嵌入式设计带来几个非常鲜明的特点。第一,零配置零维护,没有服务要安装、启动、监控,也就没有端口占用和密码管理的问题;第二,数据库就是一个普通文件,备份就是复制文件,迁移就是拷贝文件,甚至可以通过邮件发送;第三,数据访问没有网络往返开销,在本地场景下读写延迟极低。当然代价也很明显:因为它依赖文件锁来做并发控制,多进程同时写入的能力远不如专门的服务器数据库,不适合高并发的在线写入场景。

对比项SQLiteMySQL
架构嵌入式,链接进应用进程客户端服务器,独立服务进程
部署方式零配置,无需安装需安装、配置并启动服务
数据存储单个跨平台文件服务端管理的多文件或表空间
并发写入文件锁,写操作串行多连接并发写入能力强
适用场景移动端、桌面软件、嵌入式设备高并发网站、大型业务系统

官方给SQLite的定位是嵌入式设备、移动端App、桌面软件、小型网站、数据分析的中间结果存储这些场景。一个经验性的判断标准是:如果你的写入并发不超过每秒几千次、数据库文件不超过几个TB、不需要多台机器共享同一份数据,SQLite基本都能胜任,而且表现往往比想象中好得多。

动态类型系统:建表时必须理解的核心概念

用过MySQL的人第一次接触SQLite通常会很困惑,因为你会发现往一个声明为INTEGER的列里插入字符串居然不报错。这是因为SQLite的类型系统和标准SQL完全不同,它采用的是动态类型加类型亲和性机制。列声明的类型名不会强制约束存进去的值,SQLite会根据亲和性规则尽量把值转换成声明的类型,转换不了就按原始类型存储。

SQLite内部只有五种存储类型:NULL、INTEGER、REAL、TEXT和BLOB。建表时你可以写VARCHAR(255)、DATETIME、DECIMAL(10,2)这些熟悉的类型名,但它们都会被映射到五种基本存储类型上,括号里的长度限制在SQLite里基本不起作用。下面是一个典型的建表语句:

-- 创建一个用户表
CREATE TABLE users (
    id INTEGER PRIMARY KEY,      -- 主键,实际是rowid的别名
    username TEXT NOT NULL,      -- 用户名,非空约束是生效的
    balance REAL DEFAULT 0.0,    -- 余额,默认值
    created_at TEXT DEFAULT (datetime('now', 'localtime')),
    UNIQUE(username)             -- 唯一约束
);
-- 创建索引加速查询
CREATE INDEX idx_users_created ON users(created_at);

这里有个非常重要的知识点:INTEGER PRIMARY KEY在SQLite中是rowid的别名,它会自动递增,插入时传NULL就会生成新值,而且这个主键就是数据在B树中的物理存储位置,按主键查询的效率极高。另外要注意NOT NULL、UNIQUE、CHECK这些约束是真实生效的,只有类型本身不严格约束值,所以设计表结构时要靠约束条件而不是靠类型声明来保证数据质量。

增删改查与事务处理实战

日常操作方面SQLite支持绝大多数标准SQL语法,增删改查的写法和MySQL差别不大,熟悉SQL的人几乎可以无缝切换。下面是一组完整的示例:

-- 插入数据
INSERT INTO users (username, balance) VALUES ('zhangsan', 100.5);
INSERT INTO users (username, balance) VALUES ('lisi', 200.0);
-- 查询数据
SELECT id, username, balance FROM users
WHERE balance > 150 ORDER BY balance DESC;
-- 更新数据
UPDATE users SET balance = balance - 50 WHERE username = 'lisi';
-- 删除数据
DELETE FROM users WHERE id = 2;

事务是数据库保证数据一致性的核心手段,SQLite中用BEGIN开启事务,COMMIT提交,ROLLBACK回滚。一个经典的转账场景可以这样写,两条更新语句要么同时成功,要么同时回退:

BEGIN;
UPDATE users SET balance = balance - 100 WHERE username = 'zhangsan';
UPDATE users SET balance = balance + 100 WHERE username = 'lisi';
COMMIT;
-- 如果中途出错,执行 ROLLBACK 即可恢复到转账前的状态

在各类编程语言里操作SQLite也非常方便,以Python为例,标准库自带sqlite3模块,不需要安装任何第三方依赖:

import sqlite3
# 连接数据库,文件不存在会自动创建
conn = sqlite3.connect('app.db')
cursor = conn.cursor()
cursor.execute(
    'INSERT INTO users (username, balance) VALUES (?, ?)',
    ('wangwu', 300.0)
)
conn.commit()          # 提交事务
cursor.close()
conn.close()

注意上面示例里用的是参数化查询,也就是问号占位符的方式传值。千万不要用字符串格式化直接拼接SQL,否则会有SQL注入风险,这一点在任何数据库上都是同样的道理。

并发写入与WAL模式:解锁多读单写

SQLite默认使用回滚日志模式,写入时需要先拿到排它锁,期间其他连接连读操作都会被阻塞,这在读多写少的场景里问题不大,但一旦读写混合就容易出现database is locked报错。解决办法是开启WAL模式,也就是预写日志模式:

-- 开启WAL模式,只需执行一次,设置会持久保存在数据库文件里
PRAGMA journal_mode = WAL;
-- 设置锁等待超时,避免立即报database is locked
PRAGMA busy_timeout = 5000;  -- 单位毫秒,等待5秒

WAL模式下写入操作先追加到独立的wal日志文件,读操作继续读主数据库文件,两者互不干扰,从而实现多个读者和一个写者真正并发工作,写操作之间仍然串行。对绝大多数应用来说,开启WAL再设置一个合理的busy_timeout,就能解决九成的锁冲突问题。需要注意WAL模式下会产生额外的-wal和-shm文件,备份时如果用复制文件的方式,要么先执行PRAGMA wal_checkpoint(TRUNCATE)把日志合并回主文件,要么把这几个文件一起复制走,缺一个都可能导致数据不完整。

常见问题与注意事项汇总

最后把实际使用中高频出现的问题集中说明一遍。首先是日期时间处理,SQLite没有原生的日期类型,惯例是用TEXT存ISO格式的字符串,或者用INTEGER存Unix时间戳,配合内置的datetime()、strftime()等函数做转换和格式化:

-- 当前本地时间
SELECT datetime('now', 'localtime');
-- Unix时间戳转标准时间字符串
SELECT datetime(1705305000, 'unixepoch');
-- 计算某个时间字段距今的天数
SELECT CAST(julianday('now') - julianday(created_at) AS INTEGER) FROM users;

其次还有几个容易踩的细节。LIKE运算符默认对英文字母不区分大小写,但对中文等非ASCII字符是区分的,需要严格控制匹配行为时可以改用GLOB运算符;ALTER TABLE的功能比较有限,早期版本只能改表名和加列,删列、修改列类型需要走建新表再导数据的流程;单条记录不建议存太大的BLOB,几兆以上的大文件更推荐存路径引用而不是直接塞进数据库,否则读写性能和文件体积都会受拖累;另外绝对不要用SQLite去打开来路不明的数据库文件,解析恶意构造的文件存在安全风险,这是官方文档明确警告过的。

总结一下,SQLite小而全、稳而快,理解了单文件架构、动态类型、事务机制和WAL并发模型这四件事,就能把它用得又稳又顺。选型时记住它的边界:本地存储和中小并发场景它是利器,高并发多机写入还是交给服务器型数据库更合适。

广告位 · 文章底部(后台-模块管理替换此区域)