为mysql增加show error函数

本文介绍了一种在MySQL存储过程中捕获并记录异常信息的方法,通过使用UDF扩展函数配合自定义错误处理逻辑,实现详细错误信息的获取。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  mysql在存储过程异常处理方面有个很大的缺点,就是在异常处理块中不能打印出错误信息和错误号等信息。这给我们记录错误日志带来了很大的不便。
 《MySQL Stored Procedure Programming》里提到了jdbc的异常处理可以捕捉到mysql的错误,代码如下:
 
  try {
    Class.forName("com.mysql.jdbc.Driver").newInstance(  );

    String ConnectionString="jdbc:mysql://" + hostname + "/" + database + "?user=" +
           username + "&password=" + password;
    System.out.println(ConnectionString);
    Connection conn = DriverManager.getConnection(ConnectionString);
    Statement stmt=conn.createStatement(  );
    stmt.execute("call error_test_proc(1)");
  }
  catch(SQLException SQLEx) {
    System.out.println("MySQL error: "+SQLEx.getErrorCode(  )+
            " SQLSTATE:" +SQLEx.getSQLState(  ));
     System.out.println(SQLEx.getMessage(  ));


  可不可以借用jdbc的处理方式呢?看一下mysql jdbc在异常处理这块的code, 却发现是用"show errors"发送数据库命令,然后解析返回数据集的方式。
  显然,我们在mysql的存储过程中,是无法直接用"show errors"的方式来记录错误信息的。
 
  到这里,似乎已经无路可走了。其实mysql在走自己路的同时,也留了路给开发者,比如udf。
  udf以动态链接库的方式注入到数据库连接环境中,这就给我们提供了一个可以进入mysql server的后门,从而获取mysql server的各种运行时信息。并且很重要的,udf是用较温和的方式实现了mysql接口的扩展。
 
  以下是我用用udf来返回mysql错误信息。主体代码如下:
  extern "C" my_bool showerr_udf_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
  {
   int msgLen = 0;
   char errCode[50];
   THD* thd = current_thd;
   MYSQL_ERROR *err;
   err = (MYSQL_ERROR*)(thd->warn_list.first_node()->info);
 
   if (NULL != err)
   {
    sprintf(errCode,"error code: %d ",err->code);
    msgLen = strlen(err->msg) + strlen(errCode) + 1;
 
    if (!(initid->ptr=(char*) malloc(msgLen)))
    {
     strmov(message,"Couldn't allocate memory");
     return 1;
    }
    bzero(initid->ptr,msgLen);
    strcpy(initid->ptr,errCode);
    strcat(initid->ptr,err->msg);
    initid->max_length = msgLen;
   }
 
   initid->maybe_null = 1;
   return 0;
  }
 

  编译后,注册udf:
  mysql> CREATE FUNCTION showerr_udf RETURNS STRING SONAME "udf_example.dll";
  Query OK, 0 rows affected (0.02 sec)
 
  再做个简单的测试:
 
  mysql> select k from tab_not_exists;
  ERROR 1146 (42S02): Table 'test.tab_not_exists' doesn't exist
 
  mysql> select showerr_udf();
  +------------------------------------------------------------+
  | showerr_udf()                                              |
  +------------------------------------------------------------+
  | error code: 1146 Table 'test.tab_not_exists' doesn't exist |
  +------------------------------------------------------------+
  1 row in set (0.00 sec)
 
  看起来不错。
  再把showerr_udf()放到存储过程里试试看。
 
  1: 创建测试表:
  create table department(id int, name varchar(100)) engine innodb;
 
  2:创建测试存储过程:
 
  DELIMITER $$
  DROP PROCEDURE IF EXISTS sp_test$$

  CREATE PROCEDURE sp_test()
     READS SQL DATA
  BEGIN
     DECLARE l_last_row INT DEFAULT 0;
     DECLARE l_dept_id  INT;
     DECLARE l_err  varchar(1000);
     DECLARE c_dept CURSOR FOR
         SELECT name FROM department where id=1;
    
        
    /* handler to set l_last_row=1 if a cursor returns no more rows */
     DECLARE CONTINUE HANDLER FOR NOT FOUND
     BEGIN
         SET l_last_row=1;
         SET l_err = showerr_udf();
     END;
 
     OPEN c_dept;
     dept_cursor: LOOP
          FETCH c_dept INTO l_dept_id;
          IF (l_last_row=1) THEN
               LEAVE dept_cursor;
          END IF;
     END LOOP dept_cursor;
     CLOSE c_dept;
    
     SELECT l_err;
  END$$
 
  DELIMITER ;
 
  3:调用测试
  mysql> call sp_test();
  +----------------------------------------------------------------------+
  | l_err                                                                |
  +----------------------------------------------------------------------+
  | NULL                

    |
  +----------------------------------------------------------------------+
  1 row in set (0.00 sec)

  返回的结果居然是空字符串。于是做了个猜测:一旦在存储过程中定义了异常处理,那么mysql会把错误静默掉,
且不会把错误信息保存到thd线程的错误列表里。(这个猜测可以用"show errors"命令同样不返回错误信息来侧面印证。)
  
  然后果真在mysql的代码里看到了这样的注解(mysqld.cc的my_message_sql函数):
   /*
      If a continue handler is found, the error message will be cleared
      by the stored procedures code.
    */
    if (thd->spcont &&
        ! (MyFlags & ME_NO_SP_HANDLER) &&
        thd->spcont->handle_error(error, MYSQL_ERROR::WARN_LEVEL_ERROR, thd))
    {
      /*
        Do not push any warnings, a handled error must be completely
        silenced.
      */
      DBUG_RETURN(0);
    }

 

  找到了原因,就可以尝试去改变。在上面的代码中加入push_warning方法(mysql自己的方法),如下:
  /*
      If a continue handler is found, the error message will be cleared
      by the stored procedures code.
    */
    if (thd->spcont &&
        ! (MyFlags & ME_NO_SP_HANDLER) &&
        thd->spcont->handle_error(error, MYSQL_ERROR::WARN_LEVEL_ERROR, thd))
    {
      /*
        Do not push any warnings, a handled error must be completely
        silenced.
      */
      push_warning(thd, MYSQL_ERROR::WARN_LEVEL_ERROR, error, str);
     
      DBUG_RETURN(0);
    }
 
  还需要在mysql错误处理模块中,注释掉吞异常的代码:
  if (thd->handle_error(code, msg, level))
    DBUG_RETURN(NULL);

  if (thd->spcont &&
      thd->spcont->handle_error(code, level, thd))
  {
    DBUG_RETURN(NULL);
  }
 
  重新构建,重启mysql server。再做一次测试:
  mysql> call sp_test();
  +----------------------------------------------------------------------+
  | l_err                                                                |
  +----------------------------------------------------------------------+
  | error code: 1329 No data - zero rows fetched, selected, or processed |
  +----------------------------------------------------------------------+
  1 row in set (0.00 sec)
 
  Query OK, 0 rows affected, 1 warning (0.02 sec)
 
  终于可以在mysql的存储过程中,记录详细的错误信息了。虽然部分解决了问题(因为没做全面测试),但却修改了mysql的服务器代码,偏离了一开始的期望。希望mysql能在后续的版本中修正这样的问题,毕竟对于异常处理来说,打印错误信息和错误号,以及重抛异常等是一个很基本的要求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值