SQLite的数据类型设计在关系型数据库中非常独特。它不像MySQL那样严格校验列类型,而是采用动态类型加类型亲和性的机制。也就是说,你在建表语句里写的类型并不是强约束,真正决定数据在磁盘上如何存储的是存储类(Storage Class)。理解这套机制,是用好SQLite的第一步,也是避免出现"查不出数据"这类诡异问题的前提。

一、SQLite的五种存储类
SQLite内部只有五种存储类,任何值在存储时都必然属于其中一类。第一种是NULL,表示空值,不占用实际数据空间。第二种是INTEGER,带符号整数,根据数值大小动态使用1、2、3、4、6或8个字节存储,这是SQLite节省空间的典型设计——存一个100以内的数字只占1字节,而不是固定8字节。
第三种是REAL,8字节的IEEE浮点数。第四种是TEXT,字符串,默认以UTF-8编码,也可以在建库时指定UTF-16le或UTF-16be。第五种是BLOB,二进制大对象,数据按输入时的原始字节存储,不做任何转换。可以用内置函数typeof()查看某个值实际所属的存储类:
-- 查看不同值的存储类
SELECT typeof(100); -- 返回 integer
SELECT typeof(100.0); -- 返回 real
SELECT typeof('100'); -- 返回 text
SELECT typeof(x'414243'); -- 返回 blob
SELECT typeof(NULL); -- 返回 null
需要注意的是,INTEGER和REAL是严格区分的。100和100.0在SQLite里是两个不同存储类的值,虽然比较时会认为相等,但排序和索引行为可能有细微差别。
二、类型亲和性:声明类型如何影响实际存储
既然实际存储由存储类决定,那建表时的类型声明有什么用?答案是类型亲和性(Type Affinity)。每张表的每一列都有一个亲和性,它决定了数据插入时倾向被转换成哪种存储类。比如某列的亲和性是INTEGER,你插入字符串"123",SQLite会尝试把它转成整数123再存。
亲和性的判定基于声明类型的字符串匹配,规则按优先级依次是:如果声明类型包含"INT",亲和性为INTEGER;包含"CHAR"、"CLOB"或"TEXT",亲和性为TEXT;包含"BLOB"或类型为空,亲和性为BLOB(也叫NONE);包含"REAL"、"FLOA"或"DOUB",亲和性为REAL;其余情况(如包含"DATE"、"BOOL"等)默认为NUMERIC。看下面的例子:
CREATE TABLE demo (
a INT, -- INTEGER亲和性
b VARCHAR(20),-- TEXT亲和性
c BLOB, -- BLOB亲和性,存什么就是什么
d DATE, -- NUMERIC亲和性
e BOOLEAN -- NUMERIC亲和性
);
-- c列存入字符串,会原样保留为text
INSERT INTO demo VALUES ('12', '34', '56', '2024-01-01', 'true');
SELECT typeof(a), typeof(b), typeof(c), typeof(d), typeof(e) FROM demo;
-- 结果: integer | text | text | text | text
-- 注意a被转换成了整数,d和e的字符串无法无损转数字,保持text
SQLite没有内置的BOOLEAN和DATE类型。TRUE和FALSE关键字只是整数1和0的别名,日期时间通常以TEXT(ISO8601字符串)、REAL(儒略日)或INTEGER(Unix时间戳)三种方式存储,配合date()、strftime()等函数处理。
三、INTEGER主键与ROWID的优化机制
几乎所有SQLite表都有一个隐藏的64位整数行标识ROWID。如果某列声明为INTEGER PRIMARY KEY,该列就成为ROWID的别名,这是SQLite中唯一接近"强类型"的情况——这一列只能存整数,且插入NULL时会自动取未使用的最大值加一,实现自增效果,不需要写AUTOINCREMENT关键字。
CREATE TABLE users (
id INTEGER PRIMARY KEY, -- 即ROWID别名,自动自增
name TEXT NOT NULL,
age INTEGER
);
INSERT INTO users (name, age) VALUES ('张三', 28);
SELECT rowid, id FROM users; -- 两列值相同
这个设计的性能意义重大:INTEGER PRIMARY KEY上的查找直接定位B树记录,是最快的访问路径。而表如果设置了WITHOUT ROWID选项,则没有隐藏行号,数据按主键组织,适合主键较长且几乎总是按主键查询的场景,能节省一整棵索引树的空间。
四、不同存储场景下的类型选择建议
存储普通业务数据时,遵循"声明类型贴近亲和性规则"的原则即可。用户名、邮箱、地址这类字符串用TEXT;数量、状态码、年龄用INTEGER;金额要特别注意——REAL存在浮点精度问题,0.1 + 0.2不等于0.3,涉及金钱计算建议以"分"为单位存INTEGER,或存TEXT后用程序层的高精度类型解析。
存储时间戳时,推荐用INTEGER存Unix时间戳,占4或8字节且比较排序快;如果需要人类可读且直接使用日期函数,用TEXT存ISO8601格式如"2024-01-01 12:30:00"。两者的索引范围查询都很高效,混合使用则是大忌,同一列里既有字符串日期又有整数时间戳,会让查询条件无法命中索引。
存储图片、加密数据、序列化对象等二进制内容用BLOB。BLOB不做编码转换,读写原样进原样出,适合存缩略图、证书、protobuf数据。但要注意单条BLOB不宜过大,默认建议控制在百万字节以内,超大文件更适合存文件系统路径,数据库里只存引用。
五、常见陷阱与排查方法
最经典的坑是隐式转换导致查询失配。假如某列是TEXT亲和性,你用整数条件去查,索引可能失效或结果不符合预期。排查时优先使用typeof()确认数据实际存储类,用EXPLAIN QUERY PLAN看是否走了全表扫描。另外,拼接SQL时传参类型要和列亲和性匹配,建议始终使用参数绑定而不是字符串拼接,让驱动层正确处理类型转换。
总结来说,SQLite的类型系统看似宽松,实则有一套清晰的规则。记住五存储类、理解亲和性判定、善用INTEGER主键优化、按场景匹配TEXT与INTEGER的时间存储方式,就能既享受SQLite轻量灵活的优势,又避开动态类型埋下的坑。
SQLite数据类型存储类类型亲和性修改时间:2026-09-01 04:22:30