在MySQL的存储过程、函数等可编程对象中,默认的错误处理机制较为笼统,当业务需要针对特定场景触发自定义错误,或者需要对某类异常做专属处理时,就需要用到自定义条件和处理程序。这两个特性可以让开发者灵活控制程序的异常流程,避免程序因未处理的错误直接中断。

什么是自定义条件
自定义条件是开发者根据业务需求定义的错误触发规则,相当于给特定的错误场景起一个专属的名称,后续可以在处理程序中直接引用这个名称,而不用记住复杂的错误码。MySQL中通过DECLARE ... CONDITION语句来定义自定义条件,语法格式如下:
-- 方式1:关联已有的MySQL错误码 DECLARE 条件名称 CONDITION FOR 错误码; -- 方式2:关联SQLSTATE值 DECLARE 条件名称 CONDITION FOR SQLSTATE 'sqlstate值';
其中错误码是MySQL内置的数字错误编号,比如1062代表唯一键冲突;SQLSTATE是符合ANSI SQL标准的5位字符串错误标识,比如23000代表完整性约束违反。自定义条件的名称需要符合MySQL的标识符命名规则,建议使用有意义的名称,方便后续维护。
什么是处理程序
处理程序是用来定义当某个条件被触发时,程序应该执行的处理逻辑,通过DECLARE ... HANDLER语句声明,语法格式如下:
DECLARE 处理类型 HANDLER FOR 条件值 执行语句;
处理类型有两种取值:
- CONTINUE:当条件触发后,执行完处理语句,程序继续往下执行
- EXIT:当条件触发后,执行完处理语句,程序直接退出当前声明的BEGIN...END块
条件值可以是以下几种形式:
- 具体的错误码,比如1062
- SQLSTATE值,比如23000
- 已经定义好的自定义条件名称
- 预定义的条件,比如SQLEXCEPTION(代表所有SQL异常)、SQLWARNING(代表所有警告)、NOT FOUND(代表无数据返回)
自定义条件和处理程序的结合使用
实际开发中通常会先定义自定义条件,再声明对应的处理程序,让代码逻辑更清晰。下面通过一个完整的存储过程示例,演示如何实现用户注册时的唯一手机号校验,当手机号已存在时触发自定义错误,并给出友好提示。
示例:用户注册唯一手机号校验
首先创建用户表,表中手机号字段设置唯一约束:
CREATE TABLE IF NOT EXISTS user_info (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
phone VARCHAR(20) NOT NULL UNIQUE COMMENT '手机号,唯一',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
接下来创建存储过程,定义唯一冲突的自定义条件,以及对应的处理程序:
DELIMITER //
CREATE PROCEDURE proc_register_user(
IN p_username VARCHAR(50),
IN p_phone VARCHAR(20),
OUT p_result VARCHAR(100)
)
BEGIN
-- 定义自定义条件:关联唯一键冲突的SQLSTATE 23000
DECLARE duplicate_phone CONDITION FOR SQLSTATE '23000';
-- 定义处理程序:当触发duplicate_phone条件时,设置返回结果并继续后续逻辑
DECLARE CONTINUE HANDLER FOR duplicate_phone
BEGIN
SET p_result = '注册失败:该手机号已被注册';
END;
-- 初始化返回结果
SET p_result = '注册成功';
-- 插入用户数据,如果手机号重复会触发唯一约束异常
INSERT INTO user_info (username, phone) VALUES (p_username, p_phone);
-- 如果插入成功,p_result保持初始值;如果触发异常,p_result会被处理程序修改
END //
DELIMITER ;
调用存储过程测试效果:
-- 第一次注册,正常插入
CALL proc_register_user('张三', '13800138000', @result);
SELECT @result; -- 输出:注册成功
-- 第二次用相同手机号注册,触发唯一冲突
CALL proc_register_user('李四', '13800138000', @result);
SELECT @result; -- 输出:注册失败:该手机号已被注册
处理程序的生效范围
处理程序的声明有作用域限制,它只对当前BEGIN...END块以及嵌套在该块内的子块生效。如果外层块声明了处理程序,内层块没有声明对应的处理程序,那么内层块触发条件时,会向外层查找匹配的处理程序。如果内外层都没有匹配的处理程序,程序会直接抛出错误并终止。
下面的示例演示了处理程序的作用域规则:
DELIMITER //
CREATE PROCEDURE proc_handler_scope()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
SELECT '外层捕获到SQL异常' AS result;
END;
-- 外层块逻辑
SELECT '进入外层块' AS result;
-- 内层块
BEGIN
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000'
BEGIN
SELECT '内层捕获到唯一冲突异常' AS result;
END;
SELECT '进入内层块' AS result;
-- 触发唯一冲突,内层有对应处理程序,执行后继续
INSERT INTO user_info (username, phone) VALUES ('测试', '13800138000');
SELECT '内层块执行完成' AS result;
END;
SELECT '外层块执行完成' AS result;
END //
DELIMITER ;
调用该存储过程,因为第一次插入时手机号已经存在,内层处理程序会捕获唯一冲突,执行后继续内层逻辑,最后外层逻辑也正常执行完成,不会触发外层的SQLEXCEPTION处理程序。
注意事项
- 自定义条件和处理程序的声明必须放在存储过程、函数的变量声明之后,其他执行语句之前,否则会报语法错误
- 处理程序中如果要执行多条语句,需要用BEGIN...END包裹,单条语句可以直接写
- 如果同时使用错误码、SQLSTATE、自定义条件匹配同一个异常,MySQL会优先匹配更具体的条件,匹配顺序为:自定义条件 > SQLSTATE > 错误码 > 预定义条件
- NOT FOUND条件通常用于游标遍历的场景,当游标读取不到数据时触发,需要注意和SQL异常区分开
自定义条件和处理程序是MySQL可编程对象中非常重要的特性,合理使用可以大幅提升异常处理的灵活性,让数据库程序更健壮,也更符合业务的实际需求。建议在编写复杂的存储过程、函数时,提前规划好需要处理的异常场景,针对性定义条件和处理逻辑。