什麼是錯誤緩衝堆疊呢? 舉個很簡單的例子,比如執行下面一條語句:

mysql> INSERT INTO t_datetime VALUES(2,`4`,`5`);
ERROR 1292 (22007): Incorrect datetime value: `4` for column `log_time` at row 1

上面1292這個程式碼指示的錯誤資訊儲存在哪裡呢? 就儲存在錯誤緩衝堆疊, 在MySQL裡面叫 DIAGNOSTICS AREA。 關於這個概念,一直在MySQL5.7才得到確定的更新。 

在MySQL5.5之前,想要得到這塊區域的資料,就只能通過C的API來獲取,從SQL層面是無法檢索到的。MySQL5.5 先推出了這個概念。

在MySQL5.6釋出後,不但可以檢索這塊區域,而且還可以重新封裝,得到我們想要的資料。但是這塊區域依然是隻能儲存一次錯誤程式碼,很容易被重置。

在MySQL5.7釋出後,可以更加容易的檢索這塊區域,而且把這裡的資料放到一個STACK裡,重置的條件更加寬鬆。以下舉例子來說明。

示例表結構如下,

CREATE TABLE `t_datetime` (
  `id` int(11) NOT NULL,
  `log_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `end_time` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

用來記錄錯誤資料的日誌表。

CREATE TABLE tb_log (errorno int,errortext TEXT,error_timestamp DATETIME);

在MySQL5.6環境下,我要這樣寫一段繁雜的程式碼來獲取錯誤資訊。

DELIMITER $$
USE `new_feature`$$
DROP PROCEDURE IF EXISTS `sp_do_insert`$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_do_insert`(
IN f_id INT,
IN f_log_time VARCHAR(255),
IN f_end_time VARCHAR(255)
)
BEGIN
  DECLARE done1 TINYINT DEFAULT 0; -- 儲存是否發生異常的布林值。
  DECLARE i TINYINT DEFAULT 1;
  DECLARE v_errcount INT DEFAULT 0;  -- 獲取一次錯誤資料條數
  DECLARE v_errno INT DEFAULT 0; -- 獲取錯誤程式碼
  DECLARE v_msg TEXT; -- 獲取錯誤詳細資訊
  DECLARE CONTINUE HANDLER FOR SQLEXCEPTION -- 定義一個異常處理塊
  BEGIN
    SET done1 = 1; -- 發生異常,設定為1.
    get  diagnostics v_errcount = number;
    SET v_msg = ``;
    WHILE i <= v_errcount
    DO
      GET  DIAGNOSTICS CONDITION i
        v_errno = MYSQL_ERRNO, v_msg = MESSAGE_TEXT;
        SET @stmt = CONCAT(`select `,v_errno,`,"`,v_msg,`","`,NOW(),`" into @errno`,i,`,@msg`,i,`,@log_timestamp`,i,`;`);
        PREPARE s1 FROM @stmt;
        EXECUTE s1;
      SET i = i + 1;
    END WHILE;
    DROP PREPARE s1;
  END;
    INSERT INTO t_datetime (id,log_time,end_time) VALUES(f_id,f_log_time,f_end_time);
    IF done1 = 1 THEN -- 把錯誤資料記錄到表tb_log裡。
      SET i = 1;
      WHILE i <= v_errcount
      DO
        SET @stmt = CONCAT(`insert into tb_log `);
        SET @stmt = CONCAT(@stmt,` select @errno`,i,`,@msg`,i,`,@log_timestamp`);
        PREPARE s1 FROM @stmt;
        EXECUTE s1;
        SET i = i + 1;
      END WHILE;
      DROP PREPARE s1;
    END IF;
END$$
DELIMITER ;

MySQL5.7釋出後,現在可以精簡我的程式碼了。

DELIMITER $$
USE `new_feature`$$
DROP PROCEDURE IF EXISTS `sp_do_insert`$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `sp_do_insert`(
IN f_id INT,
IN f_log_time VARCHAR(255),
IN f_end_time VARCHAR(255)
)
BEGIN
  DECLARE i TINYINT DEFAULT 1;
  DECLARE v_errcount INT DEFAULT 0; -- 獲取一次錯誤資料條數
  DECLARE v_errno INT DEFAULT 0;  -- 獲取錯誤程式碼
  DECLARE v_msg TEXT; -- 獲取錯誤詳細資訊
  DECLARE CONTINUE HANDLER FOR SQLEXCEPTION  -- 定義一個異常處理塊
  BEGIN
    get stacked diagnostics v_errcount = number;
    WHILE i <= v_errcount
    DO
      GET stacked DIAGNOSTICS CONDITION i -- 把錯誤資料分別儲存在變數裡
        v_errno = MYSQL_ERRNO, v_msg = MESSAGE_TEXT;
        INSERT INTO tb_log VALUES (v_errno,v_msg,NOW());
      SET i = i + 1;
    END WHILE;
  END;
    INSERT INTO t_datetime (id,log_time,end_time) VALUES(f_id,f_log_time,f_end_time);  
END$$
DELIMITER ;

現在來執行下:

mysql> call sp_do_insert(2,`4`,`5`);
Query OK, 1 row affected (0.01 sec)

來檢索表tb_log的資料。

mysql> select * from tb_logG
*************************** 1. row ***************************
        errorno: 1265
      errortext: Data truncated for column `log_time` at row 1
error_timestamp: 2015-11-17 11:53:10
*************************** 2. row ***************************
        errorno: 1265
      errortext: Data truncated for column `end_time` at row 1
error_timestamp: 2015-11-17 11:53:10
*************************** 3. row ***************************
        errorno: 1062
      errortext: Duplicate entry `2` for key `PRIMARY`
error_timestamp: 2015-11-17 11:53:10
3 rows in set (0.00 sec)

總結下, 如果先用到DIAGNOSTICS AREA, 最好是在儲存過程裡面寫程式碼封裝SQL。