MySQL 错误处理简介

现世界中,根本不存在完美的程序,不管多么完美的程序,总会抛出这样或那样的异常。MySQL 为了处理程序的错误,提供了错误处理,避免由于错误而导致程序崩溃,这将避免一些灾难性事故的发生。

例如,对于航天飞机的某类应用软件来说,这种的完美程度实际上是需求的一部分,因为软件的任何意外故障都将是灾难性的。然而,在商业应用程序的世界里,我们通常会对执行环境做出某些假设,我们假设 MySQL 服务器正在运行,我们的表没有被删除,主机没有着火等等。如果出现这些情况中的任何一种,我们就认为应用程序会失败。在许多其他情况下,我们应该预测潜在的故障,并编写代码来管理这些情况,这就是异常处理的作用所在。

在 MySQL 中,当存储程序遇到错误条件时,会停止执行并向调用程序返回错误信息。这是 MySQL 的默认行为。如果我们需要一种不同的行为呢?例如,我们想捕获错误、记录错误或报告错误,然后继续执行应用程序,该怎么办?为了实现这种控制,我们需要在程序中定义异常处理程序。

下面我们将介绍如何创建各种类型的异常处理程序,以及如何通过使用 “命名” 条件来提高错误处理的可读性。

未错误处理的示例

首先,我们来看一个存储程序错误处理的示例,下面将通过 MySQL 客户端创建一个根据参数插入用户信息的简单存储过程:

-- 准备一张用户表
mysql> select * from user;
+----+-------+-----+-----+----------------------+-------------+
| id | name  | age | sex | email                | phone       |
+----+-------+-----+-----+----------------------+-------------+
|  1 | 王五  |  23 | 1   | wangwu@sina.com      | 15810202050 |
|  2 | Helen |  26 | 0   | helen@outlook.com    | 15814402560 |
|  3 | Jemo  |  27 | 1   | jemo@163.com         | 15105603501 |
|  4 | Bill  |  33 | 1   | bill@gmail.com       | 15100055760 |
|  5 | 张三  |  21 | 0   | zhangsan@sina.com.cn | 15810232022 |
|  6 | 李四  |  19 | 1   | lisi@qq.com          | 15810122441 |
+----+-------+-----+-----+----------------------+-------------+
6 rows in set (0.07 sec)

-- 设置分隔符
mysql> delimiter //

-- 创建存储过程,根据参数插入一条记录到用户表
mysql> create procedure insert_user(
   ->   in in_id int,
   ->   in in_name varchar(50),
   ->   in in_age int,
   ->   in in_sex varchar(1)
   -> )
   -> begin
   ->   INSERT INTO `user`(`id`, `name`, `age`, `sex`) values (in_id, in_name, in_age, in_sex);
   -> end
   -> //
Query OK, 0 rows affected (0.00 sec)

-- 设置分隔符
mysql> delimiter ;

-- 调用存储过程,插入用户ID不存在的记录到数据库
mysql> call insert_user(7, 'Test', 30, 0);
Query OK, 1 row affected (0.01 sec)

-- 重复插入一条记录,抛出了错误
mysql> call insert_user(7, 'Test', 30, 0);
ERROR 1062 (23000): Duplicate entry '7' for key 'PRIMARY'

上面的示例中,当我们第二次调用 insert_user 存储过程插入 ID 为 7 的用户时,出现了 ERROR 1062 主键重复错误。

如果上面存储过程是由外部程序(如 Java)调用的,我们也许可以 “保持原样”,不会产生什么不好的影响。Java 和其他外部程序可以检测到此类错误,然后采取适当的措施进行处理。

如果上面存储过程是从另一个存储过程中调用,就有可能导致整个存储过程调用栈中止,这可能不是我们想要的结果。

既然我们可以预见到这个存储过程可能会引发 MySQL ERROR 1062 错误,那么我们就可以编写代码来处理这个特定的错误代码。

带错误处理的示例

下面示例演示了这种技术。存储过程不允许异常在未处理的情况下从存储过程中传播出去(导致调用程序出现故障),而是捕获异常,设置状态标志,并将状态信息返回给调用程序。然后,调用程序就可以决定是终止该故障,还是继续执行:

CREATE DEFINER=`root`@`localhost` PROCEDURE `insert_user`(
   IN in_id INT,
   IN in_name VARCHAR(50),
   IN in_age INT,
   IN in_sex VARCHAR(1),
   OUT out_status VARCHAR(200)
)
BEGIN
   -- 定义错误处理器
   -- 当执行 SQL 代码抛出 1062 错误时,触发该错误处理器
   -- 然后,将 'Duplicate Entry' 赋值给 out_status 变量
   DECLARE CONTINUE HANDLER FOR 1062 SET out_status='Duplicate Entry';
   -- 插入用户信息
   INSERT INTO `user`(`id`, `name`, `age`, `sex`) values (in_id, in_name, in_age, in_sex);
END

使用 MySQL 客户端调用上面存储过程,如下:

-- 定义一个用户变量,用来存放输出的错误信息
mysql> set @out_state='';
Query OK, 0 rows affected (0.00 sec)

-- 调用存储过程
mysql> call insert_user(7, 'Test', 30, 0, @out_state);
Query OK, 0 rows affected (0.00 sec)

-- 查看 @out_state 变量的值
mysql> select @out_state;
+-----------------+
| @out_state      |
+-----------------+
| Duplicate Entry |
+-----------------+
1 row in set (0.07 sec)

注意,我们将在后续章节详细介绍 HANDLER 子句的语法。现在,我们只需要理解 DECLARE CONTINUE HANDLER 语句告诉 MySQL:“如果遇到 ERROR 1062 (23000): Duplicate entry 错误,那么继续执行,但将变量 out_status 的值设置为 'Duplicate Entry'"。

根据调用结果可知,这种实现不会向调用程序返回错误,我们可以检查状态变量,看看存储过程是否执行成功。

下面示例展示另一个插入用户信息的存储过程,该过程调用上面过程插入用户基本信息,然后判断调用结果。如果调用失败,则输出错误信息,终止继续执行。如果调用成功,则修改用户的电话和邮件信息:

CREATE DEFINER=`root`@`localhost` PROCEDURE `insert_user_ext`(
   IN in_id INT,
   IN in_name VARCHAR(50),
   IN in_age INT,
   IN in_sex VARCHAR(1),
   IN in_phone varchar(200),
   IN in_email varchar(200))
myBegin:BEGIN
   DECLARE call_state VARCHAR(200) DEFAULT '';
   -- 调用 insert_user 过程插入用户基本信息
   CALL insert_user(in_id, in_name, in_age, in_sex, call_state);
   IF call_state = 'Duplicate Entry' THEN
       -- 直接输出错误消息
       SELECT '插入用户信息失败,ID冲突' as Error;
       -- 离开 BEGIN 语句块
       LEAVE myBegin;
   END IF;

   -- 更新,添加电话和邮件
   UPDATE `user` SET phont=in_phone, email=in_email WHERE id=in_id;
END myBegin

使用 MySQL 客户端调用上面存储过程,如下:

mysql> call insert_user_ext(7, 'Test', 30, 0, '15100223311', 'test@gmail.com');
+--------------------------+
| Error                    |
+--------------------------+
| 插入用户信息失败,ID冲突     |
+--------------------------+
1 row in set (0.03 sec)

Query OK, 0 rows affected (0.00 sec)
说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号