SQLite和MySQL、PostgreSQL这类传统数据库在类型系统上差别相当大。它没有严格意义上的数据类型校验,往INTEGER字段里插入字符串,大多数情况下不会报错,数据照样能存进去。这套机制叫类型亲和性(Type Affinity),理解它是用好SQLite的前提。本文从存储类、亲和性规则、常见误区到实际选型,把SQLite的类型系统完整梳理一遍。

五种存储类:SQLite数据的真实形态
SQLite内部只有五种存储类(Storage Class):NULL、INTEGER、REAL、TEXT和BLOB。这是数据落盘时的真实形态,无论你建表时写了什么类型,最终都会归到这五类里。
INTEGER用于存储有符号整数,根据数值大小SQLite会自动选择1、2、3、4、6或8字节存储,比如存1只占一个字节,存一个很大的数才会用到8字节,这个设计让小整数的存储非常省空间。REAL是8字节的IEEE浮点数。TEXT就是字符串,采用数据库编码(UTF-8或UTF-16)存储。BLOB存储原始二进制数据,图片、序列化后的字节流都可以放进去,SQLite不会对它做任何解释。
有一个细节值得注意:SQLite没有专门的布尔类型和日期类型。true和false分别对应整数1和0,日期时间一般用TEXT(ISO8601格式字符串)、REAL(儒略日)或INTEGER(Unix时间戳)来存。另外,参数绑定传入的整数如果发生溢出,SQLite 3.x在某些场景下会自动把它转成REAL存储,这一点在处理超大ID时需要留意。
类型亲和性:字段类型的真正含义
建表时声明的类型(比如VARCHAR(255)、DOUBLE、BOOLEAN)在SQLite里只是用来推断亲和性的线索。规则按优先级依次匹配:如果声明类型中包含INT,该字段就是INTEGER亲和性;包含CHAR、CLOB或TEXT,就是TEXT亲和性;包含BLOB或没有声明类型,就是BLOB亲和性;包含REAL、FLOA或DOUB,就是REAL亲和性;其余情况(包括包含NUM、DEC等)都是NUMERIC亲和性。
-- 下面几个字段全是 INTEGER 亲和性
CREATE TABLE t1 (
a INT,
b INTEGER,
c BIGINT,
d UNSIGNED BIG INT
);
-- 下面几个字段全是 TEXT 亲和性
CREATE TABLE t2 (
x VARCHAR(100),
y CHAR(10),
z NVARCHAR(255)
);
-- NUMERIC 亲和性:DECIMAL、NUMERIC、BOOLEAN、DATE 都属于这类
CREATE TABLE t3 (
price DECIMAL(10,2),
flag BOOLEAN,
create_time DATETIME
);亲和性的作用体现在写入和读取时。往TEXT亲和性字段里插入整数3,SQLite会先把3转成字符串'3'再存储;往INTEGER或NUMERIC亲和性字段里插入字符串'3',如果能无损转换,就会存成整数3。注意这里的关键词是"无损":字符串'3.5'插入INTEGER字段时,转成整数3会丢失小数部分,所以会原样按TEXT存储。
读取时的行为同样受亲和性影响。做比较运算时,不同存储类的大小顺序是固定的:NULL小于所有值,其次是INTEGER和REAL(数值之间正常比较),然后是TEXT,BLOB最大。这就是为什么SELECT * FROM t WHERE num > 5可能查出意料之外的结果——如果num字段里混存了字符串和数字,比较规则会和直觉不同。
NUMERIC与INTEGER的区别及常见误区
NUMERIC亲和性和INTEGER亲和性很像,但有一个重要差别:NUMERIC字段存浮点数时,如果这个浮点数能无损转成整数,就会以INTEGER形式落盘。比如插入3.0,实际存储的是整数3;插入3.5才会按REAL存储。而INTEGER亲和性字段遇到3.5时无法无损转换,会直接按REAL存3.5。所以在SQLite里,INTEGER字段里出现浮点数是完全合法的,这一点和大多数数据库都不同。
这个特性带来一个经典误区:DECIMAL字段以为能保证精度。SQLite的DECIMAL(10,2)只是NUMERIC亲和性,底层依然是浮点数或整数,不存在定点数运算。涉及金额计算时,如果直接用REAL存储,0.1加0.2不等于0.3的浮点误差照样会出现。稳妥的做法是把金额按分存成INTEGER,或者存成TEXT后用程序侧的Decimal类型处理。
-- 金额存储的推荐做法:以分为单位存整数
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
amount_cents INTEGER NOT NULL, -- 899 表示 8.99 元
created_at INTEGER NOT NULL -- Unix 时间戳
);
-- 验证 NUMERIC 亲和性的转换行为
CREATE TABLE test_num (
v NUMERIC
);
INSERT INTO test_num VALUES ('3.0'), ('3.5'), (3.0), ('abc');
SELECT v, typeof(v) FROM test_num;
-- 3, integer
-- 3.5, real
-- 3, integer
-- abc, text另一个误区是BOOLEAN。SQLite接受TRUE和FALSE关键字,但它们会被直接替换成1和0。查询时WHERE flag = 1和WHERE flag = TRUE等价,但字段里其实可以存任何值,数据库不会阻止你往BOOLEAN字段里插一个字符串'yes',逻辑判断时只有0和NULL算假,其余全为真,包括字符串'0'。
建表时的类型选择建议
结合前面的原理,给出几条实用建议。第一,主键直接用INTEGER PRIMARY KEY,它会成为表的rowid别名,插入NULL时自动分配自增值,性能和空间占用都最优。如果用TEXT做主键,需要额外建索引,写入成本更高。
第二,能预估为数值的数据就用INTEGER或REAL,别用TEXT存数字。TEXT存数字不仅比较时容易出错('10'小于'9'的字符串比较问题),还无法利用数值索引,聚合函数的结果也不对。日期时间推荐用TEXT存ISO8601格式(如'2024-01-15T10:30:00'),这样可以直接用字符串比较实现时间范围查询,也可以用内置的date()、strftime()函数处理。
第三,如果团队从其他数据库迁移过来,希望有强类型约束,可以在建表时启用严格模式。SQLite 3.37之后支持STRICT表,声明的类型必须是五种基础类型之一,插入类型不匹配的数据会直接报错,行为和传统数据库一致。
-- 严格模式建表,类型不匹配会直接报错
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER,
score REAL,
avatar BLOB
) STRICT;
-- 往 age 里插入 'abc' 会报错:
-- INSERT INTO users(name, age) VALUES ('tom', 'abc');
-- Runtime error: cannot store TEXT value in INTEGER column users.age最后提醒一点:即使理解了亲和性机制,也不要在一个字段里混存不同类型的数据。虽然SQLite允许这么做,但会让查询语义变得不可预测,索引也可能失效。类型系统给了你灵活,但克制地使用灵活才能让表结构长期可维护。
SQLite数据类型SQLite类型亲和性SQLite建表修改时间:2026-09-11 12:22:40