SQLite3默认禁用外键约束,需显式执行PRAGMA foreign_keys = ON启用;触发器用于模拟级联行为,但必须注意时机(BEFORE/AFTER)、事务一致性、语法限制及执行顺序。

SQLite3 不能自动维护外键级联行为(比如 ON DELETE CASCADE 默认是禁用的),想让关联表同步更新或记录操作,必须靠触发器手动实现。直接写 CREATE TRIGGER 就行,但顺序、时机、事务边界不注意,很容易漏数据或报错。
触发器必须显式启用外键支持
SQLite 默认关闭外键约束,哪怕你写了 FOREIGN KEY 字段,ON DELETE / ON UPDATE 也不会生效 —— 触发器不是替代方案,而是补位手段。但如果你依赖触发器来模拟级联,得先确认外键已开启,否则 PRAGMA foreign_keys = ON 不起作用,后续触发逻辑可能基于错误前提。
-
PRAGMA foreign_keys是连接级开关,每次新connect()都要重设 - Python 中需在
conn = sqlite3.connect(...)后立刻执行conn.execute("PRAGMA foreign_keys = ON") - 若没开,
DELETE FROM parent不会触发子表任何动作,你的触发器也收不到BEFORE DELETE ON parent事件
BEFORE 和 AFTER 的选择直接影响数据一致性
比如你要在删除订单时自动归档订单项到历史表,用 BEFORE DELETE 还是 AFTER DELETE?关键看依赖关系:如果归档逻辑需要读取即将被删的原始行内容(如 OLD.order_id),就必须用 BEFORE;如果只是记日志或清理缓存,AFTER 更安全,避免因触发器失败导致主操作回滚。
-
BEFORE可读OLD.*,但不能读NEW.*(不存在) -
AFTER可读OLD.*和NEW.*,但原行已删,某些字段可能为NULL - 多个触发器同属一个事件(如两个
BEFORE INSERT),执行顺序按创建时间,不可控 —— 别依赖顺序
触发器中禁止使用 SELECT ... INTO 或变量赋值
SQLite 触发器语法极度精简,不支持变量声明、循环、条件分支(IF)、存储过程式逻辑。所有操作必须是单条 SQL 语句,或者用 CASE + INSERT/UPDATE/DELETE 组合模拟简单分支。
- 想根据插入值决定是否写日志?只能用
INSERT INTO log_tb SELECT ..., datetime('now') WHERE NEW.status = 'failed' - 想更新关联表某字段?直接写
UPDATE order_items SET synced = 1 WHERE order_id = NEW.id - 不能写
SELECT count(*) FROM ... INTO @cnt—— 会报near "INTO": syntax error
事务内触发器失败会导致整个操作回滚
这是最容易被忽略的点:触发器不是“尽力而为”,而是强一致性环节。只要触发器里的任意一条语句失败(比如字段类型不匹配、约束冲突、表不存在),整个原始操作(INSERT/UPDATE/DELETE)立刻中止并回滚 —— 即使主 SQL 本身完全合法。
- 例如:触发器里
INSERT INTO audit_log,但audit_log缺少user_id非空约束,主表插入就会失败 - 调试时用
sqlite3 db.db "PRAGMA recursive_triggers = ON"开启嵌套触发(默认关),否则触发器里再改触发表,不会二次触发 - 生产环境别把日志表和业务表放在不同数据库(ATTACH),跨库触发器不被支持
真正麻烦的从来不是写触发器语法,而是判断哪条数据该在什么时刻、以什么方式联动 —— 比如用户删账号时,是立即删掉所有订单,还是只标记为“已注销”?这个业务规则一旦定下,触发器就是刚性执行者,没法打补丁。

















