MariaDB 中 AFTER UPDATE Trigger 执行失败
结论
问题不在 AFTER UPDATE,而是 Trigger 内部再次查询了触发它的 som_accounting,并通过子查询读取了正在更新的 som_ticket。
单独执行这段 SQL 时,这些表不在 Trigger 的调用上下文中,因此语句可以运行。在 Trigger 中,som_accounting 正被外层 UPDATE 使用,此时再次读取该表,会触发 MariaDB 对 Trigger 和存储函数的表访问限制。驱动显示的 unexpected end of stream 只是连接层错误,不一定是服务端返回的真正错误。
这里无须重新查询 som_accounting。当前发票的主键和付款状态都可以直接从 OLD、NEW 中取得,再使用 UPDATE ... JOIN 更新关联票券即可。
推荐实现
DELIMITER //
CREATE TRIGGER somt_au_accounting
AFTER UPDATE ON som_accounting
FOR EACH ROW
BEGIN
IF OLD.ID_paymentstatus = 1
AND NEW.ID_paymentstatus = 3 THEN
UPDATE som_ticket AS t
INNER JOIN som_accountingticket AS act
ON act.ID_ticket = t.ticketID
SET
t.ID_ticketstatus = 4,
t.activation_date = CURRENT_DATE()
WHERE act.ID_accounting = NEW.accountingID
AND t.ID_ticketstatus = 1;
END IF;
END//
DELIMITER ;
这段 Trigger 只处理付款状态从 1 变为 3 的情况。它使用 NEW.accountingID 定位本次更新的发票,通过 som_accountingticket 找到关联票券,并将状态为 1 的票券更新为 4。整个过程不再查询 som_accounting,也不需要在子查询中再次读取 som_ticket。
如果更新时允许修改 accountingID,需要按照业务规则决定使用 OLD.accountingID 还是 NEW.accountingID。主键通常不会改变,因此使用 NEW.accountingID 更直观。
为什么原 SQL 在控制台可以运行
控制台中执行的是一条独立的 UPDATE:
UPDATE som_ticket
SET ...
WHERE ticketID IN (...);
在这个执行环境中,som_accounting 只是一张普通的查询表,并不是当前由外层语句修改的表。
Trigger 则由下面的语句触发:
UPDATE som_accounting
SET ID_paymentstatus = 3
WHERE accountingID = 568;
执行期间,原 Trigger 中又包含:
SELECT * FROM som_accounting
也就是说,Trigger 试图再次访问正在触发它的表。MariaDB 对 Trigger 和存储函数有额外的表访问限制,用来避免递归修改、结果不确定以及“正在变化的表”等问题。因此,两段看似相同的 SQL,实际运行环境并不相同。
原语句还通过派生表读取了更新目标 som_ticket:
INNER JOIN (SELECT * FROM som_ticket) AS t2
这种写法常用于绕过“不能同时更新和查询同一张表”的限制,但它可能受到优化器是否合并派生表等行为的影响,在 Trigger 中也没有必要。使用 UPDATE ... JOIN,可以直接根据目标表当前行的条件进行过滤。
如何查看真正的错误
DBeaver 显示的错误:
Could not read resultset: unexpected end of stream
不足以说明具体的 SQL 问题。建议在 MariaDB 命令行中执行触发 Trigger 的 UPDATE,随后立即检查:
SHOW WARNINGS;
还应查看 MariaDB 服务端错误日志。如果连接确实被服务端异常关闭,需要确认服务进程是否发生崩溃或重启。
MariaDB 10.5.8 已经是较早的版本。如果改用简化后的 Trigger 后连接仍然断开,应先在使用相同数据的测试环境中升级到受维护的小版本,再尝试复现并检查服务端日志。不能只根据客户端显示的 unexpected end of stream 判断具体的 SQL 错误。
注意事项
- 如果
ID_paymentstatus和ID_ticketstatus是数值列,应直接使用1、3、4,无须写成字符串'1'、'3'、'4'。 AFTER UPDATETrigger 与原始的UPDATE som_accounting处于同一个事务中。票券更新失败时,发票状态更新通常也会回滚。- 如果一条语句更新多条发票,Trigger 会针对每一行执行一次。
- 建议为关联查询建立合适的索引,例如:
CREATE INDEX idx_accountingticket_accounting_ticket
ON som_accountingticket (ID_accounting, ID_ticket);
CREATE DEFINER必须填写真实且存在的账户。如果没有明确需要,可以省略DEFINER,由 MariaDB 使用当前账户创建 Trigger。
备注:内容仅供参考。
