sqlite触发器禁止递归调用自身,这是硬性限制而非配置问题;after/before触发器中对本表的dml操作会直接报错,唯一可行方案是将逻辑移至应用层或用临时表+外部批量处理。

SQLite 触发器不能递归调用自身
SQLite 明确禁止触发器在执行过程中再次触发同一张表的同类型事件(比如 AFTER UPDATE 触发器里再执行一次 UPDATE 当前表),这会导致报错 no such function: recursive 或更常见的 database is locked / cannot modify <table_name> because it is being used by trigger</table_name>。这不是配置问题,是 SQLite 的硬性限制:它不支持触发器嵌套或自触发。
触发器里 UPDATE 同一张表就直接失败
哪怕你只写一条 UPDATE 语句去改当前表的另一行,只要该行满足触发条件(比如有 WHERE 匹配、或没加足够约束),就会再次触发,形成死锁。SQLite 不像 PostgreSQL 那样提供 pg_notify 或 MySQL 那样允许有限嵌套(max_sp_recursion_depth),它干脆不让你走这条路。
- BEFORE 触发器中修改
NEW.xxx只影响**当前正在插入/更新的那一行**,对其他行无效 - AFTER 触发器中读取
NEW/OLD是只读快照,赋值会报语法错误 - 试图用
INSERT INTO ... SELECT或REPLACE绕过?只要目标表是触发表本身,照样被拦
真正能用的替代方案只有两种
要么把逻辑提到应用层,要么用临时表 + 应用控制循环。没有“启用递归触发器”的编译开关——SQLite 根本没这个功能。
-
应用层事务包干:在代码里用单个事务完成主操作 + 关联更新,例如 Python 中先
UPDATE orders SET status = ? WHERE id = ?,再UPDATE order_items SET shipped = 1 WHERE order_id = ? -
用临时表暂存中间状态:在触发器里 INSERT 到
temp.pending_updates(需提前建好),然后由外部定时任务或下一次业务请求批量处理 - 别碰
PRAGMA recursive_triggers = ON—— 这个 pragma 只控制INSTEAD OF触发器是否可递归调用其他触发器,**不影响普通 AFTER/BEFORE 对本表的 DML 禁令**
容易被忽略的隐性递归场景
最常踩坑的是外键级联动作和触发器共存。例如定义了 ON DELETE CASCADE,又在子表上写了 AFTER DELETE 触发器——当父表删一行,子表自动删多行,每删一行都触发一次,你以为只是删数据,其实已在反复进触发器。
- 检查所有相关表的外键定义:
PRAGMA foreign_key_list(<table_name>)</table_name> - 触发器里避免任何可能间接导致本表变更的操作:比如调用函数返回值用于 UPDATE、或用子查询结果驱动 INSERT
- 调试时加日志(如写入
debug_log表)比靠报错定位更可靠,因为很多递归失败表现为静默卡住或超时











