SQLite触发器是一种由INSERT、UPDATE、DELETE等数据库事件自动触发的特殊对象,它在很多业务场景里能替我们省掉大量重复代码。比如你想在删除一条订单记录时自动写入日志表,或者在修改库存数量时防止出现负数,这些都不需要在应用层写额外的判断逻辑,交给触发器就能搞定。本文将围绕SQLite触发器的编写方法,从语法结构到完整实例逐层展开。

SQLite触发器的基本语法结构
编写触发器使用CREATE TRIGGER语句,完整语法如下:
CREATE TRIGGER 触发器名 [BEFORE|AFTER] [INSERT|UPDATE|DELETE]
ON 表名
FOR EACH ROW
[WHEN 条件表达式]
BEGIN
-- SQL语句,可以有多条
END;语法中有几个关键点需要理解。首先是触发时机,BEFORE表示在数据真正写入或删除之前执行触发器逻辑,适合做数据校验和拦截;AFTER则是在操作完成后执行,适合做日志记录和后续联动处理。其次是触发事件,一个触发器只能绑定一种事件类型,如果插入和删除都需要处理,就要分别创建两个触发器。
FOR EACH ROW在SQLite中是行级触发的声明。需要注意的是,SQLite只支持行级触发器,不像某些数据库还支持语句级触发器,所以这一行写不写效果相同,但为了可读性建议保留。另外,如果一条UPDATE语句修改了10行数据,触发器会执行10次,这一点在写高开销逻辑时要特别留意性能影响。
WHEN子句是可选的过滤条件,只有满足条件的行才会触发逻辑体。它相当于给触发器加了一道门槛,例如只在金额大于某个阈值时才记录日志,可以显著减少不必要的执行。触发器内部的SQL语句使用NEW.列名访问新数据,用OLD.列名访问旧数据。INSERT事件只有NEW可用,DELETE事件只有OLD可用,UPDATE事件两者都可以使用。
三个典型实例:日志记录、级联更新与数据校验
先看第一个实例,用触发器自动记录操作日志。假设有一张用户表和一个日志表,我们希望在用户信息被删除时自动留痕:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
email TEXT
);
CREATE TABLE user_logs (
log_id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
action TEXT,
log_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 删除用户时自动写入日志
CREATE TRIGGER trg_user_delete
AFTER DELETE ON users
FOR EACH ROW
BEGIN
INSERT INTO user_logs (user_id, action)
VALUES (OLD.id, 'DELETE');
END;第二个实例是级联更新。SQLite默认的外键约束不提供ON UPDATE CASCADE之外的一些灵活行为,有时我们会用触发器手工实现关联同步。例如主表分类ID变更时,自动更新商品表中的分类外键:
CREATE TRIGGER trg_category_update
AFTER UPDATE OF id ON categories
FOR EACH ROW
BEGIN
UPDATE products
SET category_id = NEW.id
WHERE category_id = OLD.id;
END;注意这里的UPDATE OF id写法,它可以限定只有特定列被修改时才触发,避免其他列的无关更新造成额外开销。
第三个实例是数据校验。SQLite的CHECK约束功能有限,遇到需要跨表校验的场景,BEFORE INSERT触发器配合RAISE函数是很好的方案。下面的例子防止库存数量被更新为负数:
CREATE TRIGGER trg_stock_check
BEFORE UPDATE ON inventory
FOR EACH ROW
WHEN NEW.quantity < 0
BEGIN
SELECT RAISE(ABORT, '库存数量不能为负数');
END;RAISE(ABORT, 消息)会中断当前操作并回滚整个语句,错误消息会直接返回给调用方。除了ABORT,还有ROLLBACK和IGNORE两种选择,IGNORE表示静默跳过当前行的操作,其余行继续处理,这个行为在批量导入数据时非常实用。
触发器的管理、调试与常见坑点
管理触发器主要用三条命令。查看数据库中已创建的触发器,可以查询系统表或使用专有命令:
SELECT name, sql FROM sqlite_master WHERE type = 'trigger';
修改触发器没有专门的ALTER语句,只能先DROP TRIGGER 触发器名再重新创建。如果要在表结构变更时保留触发器,SQLite提供了临时的DROP TRIGGER IF EXISTS写法,配合CREATE TRIGGER IF NOT EXISTS可以写出幂等的建表脚本。
调试触发器有几个实用技巧。触发器内部的语法错误只在触发时才暴露,建议创建后立刻执行一次触发语句做冒烟测试。可以用EXPLAIN查看触发器的执行计划,也可以在触发器里临时插入调试数据到一张专用的调试表,观察逻辑是否按预期走到各个分支。
最后提醒几个常见的坑。第一,触发器中不能再操作触发表本身,比如在users表的触发器里UPDATE users,会导致递归触发或直接报错。第二,批量导入数据时触发器会逐行执行,性能可能急剧下降,大批量场景建议临时禁用触发器。第三,触发器对应用层是透明的,出了问题不容易察觉,因此涉及关键业务逻辑的触发器一定要在文档中明确记录,避免后续维护时有人重复实现同样的逻辑造成数据错乱。掌握这些要点后,你就可以放心地在SQLite项目中使用触发器来简化数据层的维护工作了。