登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

MySQL RESIGNAL 怎样在存储程序中保留原始错误信息

来源:17golang原创

时间:2026-10-20 23:01:49 280浏览 收藏

MySQL 条件处理器执行记录或清理语句后,如果仍要向调用方传递触发处理器的原始错误,使用裸 RESIGNAL;,不要重新拼装一个通用 SIGNAL。需要记录原始诊断时,在处理器内使用 GET STACKED DIAGNOSTICS;需要有意修改某项信息时,再使用 RESIGNAL SET。

官方地址:https://www.mysql.com/

下面以 MySQL 8.4 为范围,使用主动触发的受控异常演示,不涉及生产表写入。示例未在本文环境执行;下文给出的都是语义预期和读者可复现的方法,不是实测数据或性能结论。

先记录错误三元组作为对照

假设一个存储过程需要记下失败原因,再把失败交给应用。如果统一改成“操作失败”,调用端可能失去用于分类的错误号。这里先把基线定义为三项:SQLSTATE、MYSQL_ERRNO、MESSAGE_TEXT。判断“保留原错”,不应只看客户端有没有显示一段文字。

观察项用途比较方法
SQLSTATE条件类别比较五字符状态值
MYSQL_ERRNOMySQL 错误标识比较数字错误号
MESSAGE_TEXT具体诊断文本区分原文、主动改写与客户端展示

这是一套错误传递指标,不是压测指标。若没有采集耗时、吞吐和负载,就不能把“异常更完整”写成“性能提高”。对排障而言,先确认语义正确才有讨论其他代价的基础。

额外语句改变的是哪个诊断区

条件处理器激活时,诊断区有当前层与堆叠层。当前诊断区 CURRENT 可以被处理器中的后续语句改变;堆叠诊断区 STACKED 保存处理器激活时的条件信息。只在记录语句之后读取 CURRENT,就可能读到记录语句自己的诊断,而不是业务失败。

GET STACKED DIAGNOSTICS 用于读取原条件;RESIGNAL 用于重新向外传递条件。二者不是同一个动作。只把值读进变量,并不代表调用者会收到失败;裸 RESIGNAL 本身也不要求先读出这些变量。

当前诊断区、堆叠诊断区以及诊断读取和透传语句的静态关系
图1:诊断区静态结构说明图。连线区分读取对象与透传依据,不表示逐步执行流程,也不是运行截图。

在处理器里读取原条件再裸 RESIGNAL

下面的过程有两种模式:0 原样透传,1 只改写消息。先读取条件数量,再按该数量取主要错误,避免把 CONDITION 1 当作所有多条件场景的固定答案。会话变量只为教学观察使用,不是持久审计系统。

-- 在隔离测试库使用新的过程名;已有同名对象时不要直接覆盖
DELIMITER //
CREATE PROCEDURE demo_error_relay_28(IN p_reword BOOLEAN)
BEGIN
  DECLARE v_count INT;
  DECLARE v_state CHAR(5);
  DECLARE v_errno INT;
  DECLARE v_message TEXT;

  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    -- 从堆叠诊断区读取触发处理器的原条件
    GET STACKED DIAGNOSTICS v_count = NUMBER;
    GET STACKED DIAGNOSTICS CONDITION v_count
      v_state = RETURNED_SQLSTATE,
      v_errno = MYSQL_ERRNO,
      v_message = MESSAGE_TEXT;

    -- 额外语句用于观察,可能影响当前诊断区但不替代原条件
    SET @demo_original_state = v_state,
        @demo_original_errno = v_errno,
        @demo_original_message = v_message;

    IF p_reword THEN
      -- 只主动替换消息;不指定新 SQLSTATE 或错误号
      RESIGNAL SET MESSAGE_TEXT = 'operation rejected; inspect server diagnostics';
    END IF;

    -- 不带任何参数,传递进入本处理器时的原条件
    RESIGNAL;
  END;

  -- 受控测试输入:明确指定三项基线,不依赖未知业务表
  SIGNAL SQLSTATE '45000'
    SET MYSQL_ERRNO = 1644, MESSAGE_TEXT = 'demo input rejected';
