主键

与标准SQL规范类似,SQLite的主键可以定义一列或多个列。在声明主键的时候,只能限定列名,而无法限定一个表达式。在SQLite中,声明索引的时候可以限定一个表达式。 声明主键示例:

CREATE TABLE test (
    a PRIMARY KEY
);

详细语法:
primaray key syntax

indexed-column的语法:(注意:在主键中不支持expr语法)

indexed column syntax

conflict-clause语法:

conflict clause syntax

主键默认包含unique约束, conflict-clause用于指定出现 unique约束冲突时该怎么办,默认是ABORT。

另需额外注意,与标准SQL不同的是,SQLite可以给主键设置NULL值,且NULL与NULL之间是不等的。即当a列为主键时,以下语句可以执行多次:

INSERT INTO test (a) VALUES ( null );

这是SQLite的一个历史遗留BUG,且已声明为了向后兼容不会修复这个BUG。如果需要限定主键不能设置为NULL,需要显示声明,如下:

CREATE TABLE test (
    a PRIMARY KEY
        NOT NULL
);

ROWID

默认情况下,SQLite中的每个表都有一个rowid列。可以通过如下声明创建一个没有rowid列的表:

CREATE TABLE test (
    a PRIMARY KEY
) WITHOUT ROWID;

rowid列是一个自增长的有符号64位整型的列。且有多个别名,可通过rowid、oid、rowid访问,且不区分大小写。如果在创建表时声明了与这三个名称同名的列,则该名称指向的就是显示声明的列,不再指向rowid。

例如:

CREATE TABLE test ( oid text );
INSERT INTO test VALUES ('a');
SELECT rowid, oid FROM test;
-- 输出: 1, "a"

rowid列的自增长逻辑是:当前rowid列的最大值加1,如果最大值加1超出了所允许的最大值,就会寻找未使用的值。

rowid列可以通过INSERT或UPDATE语句显示的设置,此时必须传入可以正确转换为整型的值,如果传入值无法正确转换为整型则报错。例如:

INSERT INTO test ( a,rowid ) VALUES ('a', '6.6'); --报错: datatype mismatch
INSERT INTO test ( a,rowid ) VALUES ('a', '6');  -- ✅

ROWID和主键

如果为某个单列声明主键,且该列的数据类型为INTEGER(INTEGER不区分大小写),则在SQLite内部,将该列名作为一个新的rowid别名,和oid、rowid类似。例如:

CREATE TABLE t(x integer PRIMARY KEY);

但声明的数据类型必须严格是INTEGER。如果是INT、BIGINT 、SHORT INTEGER等就不行。如果不是严格等于INTEGER的话,就是默认行为,即当做Affinity类型对待。

有默认rowid列的表,我们一般称之为rowid表。rowid表在SQLite内部是B-Tree数据结构,以rowid作为key。所以在查询数据的时候,通过rowid查询比通过其他索引或主键查询更快,具体快多少我个人没有测,官方文档说是快2倍。

另外要注意,rowid列不能被外键引用。外键必须引用的是显示声明的列。