本文档描述了 SQLite 3.6.19 版本(2009-10-14)中引入的 SQL 外键约束支持。
第一部分通过示例介绍了 SQL 外键的概念,并定义了本文档其余部分使用的术语。第二部分描述了应用程序为在 SQLite 中启用外键约束(默认情况下禁用)必须采取的步骤。下一部分,即第三部分,描述了用户必须创建的索引才能使用外键约束,以及为了使外键约束高效运行而应创建的索引。第四部分描述了 SQLite 支持的高级外键相关特性,第五部分描述了ALTER 和 DROP TABLE 命令如何增强以支持外键约束。最后,第六部分列举了当前实现的缺失特性和限制。
本文档不包含 SQLite 中用于创建外键约束的语法的完整描述。这可以在 CREATE TABLE 语句的文档中找到。
SQL 外键约束用于在表之间强制执行“存在”关系。例如,考虑使用以下 SQL 命令创建的数据库模式
CREATE TABLE artist( artistid INTEGER PRIMARY KEY, artistname TEXT ); CREATE TABLE track( trackid INTEGER, trackname TEXT, trackartist INTEGER -- Must map to an artist.artistid! );
使用此数据库的应用程序有权假设,对于 track 表中的每一行,都存在 artist 表中相应的行。毕竟,声明中的注释就是这样说的。不幸的是,如果用户使用外部工具编辑数据库,或者应用程序中存在错误,则可能会将不对应于 artist 表中任何行的行插入到 track 表中。或者,可能会从 artist 表中删除行,从而在 track 表中留下孤立的行,这些行与 artist 表中任何剩余的行都不对应。这可能会导致应用程序或应用程序稍后出现故障,或者至少使应用程序的编码变得更加困难。
一种解决方案是向数据库模式添加 SQL 外键约束以强制执行 artist 和 track 表之间的关系。为此,可以通过修改 track 表的声明来添加外键定义,如下所示
CREATE TABLE track( trackid INTEGER, trackname TEXT, trackartist INTEGER, FOREIGN KEY(trackartist) REFERENCES artist(artistid) );
这样,约束由 SQLite 强制执行。尝试插入不对应于 artist 表中任何行的行到 track 表中将失败,尝试删除 artist 表中存在 track 表中相关行的行也将失败。有一个例外:如果 track 表中的外键列为 NULL,则不需要 artist 表中的相应条目。用 SQL 表示,这意味着对于 track 表中的每一行,以下表达式都计算为真
trackartist IS NULL OR EXISTS(SELECT 1 FROM artist WHERE artistid=trackartist)
提示:如果应用程序需要 artist 和 track 之间更严格的关系,其中 trackartist 列中不允许使用 NULL 值,只需向模式中添加适当的“NOT NULL”约束。
还有几种其他方法可以向 CREATE TABLE 语句添加等效的外键声明。有关详细信息,请参阅 CREATE TABLE 文档。
以下 SQLite 命令行会话说明了添加到 track 表的外键约束的效果
sqlite> SELECT * FROM artist; artistid artistname -------- ----------------- 1 Dean Martin 2 Frank Sinatra sqlite> SELECT * FROM track; trackid trackname trackartist ------- ----------------- ----------- 11 That's Amore 1 12 Christmas Blues 1 13 My Way 2 sqlite> -- This fails because the value inserted into the trackartist column (3) sqlite> -- does not correspond to row in the artist table. sqlite> INSERT INTO track VALUES(14, 'Mr. Bojangles', 3); SQL error: foreign key constraint failed sqlite> -- This succeeds because a NULL is inserted into trackartist. A sqlite> -- corresponding row in the artist table is not required in this case. sqlite> INSERT INTO track VALUES(14, 'Mr. Bojangles', NULL); sqlite> -- Trying to modify the trackartist field of the record after it has sqlite> -- been inserted does not work either, since the new value of trackartist (3) sqlite> -- Still does not correspond to any row in the artist table. sqlite> UPDATE track SET trackartist = 3 WHERE trackname = 'Mr. Bojangles'; SQL error: foreign key constraint failed sqlite> -- Insert the required row into the artist table. It is then possible to sqlite> -- update the inserted row to set trackartist to 3 (since a corresponding sqlite> -- row in the artist table now exists). sqlite> INSERT INTO artist VALUES(3, 'Sammy Davis Jr.'); sqlite> UPDATE track SET trackartist = 3 WHERE trackname = 'Mr. Bojangles'; sqlite> -- Now that "Sammy Davis Jr." (artistid = 3) has been added to the database, sqlite> -- it is possible to INSERT new tracks using this artist without violating sqlite> -- the foreign key constraint: sqlite> INSERT INTO track VALUES(15, 'Boogie Woogie', 3);
正如预期的那样,无法通过删除或更新 artist 表中的行来操作数据库到违反外键约束的状态
sqlite> -- Attempting to delete the artist record for "Frank Sinatra" fails, since sqlite> -- the track table contains a row that refer to it. sqlite> DELETE FROM artist WHERE artistname = 'Frank Sinatra'; SQL error: foreign key constraint failed sqlite> -- Delete all the records from the track table that refer to the artist sqlite> -- "Frank Sinatra". Only then is it possible to delete the artist. sqlite> DELETE FROM track WHERE trackname = 'My Way'; sqlite> DELETE FROM artist WHERE artistname = 'Frank Sinatra'; sqlite> -- Try to update the artistid of a row in the artist table while there sqlite> -- exists records in the track table that refer to it. sqlite> UPDATE artist SET artistid=4 WHERE artistname = 'Dean Martin'; SQL error: foreign key constraint failed sqlite> -- Once all the records that refer to a row in the artist table have sqlite> -- been deleted, it is possible to modify the artistid of the row. sqlite> DELETE FROM track WHERE trackname IN('That''s Amore', 'Christmas Blues'); sqlite> UPDATE artist SET artistid=4 WHERE artistname = 'Dean Martin';
父表是指外键约束引用的表。本节示例中的父表是 artist 表。一些书籍和文章将其称为被引用表,这可能更准确,但往往会导致混淆。
子表是指应用外键约束的表,以及包含 REFERENCES 子句的表。本节中的示例使用 track 表作为子表。其他书籍和文章将其称为引用表。
父键是指父表中外键约束引用的列或列集。这通常(但不总是)是父表的主键。父键必须是父表中命名的列或列,而不是 rowid。
子键是指子表中受外键约束约束并包含 REFERENCES 子句的列或列集。
如果子表中的每一行中,子键列中的一个或多个为 NULL,或者父表中存在一行,其中每个父键列包含的值等于其关联的子键列中的值,则外键约束得到满足。
在上段中,“等于”表示使用此处指定的规则比较值时相等。以下说明适用
为了在 SQLite 中使用外键约束,库必须编译时既未定义 SQLITE_OMIT_FOREIGN_KEY 也未定义 SQLITE_OMIT_TRIGGER。如果定义了 SQLITE_OMIT_TRIGGER 但未定义 SQLITE_OMIT_FOREIGN_KEY,则 SQLite 的行为与 3.6.19 版本(2009-10-14)之前相同 - 外键定义会被解析,并且可以使用 PRAGMA foreign_key_list 查询,但不会强制执行外键约束。在此配置中,PRAGMA foreign_keys 命令是无操作的。如果定义了 OMIT_FOREIGN_KEY,则甚至无法解析外键定义(尝试指定外键定义是语法错误)。
假设库已编译并启用了外键约束,则仍然必须在运行时使用 PRAGMA foreign_keys 命令由应用程序启用。例如
sqlite> PRAGMA foreign_keys = ON;
外键约束默认情况下处于禁用状态(为了向后兼容),因此必须为每个 数据库连接单独启用。(但是,请注意,SQLite 的未来版本可能会更改,以便默认启用外键约束。谨慎的开发人员不会对默认情况下是否启用了外键做出任何假设,而是会根据需要启用或禁用它们。)应用程序还可以使用 PRAGMA foreign_keys 语句来确定外键当前是否已启用。以下命令行会话演示了这一点
sqlite> PRAGMA foreign_keys; 0 sqlite> PRAGMA foreign_keys = ON; sqlite> PRAGMA foreign_keys; 1 sqlite> PRAGMA foreign_keys = OFF; sqlite> PRAGMA foreign_keys; 0
提示:如果命令“PRAGMA foreign_keys”返回无数据而不是包含“0”或“1”的单行,则表示您使用的 SQLite 版本不支持外键(因为版本早于 3.6.19 或因为它是使用 SQLITE_OMIT_FOREIGN_KEY 或 SQLITE_OMIT_TRIGGER 定义编译的)。
无法在 多语句事务(当 SQLite 不处于 自动提交模式时)的中间启用或禁用外键约束。尝试这样做不会返回错误;它只是没有任何效果。
通常,外键约束的父键是父表的主键。如果它们不是主键,则父键列必须共同受 UNIQUE 约束约束或具有 UNIQUE 索引。如果父键列具有 UNIQUE 索引,则该索引必须使用 CREATE TABLE 语句中为父表指定的排序规则。例如,
CREATE TABLE parent(a PRIMARY KEY, b UNIQUE, c, d, e, f); CREATE UNIQUE INDEX i1 ON parent(c, d); CREATE INDEX i2 ON parent(e); CREATE UNIQUE INDEX i3 ON parent(f COLLATE nocase); CREATE TABLE child1(f, g REFERENCES parent(a)); -- Ok CREATE TABLE child2(h, i REFERENCES parent(b)); -- Ok CREATE TABLE child3(j, k, FOREIGN KEY(j, k) REFERENCES parent(c, d)); -- Ok CREATE TABLE child4(l, m REFERENCES parent(e)); -- Error! CREATE TABLE child5(n, o REFERENCES parent(f)); -- Error! CREATE TABLE child6(p, q, FOREIGN KEY(p, q) REFERENCES parent(b, c)); -- Error! CREATE TABLE child7(r REFERENCES parent(c)); -- Error!
作为 child1、child2 和 child3 表的一部分创建的外键约束都很好。作为 child4 表的一部分声明的外键是一个错误,因为即使父键列已编制索引,该索引也不是 UNIQUE。child5 表的外键是一个错误,因为即使父键列具有唯一索引,该索引也使用不同的排序规则。child6 和 child7 表不正确,因为虽然两者在其父键上都有 UNIQUE 索引,但这些键与单个 UNIQUE 索引的列不完全匹配。
如果数据库模式包含需要查看多个表定义才能识别的外键错误,则在创建表时不会检测到这些错误。相反,此类错误会阻止应用程序准备以使用外键的方式修改子表或父表内容的 SQL 语句。在更改内容时报告的错误是“DML 错误”,在更改模式时报告的错误是“DDL 错误”。因此,换句话说,需要查看子表和父表才能识别的配置错误的外键约束是 DML 错误。外键 DML 错误的英文错误消息通常是“外键不匹配”,但如果父表不存在,也可能是“表不存在”。如果以下情况发生,则会报告外键 DML 错误
上面最后一个要点由以下内容说明
CREATE TABLE parent2(a, b, PRIMARY KEY(a,b)); CREATE TABLE child8(x, y, FOREIGN KEY(x,y) REFERENCES parent2); -- Ok CREATE TABLE child9(x REFERENCES parent2); -- Error! CREATE TABLE child10(x,y,z, FOREIGN KEY(x,y,z) REFERENCES parent2); -- Error!
相反,如果只需查看子表的定义而无需查阅父表定义即可识别外键错误,则子表的 CREATE TABLE 语句将失败。由于错误发生在模式更改期间,因此这是一个 DDL 错误。无论创建表时是否启用了外键约束,都会报告外键 DDL 错误。
子键列不需要索引,但几乎总是很有益的。回到 第 1 部分中的示例,每次应用程序从 artist 表(父表)中删除一行时,它都会执行等效于以下 SELECT 语句以搜索 track 表(子表)中的引用行。
SELECT rowid FROM track WHERE trackartist = ?
其中上述中的“?”将被替换为要从artist表中删除的记录的artistid列的值(回想一下,trackartist列是子键,而artistid列是父键)。或者,更一般地说
SELECT rowid FROM <child-table> WHERE <child-key> = :parent_key_value
如果此SELECT返回任何行,则SQLite得出结论,从父表中删除该行将违反外键约束,并返回错误。如果修改父键的内容或将新行插入父表,则可能会运行类似的查询。如果这些查询无法使用索引,则它们将被迫对整个子表进行线性扫描。在非平凡的数据库中,这可能非常昂贵。
因此,在大多数实际系统中,应在每个外键约束的子键列上创建索引。子键索引不必(通常也不会)是唯一索引。再次回到第1节中的示例,用于有效实现外键约束的完整数据库模式可能是
CREATE TABLE artist( artistid INTEGER PRIMARY KEY, artistname TEXT ); CREATE TABLE track( trackid INTEGER, trackname TEXT, trackartist INTEGER REFERENCES artist ); CREATE INDEX trackindex ON track(trackartist);
上面的代码块使用简写形式创建外键约束。将“REFERENCES <parent-table>”子句附加到列定义会创建一个外键约束,该约束将列映射到<parent-table>的主键。有关更多详细信息,请参阅CREATE TABLE文档。
复合外键约束是指子键和父键都是复合键的情况。例如,考虑以下数据库模式
CREATE TABLE album( albumartist TEXT, albumname TEXT, albumcover BINARY, PRIMARY KEY(albumartist, albumname) ); CREATE TABLE song( songid INTEGER, songartist TEXT, songalbum TEXT, songname TEXT, FOREIGN KEY(songartist, songalbum) REFERENCES album(albumartist, albumname) );
在此系统中,song表中的每个条目都必须映射到album表中具有相同艺术家和专辑组合的条目。
父键和子键必须具有相同的基数。在SQLite中,如果任何子键列(在本例中为songartist和songalbum)为NULL,则不需要在父表中存在相应的行。
SQLite中的每个外键约束都被分类为立即约束或延迟约束。默认情况下,外键约束为立即约束。到目前为止,所有展示的外键示例都是立即外键约束。
如果语句修改数据库内容,使得语句结束时违反了立即外键约束,则会抛出异常,并且语句的效果将被回滚。相反,如果语句修改数据库内容使得违反了延迟外键约束,则不会立即报告违规情况。延迟外键约束直到事务尝试COMMIT时才会被检查。只要用户拥有一个打开的事务,数据库就可以处于违反任意数量的延迟外键约束的状态。但是,只要外键约束仍然处于违反状态,COMMIT就会失败。
如果当前语句不在显式事务(BEGIN/COMMIT/ROLLBACK块)内,则语句执行完毕后立即提交隐式事务。在这种情况下,延迟约束的行为与立即约束相同。
要将外键约束标记为延迟,其声明必须包含以下子句
DEFERRABLE INITIALLY DEFERRED -- A deferred foreign key constraint
指定外键约束的完整语法可在CREATE TABLE文档中找到。将上面的短语替换为以下任何内容都会创建一个立即外键约束。
NOT DEFERRABLE INITIALLY DEFERRED -- An immediate foreign key constraint NOT DEFERRABLE INITIALLY IMMEDIATE -- An immediate foreign key constraint NOT DEFERRABLE -- An immediate foreign key constraint DEFERRABLE INITIALLY IMMEDIATE -- An immediate foreign key constraint DEFERRABLE -- An immediate foreign key constraint
defer_foreign_keys pragma可用于暂时将所有外键约束更改为延迟,而不管它们是如何声明的。
以下示例说明了使用延迟外键约束的效果。
-- Database schema. Both tables are initially empty. CREATE TABLE artist( artistid INTEGER PRIMARY KEY, artistname TEXT ); CREATE TABLE track( trackid INTEGER, trackname TEXT, trackartist INTEGER REFERENCES artist(artistid) DEFERRABLE INITIALLY DEFERRED ); sqlite3> -- If the foreign key constraint were immediate, this INSERT would sqlite3> -- cause an error (since as there is no row in table artist with sqlite3> -- artistid=5). But as the constraint is deferred and there is an sqlite3> -- open transaction, no error occurs. sqlite3> BEGIN; sqlite3> INSERT INTO track VALUES(1, 'White Christmas', 5); sqlite3> -- The following COMMIT fails, as the database is in a state that sqlite3> -- does not satisfy the deferred foreign key constraint. The sqlite3> -- transaction remains open. sqlite3> COMMIT; SQL error: foreign key constraint failed sqlite3> -- After inserting a row into the artist table with artistid=5, the sqlite3> -- deferred foreign key constraint is satisfied. It is then possible sqlite3> -- to commit the transaction without error. sqlite3> INSERT INTO artist VALUES(5, 'Bing Crosby'); sqlite3> COMMIT;
当数据库处于不满足延迟外键约束的状态时,可以释放嵌套保存点事务。另一方面,事务保存点(在当前没有打开的事务时打开的非嵌套保存点)受与COMMIT相同的限制 - 尝试在数据库处于这种状态时释放它将失败。
如果COMMIT语句(或事务SAVEPOINT的RELEASE)失败,因为数据库当前处于违反延迟外键约束的状态,并且当前存在嵌套保存点,则嵌套保存点将保持打开状态。
外键ON DELETE和ON UPDATE子句用于配置在从父表中删除行(ON DELETE)或修改现有行的父键值(ON UPDATE)时发生的的操作。单个外键约束可以为ON DELETE和ON UPDATE配置不同的操作。外键操作在许多方面类似于触发器。
SQLite数据库中每个外键关联的ON DELETE和ON UPDATE操作之一是“NO ACTION”、“RESTRICT”、“SET NULL”、“SET DEFAULT”或“CASCADE”。如果未显式指定操作,则默认为“NO ACTION”。
NO ACTION:配置“NO ACTION”表示字面意思:当从数据库中修改或删除父键时,不会采取任何特殊操作。
RESTRICT: “RESTRICT”操作表示应用程序被禁止删除(对于ON DELETE RESTRICT)或修改(对于ON UPDATE RESTRICT)存在一个或多个子键映射到的父键。RESTRICT操作的效果与正常外键约束执行之间的区别在于,RESTRICT操作处理会在字段更新后立即发生 - 而不是像立即约束那样在当前语句结束时,或者像延迟约束那样在当前事务结束时。即使附加了它的外键约束被延迟,配置RESTRICT操作也会导致SQLite立即返回错误,如果删除或修改具有依赖子键的父键。
SET NULL:如果配置的操作为“SET NULL”,则当删除父键(对于ON DELETE SET NULL)或修改父键(对于ON UPDATE SET NULL)时,子表中所有映射到父键的行(的)子键列将设置为包含SQL NULL值。
SET DEFAULT: “SET DEFAULT”操作类似于“SET NULL”,除了每个子键列都设置为包含列的默认值而不是NULL。有关如何将默认值分配给表列的详细信息,请参阅CREATE TABLE文档。
CASCADE: “CASCADE”操作将父键上的删除或更新操作传播到每个依赖的子键。对于“ON DELETE CASCADE”操作,这意味着与已删除的父行关联的子表中的每一行也将被删除。对于“ON UPDATE CASCADE”操作,这意味着存储在每个依赖子键中的值将被修改以匹配新的父键值。
例如,将“ON UPDATE CASCADE”子句添加到外键(如下所示)增强了第1节中的示例模式,允许用户更新artistid(外键约束的父键)列,而不会破坏引用完整性
-- Database schema CREATE TABLE artist( artistid INTEGER PRIMARY KEY, artistname TEXT ); CREATE TABLE track( trackid INTEGER, trackname TEXT, trackartist INTEGER REFERENCES artist(artistid) ON UPDATE CASCADE ); sqlite> SELECT * FROM artist; artistid artistname -------- ----------------- 1 Dean Martin 2 Frank Sinatra sqlite> SELECT * FROM track; trackid trackname trackartist ------- ----------------- ----------- 11 That's Amore 1 12 Christmas Blues 1 13 My Way 2 sqlite> -- Update the artistid column of the artist record for "Dean Martin". sqlite> -- Normally, this would raise a constraint, as it would orphan the two sqlite> -- dependent records in the track table. However, the ON UPDATE CASCADE clause sqlite> -- attached to the foreign key definition causes the update to "cascade" sqlite> -- to the child table, preventing the foreign key constraint violation. sqlite> UPDATE artist SET artistid = 100 WHERE artistname = 'Dean Martin'; sqlite> SELECT * FROM artist; artistid artistname -------- ----------------- 2 Frank Sinatra 100 Dean Martin sqlite> SELECT * FROM track; trackid trackname trackartist ------- ----------------- ----------- 11 That's Amore 100 12 Christmas Blues 100 13 My Way 2
配置ON UPDATE或ON DELETE操作并不意味着不需要满足外键约束。例如,如果配置了“ON DELETE SET DEFAULT”操作,但父表中没有与子键列的默认值对应的行,则在存在依赖子键时删除父键仍会导致外键违规。例如
-- Database schema CREATE TABLE artist( artistid INTEGER PRIMARY KEY, artistname TEXT ); CREATE TABLE track( trackid INTEGER, trackname TEXT, trackartist INTEGER DEFAULT 0 REFERENCES artist(artistid) ON DELETE SET DEFAULT ); sqlite> SELECT * FROM artist; artistid artistname -------- ----------------- 3 Sammy Davis Jr. sqlite> SELECT * FROM track; trackid trackname trackartist ------- ----------------- ----------- 14 Mr. Bojangles 3 sqlite> -- Deleting the row from the parent table causes the child key sqlite> -- value of the dependent row to be set to integer value 0. However, this sqlite> -- value does not correspond to any row in the parent table. Therefore sqlite> -- the foreign key constraint is violated and an is exception thrown. sqlite> DELETE FROM artist WHERE artistname = 'Sammy Davis Jr.'; SQL error: foreign key constraint failed sqlite> -- This time, the value 0 does correspond to a parent table row. And sqlite> -- so the DELETE statement does not violate the foreign key constraint sqlite> -- and no exception is thrown. sqlite> INSERT INTO artist VALUES(0, 'Unknown Artist'); sqlite> DELETE FROM artist WHERE artistname = 'Sammy Davis Jr.'; sqlite> SELECT * FROM artist; artistid artistname -------- ----------------- 0 Unknown Artist sqlite> SELECT * FROM track; trackid trackname trackartist ------- ----------------- ----------- 14 Mr. Bojangles 0
熟悉SQLite触发器的人会注意到,上面示例中演示的“ON DELETE SET DEFAULT”操作在效果上类似于以下AFTER DELETE触发器
CREATE TRIGGER on_delete_set_default AFTER DELETE ON artist BEGIN UPDATE child SET trackartist = 0 WHERE trackartist = old.artistid; END;
每当删除外键约束的父表中的行,或修改父键列或列中存储的值时,事件的逻辑顺序为
ON UPDATE外键操作和SQL触发器之间存在一个重要区别。仅当父键的值被修改,使得新的父键值不等于旧值时,才会执行ON UPDATE操作。例如
-- Database schema CREATE TABLE parent(x PRIMARY KEY); CREATE TABLE child(y REFERENCES parent ON UPDATE SET NULL); sqlite> SELECT * FROM parent; x ---- key sqlite> SELECT * FROM child; y ---- key sqlite> -- Since the following UPDATE statement does not actually modify sqlite> -- the parent key value, the ON UPDATE action is not performed and sqlite> -- the child key value is not set to NULL. sqlite> UPDATE parent SET x = 'key'; sqlite> SELECT IFNULL(y, 'null') FROM child; y ---- key sqlite> -- This time, since the UPDATE statement does modify the parent key sqlite> -- value, the ON UPDATE action is performed and the child key is set sqlite> -- to NULL. sqlite> UPDATE parent SET x = 'key2'; sqlite> SELECT IFNULL(y, 'null') FROM child; y ---- null
本节描述CREATE TABLE、ALTER TABLE和DROP TABLE命令与SQLite外键的交互方式。
CREATE TABLE命令的操作方式无论外键约束是否启用都相同。创建表时不会检查外键约束的父键定义。没有任何东西可以阻止用户创建引用不存在的父表、不存在的父键列或未由PRIMARY KEY或UNIQUE约束集体绑定的父键列的外键定义。
启用外键约束时,ALTER TABLE命令在两个方面的工作方式有所不同
无法使用“ALTER TABLE ... ADD COLUMN”语法添加包含REFERENCES子句的列,除非新列的默认值为NULL。尝试这样做会返回错误。
如果使用“ALTER TABLE ... RENAME TO”命令重命名一个或多个外键约束的父表,则外键约束的定义将被修改为通过其新名称引用父表。存储在sqlite_schema表中的子CREATE TABLE语句或语句的文本将被修改以反映新的父表名称。
如果在准备时启用了外键约束,则DROP TABLE命令将在删除表之前执行隐式DELETE以删除表中的所有行。隐式DELETE不会导致任何SQL触发器触发,但可能会调用外键操作或约束违规。如果违反了立即外键约束,则DROP TABLE语句将失败,并且不会删除该表。如果违反了延迟外键约束,则当用户尝试提交事务时,如果此时仍然存在外键约束违规,则会报告错误。在隐式DELETE中遇到的任何“外键不匹配”错误都会被忽略。
这些对 ALTER TABLE 和 DROP TABLE 命令的增强旨在确保它们不能用于创建包含外键违规的数据库,至少在启用外键约束时是这样。不过,此规则有一个例外。如果父键不受作为父表定义的一部分创建的 PRIMARY KEY 或 UNIQUE 约束的约束,但由于使用 CREATE INDEX 命令创建的索引而受 UNIQUE 约束的约束,则可以填充子表而不会导致“外键不匹配”错误。如果从数据库模式中删除 UNIQUE 索引,然后删除父表本身,则不会报告错误。但是,数据库可能会处于子表的外键约束包含不引用任何父表行的行。如果数据库模式中的所有父键都受作为父表定义的一部分添加的 PRIMARY KEY 或 UNIQUE 约束的约束,而不是受外部 UNIQUE 索引的约束,则可以避免这种情况。
上面描述的 DROP TABLE 和 ALTER TABLE 命令的属性仅在启用外键时才适用。如果用户认为它们不可取,则解决方法是在执行 DROP 或 ALTER TABLE 命令之前使用 PRAGMA foreign_keys 禁用外键约束。当然,在禁用外键约束时,没有任何东西可以阻止用户违反外键约束,从而创建内部不一致的数据库。
本节列出了一些在其他地方未提及的限制和省略的功能。
不支持 MATCH 子句。根据 SQL92,可以将 MATCH 子句附加到复合外键定义以修改处理子键中出现的 NULL 值的方式。如果指定了“MATCH SIMPLE”,则如果一个或多个子键值为 NULL,则子键不需要对应于父表的任何行。如果指定了“MATCH FULL”,则如果任何子键值为 NULL,则不需要父表中的任何对应行,但所有子键值都必须为 NULL。最后,如果外键约束声明为“MATCH PARTIAL”并且其中一个子键值为 NULL,则父表中必须至少存在一行,其中非 NULL 子键值与父键值匹配。
SQLite 解析 MATCH 子句(即,如果指定了 MATCH 子句,则不会报告语法错误),但不会强制执行它们。SQLite 中的所有外键约束都按指定 MATCH SIMPLE 的方式处理。
不支持在延迟模式和立即模式之间切换约束。许多系统允许用户在运行时在延迟模式和立即模式之间切换各个外键约束(例如,使用 Oracle 的“SET CONSTRAINT”命令)。SQLite 不支持此功能。在 SQLite 中,外键约束在创建时会永久标记为延迟或立即。
外键操作上的递归限制。SQLITE_MAX_TRIGGER_DEPTH 和 SQLITE_LIMIT_TRIGGER_DEPTH 设置确定触发器程序递归的最大允许深度。出于这些限制的目的,外键操作 被视为触发器程序。 PRAGMA recursive_triggers 设置不影响外键操作。无法禁用递归外键操作。
外键不得跨越模式边界。也就是说,在 REFERENCES (X.Y)
中,表 X
仅在包含 REFERENCES
子句的模式内解析。