SHOW TRIGGERS 在沙箱里报语法错,等价物是 sqlite_master。
sqlite_master 只有四列有用:type、name、tbl_name、sql,第四列存的是建表原文。
触发器名和索引名一样,是全库唯一的。
教材 15.2 两个小节:SHOW TRIGGERS 语句、查 triggers 表。
两条路在沙箱里都断了,但断的方式不一样,这个区别本身有用。
能查触发器有三层用处:确认它建成了、看清它到底干了什么、删之前知道有哪些。
第三层最实用,下一篇结尾的批量删就靠这个。
这一篇也顺手补第 11 章的一个对照:索引名全库唯一,触发器名也一样。
一、SHOW TRIGGERS 报的是语法错
教材的两种查法,在沙箱里一条都不通,但报错分两类。
| 教材里的查法 | 沙箱的反应 |
|---|---|
SHOW TRIGGERS |
near "SHOW": syntax error |
SHOW TRIGGERS FROM 库名 |
near "SHOW": syntax error |
SHOW CREATE TRIGGER 名 |
near "SHOW": syntax error |
SELECT * FROM information_schema.TRIGGERS |
no such table: information_schema.TRIGGERS |
SELECT * FROM mysql.triggers |
no such table: mysql.triggers |
前三行是 syntax error,说明语法不存在——解析阶段就被挡了。
后两行是 no such table,说明语法有、数据源没有——解析通过了,找表失败。
这个区别在实际排查里很有用:看到 syntax error 就别再试同一条语句的变体了,换语言;
看到 no such table 才是「查的地方不对」。
顺带说清教材那句「查看 triggers 表中触发器信息」:
旧版 MySQL 在 mysql 库里有一张 triggers 表,后来统一到了 information_schema.TRIGGERS。
两个名字都能在书里见到,指的是同一件事——「有一张系统表专门记触发器」。
二、等价物:sqlite_master 这张表
SQLite 把「自己有哪些表、视图、索引、触发器」全记在一张叫 sqlite_master 的表里。
它相当于 MySQL 的 information_schema,但只有一张表、四列有用。
| 列 | 含义 |
|---|---|
type |
table/view/index/trigger |
name |
对象名,这里就是触发器名 |
tbl_name |
挂在哪张表(或视图)上 |
sql |
建这个对象的语句原文 |
查触发器就是 WHERE type = 'trigger'。
查出来几行,就说明库里挂着几个触发器。
第四列 sql 最值钱:它存的是你当初敲的建触发器语句原文,等于把定义完整存下来了。
想看某个触发器到底干了什么,读这一列就够,不用去翻代码仓库。
三、把定义完整读出来
定义可能很长,一整行打下来超过一百字,得折开才看得清。
折开不影响内容,sql 里存的仍然是一整行。
CREATE TRIGGER trg_goods_upd AFTER UPDATE OF price ON tb_g
FOR EACH ROW
WHEN NEW.price > OLD.price
BEGIN
INSERT INTO tb_log(act, ref, note)
VALUES ('涨价', NEW.id, printf('%g->%g', OLD.price, NEW.price));
UPDATE tb_stat
SET total = total + (NEW.price - OLD.price) * stock
WHERE id = 1;
END;
读这段定义能看出四件事:时机 AFTER、事件 UPDATE OF price、
条件 WHEN NEW.price > OLD.price、动作是两条语句。
这一段其实就是「触发器的全部语法」:
CREATE TRIGGER 名 时机 事件 ON 表 FOR EACH ROW [WHEN 条件] BEGIN 语句; 语句; END。
方括号里那个 WHEN 是可选的,其余都是必写项。
四、UPDATE OF 只认列名
UPDATE OF price 这个写法要特别注意:只有改到 price 这一列才触发。
改库存、改名字都不触发。
| 执行的语句 | 日志累计 |
|---|---|
UPDATE tb_g SET stock = 999 |
0 条 |
UPDATE tb_g SET price = 388 |
1 条 |
这个机制在「只关心某几列变化」时特别省:一张二十列的表,只在价格变动时才要跑一段逻辑,
不加这个限定就得每列都触发再自己判断。
MySQL 没有 UPDATE OF 列名。
它任何一列变了都会触发,
要限制只能靠 BEGIN ... END 里自己写 IF NEW.price <> OLD.price THEN ... END IF。
所以这段定义搬到 MySQL 上是要改写的,不能直接照抄。
五、触发器名全库唯一
第 11 章讲索引时验过一次:索引名全库唯一,不是表内唯一。
触发器也一样。在两张不同的表上建同名触发器,第二句直接被挡:trigger dup already exists。
MySQL 那边是「库内唯一」,跨库可以重名。
两边都要求同一个库里不能撞名,这一条是一致的。
所以起名最好带上表名当前缀:trg_goods_ins、trg_order_upd。
只叫 ins、upd 这种,第二张表就没法起名了,只能改成 ins2,越改越乱。
六、视图上的触发器,tbl_name 记的是视图
在视图上建三个 INSTEAD OF 触发器,查 sqlite_master,三行的 tbl_name 全是视图名。
name |
tbl_name |
|---|---|
vi_insert |
v_g |
vi_update |
v_g |
vi_delete |
v_g |
INSTEAD OF 是视图专用这件事,在元数据里也写得清清楚楚。
这张表还有个用法:按 tbl_name 筛一遍,就知道某张表上挂了哪些触发器。
删表之前、改表结构之前,先这么筛一次,比凭记忆可靠。
下一篇结尾的批量删,就是从这一步开始的。
七、动手 Demo:五种查法挨个试,再从 sqlite_master 读定义
这一段先按教材的写法挨个试:SHOW TRIGGERS、SHOW CREATE TRIGGER、
information_schema.TRIGGERS、mysql.triggers,把五种报错摆在一起对照;
再改用 sqlite_master 把定义完整读出来,最后验 UPDATE OF 和触发器名的唯一性。
import sqlite3
c = sqlite3.connect(u":memory:", isolation_level=None)
c.executescript(u"""
CREATE TABLE tb_g (id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT, price REAL, stock INTEGER);
CREATE TABLE tb_log (id INTEGER PRIMARY KEY AUTOINCREMENT,
act TEXT, ref INTEGER, note TEXT);
""")
c.execute(u"CREATE TRIGGER trg_goods_ins AFTER INSERT ON tb_g FOR EACH ROW "
u"BEGIN INSERT INTO tb_log(act, ref) VALUES ('新增', NEW.id); END")
# ① 教材的查法,五种挨个试
for q in (u"SHOW TRIGGERS", u"SHOW TRIGGERS FROM db_database28",
u"SHOW CREATE TRIGGER trg_goods_ins",
u"SELECT * FROM information_schema.TRIGGERS",
u"SELECT * FROM mysql.triggers"):
try:
c.execute(q).fetchall()
print(q, u"-> 成功")
except sqlite3.Error as e:
print(q, u"->", e)
# ② 等价物:sqlite_master 里筛 type = 'trigger'
for row in c.execute(u"SELECT type, name, tbl_name "
u"FROM sqlite_master WHERE type = 'trigger'"):
print(row)
# ③ 读定义:sql 列存的是原文
print(c.execute(u"SELECT sql FROM sqlite_master "
u"WHERE name = 'trg_goods_ins'").fetchone()[0])
# ④ UPDATE OF 只认列名
c.execute(u"CREATE TRIGGER trg_price AFTER UPDATE OF price ON tb_g "
u"FOR EACH ROW BEGIN INSERT INTO tb_log(act) VALUES ('碰了价格'); END")
c.execute(u"UPDATE tb_g SET stock = 999 WHERE id = 1") # 不触发
c.execute(u"UPDATE tb_g SET price = 388 WHERE id = 1") # 触发
print(c.execute(u"SELECT count(*) FROM tb_log").fetchone())
# ⑤ 触发器名全库唯一:换一张表再叫同名
c.execute(u"CREATE TABLE tb_h (id INTEGER PRIMARY KEY, v REAL)")
try:
c.execute(u"CREATE TRIGGER trg_price AFTER INSERT ON tb_h "
u"FOR EACH ROW BEGIN SELECT 1; END")
except sqlite3.Error as e:
print(e)
实跑输出:
--------------------------------------------------------------------
查看触发器:SHOW 不认,得看 sqlite_master
--------------------------------------------------------------------
SHOW TRIGGERS 在沙箱里报的是语法错
--------------------------------------------------------------------
**教材 15.2 给了两种查法: `SHOW TRIGGERS`,和直接查系统表。**
**沙箱这两种都不通,报错的位置都在关键字上,说明它根本没这套语法。**
教材里的查法 沙箱的反应
---------------------------------------------------------------------------------------
`SHOW TRIGGERS` near "SHOW": syntax error
`SHOW TRIGGERS FROM db_database28` near "SHOW": syntax error
`SHOW CREATE TRIGGER trg_goods_ins` near "SHOW": syntax error
`SELECT * FROM information_schema.TRIGGERS` no such table: information_schema.TRIGGERS
`SELECT * FROM mysql.triggers` no such table: mysql.triggers
**注意报错分两类。**
**`SHOW` 开头的报 `near "SHOW": syntax error`,解析阶段就被挡了。**
**`information_schema` 那两条报 `no such table`,解析通过、找表失败。**
**这个区别有用:前者说明语法不存在,后者说明语法有、数据源没有。**
等价物:sqlite_master 这张表
--------------------------------------------------------------------
**SQLite 把「自己有哪些表、视图、索引、触发器」全记在一张叫 `sqlite_master` 的表里。**
**它相当于 MySQL 的 `information_schema`,但只有一张表、四个有用的列。**
mysql> SELECT type, name, tbl_name FROM sqlite_master WHERE type = 'trigger'
type name tbl_name
------------------------------
trigger trg_goods_ins tb_g
1 row in set
**`type` 是 `trigger`,`name` 是触发器名,`tbl_name` 是它挂在哪张表上。**
**第四列 `sql` 存的是你当初敲的建触发器语句原文——这一列最值钱,等于把定义完整存下来了。**
把定义完整读出来
--------------------------------------------------------------------
**定义可能很长,一整行打下来超过一百字,得折开才看得清。**
mysql> 把 sql 这一列原样打出来(原文一整行,这里按空格折开看):
| CREATE TRIGGER trg_goods_upd AFTER UPDATE OF price ON tb_g FOR EACH ROW WHEN
| NEW.price > OLD.price BEGIN INSERT INTO tb_log(act, ref, note) VALUES ('涨价',
| NEW.id, printf('%g->%g', OLD.price, NEW.price)); UPDATE tb_stat SET total = total +
| (NEW.price - OLD.price) * stock WHERE id = 1; END
**读这段定义能看出四件事:**
**时机 `AFTER`、事件 `UPDATE OF price`、条件 `WHEN NEW.price > OLD.price`。**
**动作是两条语句,用分号隔开,一起包在 `BEGIN` 和 `END` 之间。**
**`UPDATE OF price` 要特别注意:只有改到 `price` 这一列才触发。**
UPDATE OF 只认列名,改别的列不触发
--------------------------------------------------------------------
执行的语句 日志累计
---------------------------------------
`UPDATE tb_g SET stock = 999` 0 条
`UPDATE tb_g SET price = 388` 1 条
**改库存没反应,改价格才记一条。**
**MySQL 没有 `UPDATE OF 列名` 这个写法,任何一列变了都会触发。**
**要限制只能靠 `BEGIN ... END` 里自己写 `IF`。**
**所以这段定义搬到 MySQL 上是要改写的,不能直接照抄。**
触发器名是全库唯一的
--------------------------------------------------------------------
**第 11 章讲索引的时候验过一次:索引名全库唯一,不是表内唯一。**
**触发器也一样。在两张不同的表上建同名的触发器,第二句直接被挡。**
在 tb_h 上再建一个叫 dup 的:trigger dup already exists
**MySQL 的触发器名是「库内唯一」,跨库可以重名。**
**两边都要求同一个库里不能撞名,这一条是一致的,所以起名时最好带上表名当前缀。**
视图上的触发器,tbl_name 记的是视图
--------------------------------------------------------------------
mysql> SELECT name, tbl_name FROM sqlite_master WHERE type = 'trigger'
name tbl_name
------------------
vi_insert v_g
vi_update v_g
vi_delete v_g
3 rows in set
**三个触发器全挂在 `v_g` 上,`INSTEAD OF` 是视图专用,元数据里写得很清楚。**
**这张表还有个用法:按 `tbl_name` 筛一遍,就知道某张表上挂了哪些触发器。**
第一节那张表一摆出来,syntax error 和 no such table 就分得很清楚了。
前三行是「这套语法压根没有」,后两行是「语法有、那张表不在」。
第三节打出来的定义只有一行,说明 sql 列存的是原文:你当初怎么敲的、空格怎么留的,
它就怎么存。
所以「从库里读到的定义」和「代码仓库里那份」可能不一致——两边改过一边没同步,
这种事在运维里很常见。
第五节那次同名失败,报的是 trigger dup already exists。
注意它没说「这张表上已经有了」,说的是「这个名字已经存在」——一句话点明了作用域是全库。
30 秒实验
- 在
sqlite_master里WHERE type IN ('table','view','index','trigger')各查一次, 看这张表到底记了几类对象,type有哪几个取值。 - 建一个
UPDATE触发器(不加OF),改stock和改price各一次,看触发几次; 再改成UPDATE OF price,同样两次改动,看差在哪。 - 把
sqlite_master里某个触发器的sql列整段复制出来,直接再执行一遍,看能不能重建成功、 报什么错——然后删掉原来的再试一次。
常见误区
- 把
SHOW TRIGGERS的报错理解成「权限不够」或「版本太老」。syntax error说明这条语法在这套引擎里不存在,不是权限问题,换权限也解决不了。 - 以为触发器名是表内唯一,于是两张表都起了
trg_ins。第二张表直接建不上。trigger 名 already exists这句报错已经说明了作用域。 - 以为
sqlite_master.sql里存的是规范化之后的标准写法。它存的是原文, 包括你当初的大小写和空白。要判断「定义到底长什么样」,得读它,而不是回忆自己当初怎么写的。
自测三问(点开对答案)
1. `syntax error` 和 `no such table` 这两个报错,能推出什么不同的结论? —— `syntax error` 说明这条语法在引擎里不存在(`SHOW` 全家都是),改参数没用,得换写法; `no such table` 说明语法对了、只是那张表不在(`information_schema` 就是这样), 该查的是「数据源」而不是「语法」。 2. 想删某张表上的全部触发器,第一步该做什么? —— 查 `sqlite_master`: `SELECT name FROM sqlite_master WHERE type = 'trigger' AND tbl_name = '表名'`。 拿到名单再逐个删,别凭记忆写死名字。 3. `UPDATE OF price` 这个写法,MySQL 上怎么写? —— MySQL 没有 `UPDATE OF`,只能写 `UPDATE`, 然后在触发器体里用 `IF NEW.price <> OLD.price THEN ... END IF` 自己判断。下一篇讲怎么用触发器:先看清 BEFORE 和 AFTER 的先后、同类型多个谁先跑,
再拿它做拦截和冗余汇总这两件实事。
- ☐ (本篇完,下一篇 15.3 使用触发器)