END//
DELIMITER ;

DELIMITER 是 mysql 客户端命令,不是服务器 SQL。通过驱动创建过程时,应把完整 CREATE PROCEDURE 作为一个语句提交,不把 DELIMITER 发给服务器。需要相应建过程与调用权限;本文没有让读者替换现有生产过程。

用受控输入比较透传与消息改写

在专用连接分开调用两次。CALL 会按设计返回错误,如果客户端在错误后停止脚本,要在下一次交互中读取会话变量,或让测试代码先捕获异常再读取;不要误把“脚本停了”判断为没有保存诊断。

-- 第一组:预期向客户端传递完整原条件
CALL demo_error_relay_28(0);

-- 捕获上面的预期异常后,在同一连接读取原条件观察值
SELECT @demo_original_state, @demo_original_errno, @demo_original_message;
-- 第二组:预期仅消息改为处理器指定文本
CALL demo_error_relay_28(1);

-- 会话变量仍记录堆叠层读取的原条件,而不是改写后的消息
SELECT @demo_original_state, @demo_original_errno, @demo_original_message;

把客户端异常对象中的状态、数字码与消息分别保存。注意有的客户端会在消息外添加数据库前缀、过程名或格式化信息,不要把展示包装当作 MESSAGE_TEXT 自身。整个对照保持同一版本、同一过程和同一输入异常,仅改变模式参数。

把预期变化和实测数据分开

模式客户端 SQLSTATE客户端错误号消息的语义预期
0:裸 RESIGNAL450001644demo input rejected
1:RESIGNAL SET MESSAGE_TEXT450001644operation rejected; inspect server diagnostics

这张表来自示例中显式指定的条件和 RESIGNAL 的语义,不能作为已执行输出引用。读者应在自己的测试记录中填入实际值,并另记服务端版本及驱动信息。两组会话观察值都应保留原消息;第二组客户端收到的却是主动改写后的文本,这正是“原始记录”和“对外契约”的区别。

诊断条件中的状态、错误号和消息字段与原样及改写策略
图2:错误字段静态说明图。原样传递与定向改写有不同契约;图中不列实测结果或性能数字。

如果生产代码使用 RESIGNAL SQLSTATE ... 或 SET MYSQL_ERRNO,已经明确选择修改条件,不应继续宣称“完整保留原错”。要统一业务错误时,先独立保存底层诊断,并让调用端知道它接收的是新契约。

日志失败与事务责任仍要单独处理

上面的额外语句是简单 SET;替换成日志 INSERT 后,日志本身也可能失败。如果它抛出未处理的新异常,使后续 RESIGNAL 没有机会执行,原样透传设计就会被打断。日志与清理需要独立的失败策略,不能把示例夸大成“任何情况下都保留原错”。

若业务规定日志失败不得覆盖业务错误,可以在更小的嵌套块中专门处理日志异常,同时把日志失败发送到另一个受控观察渠道。这是主动取舍,不能静默吞掉所有清理失败后仍声称系统没有异常。

RESIGNAL 会自动回滚业务事务吗?

错误传递与事务归属是两件事。由调用者持有事务时,不要在通用处理器里无条件提交或回滚整个事务;应按明确的事务契约处理。本文示例没有开启事务,也没有业务写入。

能在普通语句位置直接 RESIGNAL 吗?

不能把它当成通用 SIGNAL;它要求活动的条件处理器上下文。没有原条件时,应重新设计异常产生的位置,而不是依赖 RESIGNAL 生成新错。

为什么示例没有使用 OUT 参数返回原错?

抛出异常后的 OUT/INOUT 传递另有规则,不宜把它当作一定可达的错误回传通道。应用应接收数据库异常;教学会话变量也不能跨连接读取,更不能在连接池中当作永久记录。

参考:RESIGNAL、GET DIAGNOSTICS、诊断区、SIGNAL。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>