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

MySQL SIGNAL 怎样在存储程序中定义自定义异常

来源:17golang原创

时间:2026-10-20 22:55:48 132浏览 收藏

存储程序里遇到参数不合法、业务状态不允许或数据边界不满足时,最容易出现的临时方案是故意访问一张不存在的表,让 MySQL “替你报错”。更稳妥的做法是使用 SIGNAL 主动返回异常,把 SQLSTATE、错误码和消息一次写进调用方能识别的错误契约。

官方地址:https://dev.mysql.com/doc/refman/8.4/en/signal.html

要点速览
  • SIGNAL 的核心是先选择一个合法的 SQLSTATE,再用 SET 填写异常信息。
  • 通用用户自定义异常通常使用 '45000';不要使用以 '00' 开头的成功类 SQLSTATE。
  • DECLARE CONDITION 可以给 SQLSTATE 起名字,但命名条件供 SIGNAL 使用时应基于 SQLSTATE,而不是只绑定 MySQL 错误号。
  • SIGNAL 主动创建异常,handler 负责捕获,RESIGNAL 负责在 handler 中转发或修改已有异常。

先用 SQLSTATE 划定异常类别

SIGNAL 的第一步不是填写中文提示,而是确定异常类别。MySQL 8.4 文档规定,condition_value 可以直接写五字符的 SQLSTATE,也可以引用一个此前声明的命名条件。SQLSTATE 前两位表达类别,'45000' 是常用的“未处理用户自定义异常”。

CREATE PROCEDURE create_order(IN p_amount DECIMAL(10, 2))
BEGIN
    -- 金额不满足业务约束时,主动返回用户自定义异常
    IF p_amount 

如果需要表达更具体的条件,可以选用合适的 SQLSTATE,而不是把所有异常都塞进一个字符串。需要特别注意的是,SQLSTATE 不能以 '00' 开头,因为这一类表示成功;写错时 MySQL 会报告 Bad SQLSTATE。

MySQL SIGNAL 的 SQLSTATE、命名条件、存储程序作用域和异常出口之间的静态关系说明图
图1:SQLSTATE 与命名条件的关系图,展示存储程序作用域中的异常定义边界;这是静态说明图,不是截图或运行证据。

用 SIGNAL SET 填写错误契约

只返回一个 SQLSTATE 往往不够。应用端通常还需要稳定的错误号和可读消息,这时使用 SET 一次填写 MESSAGE_TEXT 与 MYSQL_ERRNO。同一个信息项在一条 SIGNAL 中只能出现一次,值可以来自存储程序参数、局部变量、系统变量或字面量。

CREATE PROCEDURE create_order(IN p_amount DECIMAL(10, 2))
BEGIN
    DECLARE v_message VARCHAR(128);

    -- 先把可复用的提示组装好,避免异常消息和判断条件脱节
    SET v_message = CONCAT('订单金额无效: ', p_amount);

    IF p_amount 

这里的职责分工很清楚:SQLSTATE 适合做异常类别判断,MYSQL_ERRNO 适合映射内部错误码,MESSAGE_TEXT 适合给日志或界面提供上下文。不要把关键业务分支只建立在可变的中文消息上。

MySQL SIGNAL 的 SQLSTATE、MYSQL_ERRNO、MESSAGE_TEXT 与客户端错误契约之间的静态关系说明图
图2:SIGNAL 信息项关系图,展示异常字段如何组成面向客户端的稳定错误契约;这是静态说明图,不是截图或运行证据。

用 DECLARE CONDITION 管理重复条件

当同一种异常会在多个分支出现时,可以在适用的复合语句块中先声明命名条件。命名条件让正文不必重复散落的 SQLSTATE 字符串,也能把“条件叫什么”和“条件是什么”分开维护。

CREATE PROCEDURE reserve_stock(IN p_stock INT)
BEGIN
    -- 命名条件绑定 SQLSTATE,便于在多个分支复用
    DECLARE stock_empty CONDITION FOR SQLSTATE '45000';

    IF p_stock 

声明作用域是迁移时的重点:内层同名条件会遮蔽外层同名条件;如果命名条件不在 SIGNAL 所处作用域内,会出现未定义条件错误。另一个常见误区是用 MySQL 错误号声明命名条件后再直接 SIGNAL,这种条件并不满足 SIGNAL 对命名条件的要求。

区分 SIGNAL、handler 与 RESIGNAL

这三个语句经常被混在同一段存储程序里,但它们的职责不同:SIGNAL 是主动创建条件,DECLARE ... HANDLER 是声明发生条件后的处理动作,RESIGNAL 则只能在条件处理器的上下文里转发已有条件,并可修改部分信息。

CREATE PROCEDURE import_one(IN p_code VARCHAR(32))
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        -- handler 负责捕获;RESIGNAL 负责保留异常并补充统一消息
        RESIGNAL SET MESSAGE_TEXT = '导入订单失败';
    END;

    IF p_code IS NULL OR p_code = '' THEN
        -- 这是主动创建异常,不是对已有异常做转发
        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = '订单编码不能为空',
                MYSQL_ERRNO = 30003;
    END IF;
END;
-- 不在 handler 中调用 RESIGNAL,会得到 handler not active 类错误

如果需求是“发现业务条件不满足,直接给客户端一个新异常”,用 SIGNAL。如果需求是“捕获底层异常后保留它,再补充统一上下文”,才考虑 RESIGNAL。这条边界能避免为了统一消息而意外丢失原始 SQLSTATE。

按迁移清单收紧异常边界

将旧的“制造一个数据库错误”改成 SIGNAL 时,可以把检查拆成四个可执行项目。它们不是额外的运行时验证,而是写存储程序时应固定下来的接口约定。

检查项推荐做法常见迁移风险
SQLSTATE选择合法的五字符值,用户异常优先考虑 45000使用 00 开头的成功类值
错误信息用 MYSQL_ERRNO 和 MESSAGE_TEXT 分别承载机器码与可读提示让客户端解析中文消息判断分支
命名条件在当前作用域用 SQLSTATE 定义并复用把 MySQL 错误号当成可直接 SIGNAL 的命名条件
异常流转主动抛出用 SIGNAL,handler 内转发用 RESIGNAL在 handler 外使用 RESIGNAL,或覆盖掉原始错误

最终可以把异常契约写进存储程序的接口文档:调用方先按 SQLSTATE 判断大类,再按错误号分支,最后把消息写入日志或展示给用户。这样数据库从“偶尔报错的黑盒”变成了可以随版本迁移、可被客户端稳定消费的错误边界。

常见问题

SIGNAL 只能写在存储过程中吗?

不只能。MySQL 文档说明它也可以出现在触发器、事件或客户端直接执行的语句中;本文聚焦存储程序的自定义异常契约。

MESSAGE_TEXT 和 MYSQL_ERRNO 哪个更重要?

两者用途不同。错误号适合稳定分支判断,消息适合上下文展示;不要用消息文本替代机器可读的错误码。

什么时候应该使用 RESIGNAL?

当当前代码已经处于条件处理器中,并且希望把已有异常继续向外传递或补充信息时使用;主动创建新条件仍然使用 SIGNAL。

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