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

MySQL GET DIAGNOSTICS CONDITION 怎样在异常处理里拿到真正的错误信息

来源:17golang原创

时间:2026-11-10 00:08:42 285浏览 收藏

在存储过程中捕获异常后,明明已经写了 GET DIAGNOSTICS CONDITION 1,却拿到空的 MESSAGE_TEXT,常见原因不是错误不存在,而是读错了诊断区,或者在读取前又执行了会改变诊断区的语句。本文把这个故障拆开,给出一套能保留原始 SQLSTATE、错误号和错误文本的写法。

官方文档:https://dev.mysql.com/doc/refman/8.0/en/get-diagnostics.html

本文只讨论 MySQL 存储程序内的异常处理,不讨论客户端驱动如何封装数据库错误。

先看症状:为什么 CONDITION 1 可能不是你要的错误

问题通常出现在一个带 DECLARE ... HANDLER 的过程里:业务语句触发异常,handler 中先做回滚、写日志或查询状态,最后才调用 GET DIAGNOSTICS。这时即使语句能够执行,读到的也可能是后续语句产生的诊断信息。

还有一个容易忽略的点:一次 SQL 语句可能留下多个 condition。MySQL 官方文档明确提醒,不能机械地把“第一条 condition”当成主错误;更稳妥的方式是先取得诊断区的 NUMBER,再用这个编号读取条件。

MySQL 当前诊断区、堆叠诊断区与异常处理器之间的关系说明图
图1:诊断区边界结构说明图,展示当前诊断区、STACKED 诊断区和 handler 的静态关系;这是说明图,不是运行截图。

触发条件:handler 里要读 STACKED 诊断区

在异常 handler 中,CURRENT 表示当前诊断区;STACKED 表示进入 handler 前保存的那一层诊断区,并且只在 condition handler 上下文中可用。要拿到触发 handler 的原始错误,优先使用 GET STACKED DIAGNOSTICS。

如果只是执行一条普通 SQL 后立即读取结果,可以使用 GET CURRENT DIAGNOSTICS。两者不是“新旧语法”的区别,而是读取上下文不同。

-- 普通语句之后读取当前诊断区,适合不在 handler 中的场景
GET CURRENT DIAGNOSTICS
  @condition_count = NUMBER;

-- handler 中读取触发异常前保存的诊断区,避免混入 handler 自己的语句
GET STACKED DIAGNOSTICS CONDITION 1
  @sqlstate = RETURNED_SQLSTATE,
  @errno = MYSQL_ERRNO,
  @message = MESSAGE_TEXT;

修复动作:先取 NUMBER,再按编号读取原始信息

下面的过程把读取动作放在 handler 的第一段,并把结果放到局部变量。示例中的业务表名只用于表达触发异常的位置,重点是诊断区选择和条件编号。

-- 该过程演示:捕获异常时保留原始诊断信息,再回滚并重新抛出
DELIMITER //
CREATE PROCEDURE create_order(IN p_user_id BIGINT)
BEGIN
  DECLARE v_count INT DEFAULT 0;
  DECLARE v_state CHAR(5) DEFAULT '';
  DECLARE v_errno INT DEFAULT 0;
  DECLARE v_message TEXT DEFAULT '';

  DECLARE EXIT HANDLER FOR SQLEXCEPTION
  BEGIN
    -- 必须先读取 STACKED 区,避免回滚或日志语句覆盖原错误
    GET STACKED DIAGNOSTICS v_count = NUMBER;
    IF v_count > 0 THEN
      -- 用最后一个条件编号获取更可能对应主错误的诊断项
      GET STACKED DIAGNOSTICS CONDITION v_count
        v_state = RETURNED_SQLSTATE,
        v_errno = MYSQL_ERRNO,
        v_message = MESSAGE_TEXT;
    END IF;

    -- 这里先回滚业务变更;日志字段使用已经保存的局部变量
    ROLLBACK;
    INSERT INTO order_error_log(sqlstate_code, mysql_errno, message_text)
      VALUES (v_state, v_errno, v_message);
    -- 继续把原始异常交给上层调用方,不吞掉失败原因
    RESIGNAL;
  END;

  -- 业务语句只是触发点,实际项目中替换为自己的写入逻辑
  START TRANSACTION;
  INSERT INTO orders(user_id) VALUES (p_user_id);
  COMMIT;
END//
DELIMITER ;

这里有三个关键动作:第一,handler 的第一条有效操作就是读取 STACKED 区;第二,先取 NUMBER 再取条件;第三,把值保存到变量后才回滚、记录日志和 RESIGNAL。

MySQL 异常处理器先保存诊断字段再回滚记录并重新抛出的关系说明图
图2:异常处理职责说明图,展示诊断字段、回滚、日志记录和 RESIGNAL 的静态关系;这是说明图,不是运行截图。

防复发:用四项检查排除“空错误信息”

检查项正确做法常见误区
诊断区handler 中优先读 STACKED把 CURRENT 当成触发异常的快照
读取时机把 GET DIAGNOSTICS 放在 handler 前部先 ROLLBACK 或 INSERT 日志
条件编号先取 NUMBER,再读取对应 CONDITION固定写 CONDITION 1
异常传播保存字段后再决定 RESIGNAL记录后直接吞掉异常

排查时可以先把 RETURNED_SQLSTATE、MYSQL_ERRNO 和 MESSAGE_TEXT 写到临时变量或专用日志表,再确认 handler 内没有提前执行会改变诊断上下文的语句。不要把“日志表插入成功”误认为原业务已经成功。

总结与常见问题

MySQL 的关键不是把 CONDITION 1 写得更复杂,而是先选对诊断区、抓住读取时机,再按诊断区中的条件数量取值。handler 中使用 STACKED,先读 NUMBER,保存完原始字段后再回滚、记录和 RESIGNAL,就能避免大多数“错误信息为空或不对”的问题。

为什么在 handler 外不能使用 STACKED?

STACKED 依赖 condition handler 保存的上一层诊断上下文,只能在 handler 中读取;普通 SQL 后的即时诊断应使用 CURRENT。

为什么不一直读取 CONDITION 1?

一条语句可能产生多个 condition,MySQL 不保证编号 1 就是你想记录的主错误。先取得 NUMBER,再按实际编号取值更稳妥。

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