MySQL原生不支持TRY CATCH,需用DECLARE CONTINUE HANDLER FOR SQLSTATE '23000'捕获唯一约束冲突等错误,不可用错误号1062;必须前置声明、显式设标志变量,并注意其仅作用于当前BEGIN...END块。

MySQL不支持TRY CATCH语法,但可以用DECLARE HANDLER模拟
MySQL原生没有TRY...CATCH语句,这是和SQL Server、PostgreSQL最直观的差异。想在存储过程中捕获异常(比如主键冲突、除零、数据截断),必须用DECLARE HANDLER配合错误条件定义。它不是“包围式”结构,而是“声明式监听”,一旦触发对应错误,就跳转执行指定逻辑。
如何声明一个处理SQLSTATE '23000'(唯一约束冲突)的HANDLER
SQLSTATE '23000'覆盖了主键/唯一索引冲突、外键约束失败等常见写入错误。注意不能只写错误号(如1062),因为不同错误可能共享同一MySQL错误号,而SQLSTATE更标准、可移植性更好。
实操建议:
- 用
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000',而不是EXIT——否则HANDLER执行完存储过程就退出,后续逻辑无法继续 - 在HANDLER内部,必须显式设置一个标志变量(如
SET duplicate_found = TRUE),否则外部无法感知是否发生了异常 - HANDLER作用域仅限当前BEGIN...END块,嵌套块需各自声明
示例片段:
DECLARE duplicate_found BOOLEAN DEFAULT FALSE; DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET duplicate_found = TRUE; INSERT INTO users (id, name) VALUES (123, 'Alice'); IF duplicate_found THEN SELECT 'Duplicate key ignored' AS msg; END IF;
为什么直接用MySQL错误号(如1062)容易出错
MySQL错误号(ER_DUP_ENTRY = 1062)看似直观,但存在两个实际问题:
- 同一错误号可能对应多种SQLSTATE(例如1062既可能是主键冲突,也可能是唯一索引冲突,但SQLSTATE都是'23000';而1292这类警告则属于'HY000')
- 某些客户端或ORM(如JDBC)默认屏蔽错误号,只暴露SQLSTATE,导致HANDLER失效
- 跨MySQL版本时,错误号偶有变动,SQLSTATE更稳定
所以优先用SQLSTATE,仅在明确需要捕获特定MySQL行为(如DECLARE HANDLER FOR 1292处理截断警告)时才用错误号。
常见陷阱:HANDLER不捕获SELECT中的NOT FOUND,也不处理语法错误
MySQL的NOT FOUND条件(如游标到末尾)和SQLWARNING/SQLEXCEPTION是三类独立机制,不能混用:
-
DECLARE HANDLER FOR NOT FOUND只对SELECT ... INTO无结果、游标FETCH失败生效,不是通用异常捕获 - 语法错误(如
SELEC *)、权限错误(ER_DBACCESS_DENIED_ERROR)属于编译期或连接期问题,HANDLER完全不生效 - HANDLER无法捕获存储函数内抛出的错误——它只作用于当前存储过程/函数体内的SQL语句
真正需要“兜底”的场景(比如关键事务必须记录日志),得靠应用层重试+MySQL错误码解析,别指望单靠HANDLER全覆盖。
HANDLER的逻辑流是隐式的、基于错误传播的,和TRY CATCH那种显式代码块完全不同。写的时候要反复确认错误是否真的会被触发、是否被上层块的HANDLER覆盖、标志变量有没有被正确读取——这些地方最容易漏掉调试输出,上线后才发现静默失败。


















