当自动提交未开启时,无法强制执行外键约束
我正在用数据库构建一个小程序,发现SQLite应该很适合我的需求。 我开始用一些外键设计数据库模式,初步测试显示应当在约束被强制执行时发出 PRAGMA foreign_keys=on。
在阅读有关Python sqlite3 模块的文档时,我看到推荐的打开连接的方式是传入 autocommit=False。 而我的测试立刻失败了。
我在Windows上使用Python 3.13.3,把代码精简为一个主表与明细表的最小示例,第一次打开时不带 autocommit=False,第二次带上它。 这段代码使用一个 ON DELETE CASCADE 约束来自动清理明细表,在两个表中插入(有效的)数据,删除主表中的记录,并控制明细表中的行是否保留:
import sqlite3
def do(con):
con.execute("PRAGMA foreign_keys = ON")
con.execute("""CREATE TABLE master (
id INTEGER PRIMARY KEY,
name TEXT)""")
con.execute("""CREATE TABLE child (
id INTEGER PRIMARY KEY,
parent INTEGER NOT NULL REFERENCES master(id) ON DELETE CASCADE)""")
con.commit()
con.execute("INSERT INTO master(id, name) VALUES(1, 'a')")
con.execute("INSERT INTO child(id, parent) VALUES(1, 1)")
con.execute("INSERT INTO child(id, parent) VALUES(2, 1)")
con.execute("DELETE FROM master WHERE id = 1")
con.commit()
all = con.execute("SELECT * FROM child").fetchall()
con.commit()
return len(all)
con = sqlite3.connect(":memory:")
print(do(con))
con.close()
con = sqlite3.connect(":memory:", autocommit=False)
print(do(con))
con.close()
我得到:
0
2
这证明只有在未开启自动提交时,记录才会被正确移除。
为什么在以 autocommit=False 打开数据库时,外键约束没有被强制执行?是否有办法强制执行它们?
解决方案
我感觉原因在于 PRAGMA 语句在 autocommit=False 时不起作用。@snakecharmerb给出的链接解释了原因:一个 PRAGMA 只能在任何事务之外发出,而 autocommit=False 会立即开启一个隐式事务。
因此有一个直接的解决方案:不要使用 PRAGMA,而是使用连接对象的 setconfig 方法:
...
def do(con):
con.setconfig(sqlite3.SQLITE_DBCONFIG_ENABLE_FKEY, True)
con.execute("""CREATE TABLE master (
...
这就足以持续地开启外键约束。
在 autocommit=False 模式下,不仅最初有一个事务处于活动状态,而且在提交后会立即重新启动一个新事务。因此这也行不通:
...
def do(con):
con.commit()
con.execute("PRAGMA foreign_keys = ON")
con.execute("""CREATE TABLE master (
...
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。