Mysql高手系列 - 第20篇:異常捕獲及處理詳解(實戰經驗)

Mysql系列的目標是:經過這個系列從入門到全面掌握一個高級開發所須要的所有技能。html

這是Mysql系列第20篇。java

環境:mysql5.7.25,cmd命令中進行演示。mysql

代碼中被[]包含的表示可選,|符號分開的表示可選其一。sql

需求背景

咱們在寫存儲過程的時候,可能會出現下列一些狀況:微信

  1. 插入的數據違反惟一約束,致使插入失敗
  2. 插入或者更新數據超過字段最大長度,致使操做失敗
  3. update影響行數和指望結果不一致

遇到上面各類異常狀況的時,可能須要咱們可以捕獲,而後可能須要回滾當前事務。併發

本文主要圍繞異常處理這塊作詳細的介紹。函數

此時咱們須要使用遊標,經過遊標的方式來遍歷select查詢的結果集,而後對每行數據進行處理。優化

本篇內容

  • 異常分類詳解
  • 內部異常詳解
  • 外部異常詳解
  • 掌握樂觀鎖解決併發修改數據出錯的問題
  • update影響行數和指望結果不一致時的處理

準備數據

建立庫:javacode2018spa

建立表:test1,test1表中的a字段爲主鍵。3d

/*建庫javacode2018*/
drop database if exists javacode2018;
create database javacode2018;

/*切換到javacode2018庫*/
use javacode2018;

DROP TABLE IF EXISTS test1;
CREATE TABLE test1(a int PRIMARY KEY);

異常分類

咱們將異常分爲mysql內部異常和外部異常

mysql內部異常

當咱們執行一些sql的時候,可能違反了mysql的一些約束,致使mysql內部報錯,如插入數據違反惟一約束,更新數據超時等,此時異常是由mysql內部拋出的,咱們將這些由mysql拋出的異常統稱爲內部異常。

外部異常

當咱們執行一個update的時候,可能咱們指望影響1行,可是實際上影響的不是1行數據,這種狀況:sql的執行結果和指望的結果不一致,這種狀況也咱們也把他做爲外部異常處理,咱們將sql執行結果和指望結果不一致的狀況統稱爲外部異常。

Mysql內部異常

示例1

test1表中的a字段爲主鍵,咱們向test1表同時插入2條數據,而且放在一個事務中執行,最終要麼都插入成功,要麼都失敗。

建立存儲過程:
/*刪除存儲過程*/
DROP PROCEDURE IF EXISTS proc1;
/*聲明結束符爲$*/
DELIMITER $
/*建立存儲過程*/
CREATE PROCEDURE proc1(a1 int,a2 int)
  BEGIN
    START TRANSACTION;
    INSERT INTO test1(a) VALUES (a1);
    INSERT INTO test1(a) VALUES (a2);
    COMMIT;
  END $
/*結束符置爲;*/
DELIMITER ;

上面存儲過程插入了兩條數據,a的值都是1。

驗證結果:
mysql> DELETE FROM test1;
Query OK, 0 rows affected (0.00 sec)

mysql> CALL proc1(1,1);
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
mysql> SELECT * from test1;
+---+
| a |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

上面先刪除了test1表中的數據,而後調用存儲過程proc1,因爲test1表中的a字段是主鍵,插入第二條數據時違反了a字段的主鍵約束,mysql內部拋出了異常,致使第二條數據插入失敗,最終只有第一條數據插入成功了。

上面的結果和咱們指望的不一致,咱們但願要麼都插入成功,要麼失敗。

那咱們怎麼作呢?咱們須要捕獲上面的主鍵約束異常,而後發現有異常的時候執行rollback回滾操做,改進上面的代碼,看下面示例2。

示例2

咱們對上面示例進行改進,捕獲上面主鍵約束異常,而後進行回滾處理,以下:

建立存儲過程:
/*刪除存儲過程*/
DROP PROCEDURE IF EXISTS proc2;
/*聲明結束符爲$*/
DELIMITER $
/*建立存儲過程*/
CREATE PROCEDURE proc2(a1 int,a2 int)
  BEGIN
    /*聲明一個變量,標識是否有sql異常*/
    DECLARE hasSqlError int DEFAULT FALSE;
    /*在執行過程當中出任何異常設置hasSqlError爲TRUE*/
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET hasSqlError=TRUE;

    /*開啓事務*/
    START TRANSACTION;
    INSERT INTO test1(a) VALUES (a1);
    INSERT INTO test1(a) VALUES (a2);

    /*根據hasSqlError判斷是否有異常,作回滾和提交操做*/
    IF hasSqlError THEN
      ROLLBACK;
    ELSE
      COMMIT;
    END IF;
  END $
/*結束符置爲;*/
DELIMITER ;
上面重點是這句:
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET hasSqlError=TRUE;

當有sql異常的時候,會將變量hasSqlError的值置爲TRUE

模擬異常狀況:
mysql> DELETE FROM test1;
Query OK, 2 rows affected (0.00 sec)

mysql> CALL proc2(1,1);
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * from test1;
Empty set (0.00 sec)

上面插入了2條同樣的數據,插入失敗,能夠看到上面test1表無數據,和指望結果一致,插入被回滾了。

模擬正常狀況:
mysql> DELETE FROM test1;
Query OK, 0 rows affected (0.00 sec)

mysql> CALL proc2(1,2);
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * from test1;
+---+
| a |
+---+
| 1 |
| 2 |
+---+
2 rows in set (0.00 sec)

上面插入了2條不一樣的數據,最終插入成功。

外部異常

外部異常不是由mysql內部拋出的錯誤,而是因爲sql的執行結果和咱們指望的結果不一致的時候,咱們須要對這種狀況作一些處理,如回滾操做。

示例1

咱們來模擬電商中下單操做,按照上面的步驟來更新帳戶餘額。

電商中有個帳戶表和訂單表,以下:
DROP TABLE IF EXISTS t_funds;
CREATE TABLE t_funds(
  user_id INT PRIMARY KEY COMMENT '用戶id',
  available DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '帳戶餘額'
) COMMENT '用戶帳戶表';
DROP TABLE IF EXISTS t_order;
CREATE TABLE t_order(
  id int PRIMARY KEY AUTO_INCREMENT COMMENT '訂單id',
  price DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '訂單金額'
) COMMENT '訂單表';
delete from t_funds;
/*插入一條數據,用戶id爲1001,餘額爲1000*/
INSERT INTO t_funds (user_id,available) VALUES (1001,1000);
下單操做涉及到操做上面的帳戶表,咱們用存儲過程來模擬實現:
/*刪除存儲過程*/
DROP PROCEDURE IF EXISTS proc3;
/*聲明結束符爲$*/
DELIMITER $
/*建立存儲過程*/
CREATE PROCEDURE proc3(v_user_id int,v_price decimal(10,2),OUT v_msg varchar(64))
  a:BEGIN
    DECLARE v_available DECIMAL(10,2);

    /*1.查詢餘額,判斷餘額是否夠*/
    select a.available into v_available from t_funds a where a.user_id = v_user_id;
    if v_available<=v_price THEN
      SET v_msg='帳戶餘額不足!';
      /*退出*/
      LEAVE a;
    END IF;

    /*模擬耗時5秒*/
    SELECT sleep(5);

    /*2.餘額減去price*/
    SET v_available = v_available - v_price;

    /*3.更新餘額*/
    START TRANSACTION;
    UPDATE t_funds SET available = v_available WHERE user_id = v_user_id;

    /*插入訂單明細*/
    INSERT INTO t_order (price) VALUES (v_price);

    /*提交事務*/
    COMMIT;
    SET v_msg='下單成功!';
  END $
/*結束符置爲;*/
DELIMITER ;

上面過程主要分爲3步驟:驗證餘額、修改餘額變量、更新餘額。

開啓2個cmd窗口,鏈接mysql,同時執行下面操做:
USE javacode2018;
CALL proc3(1001,100,@v_msg);
select @v_msg;
而後執行:
mysql> SELECT * FROM t_funds;
+---------+-----------+
| user_id | available |
+---------+-----------+
|    1001 |    900.00 |
+---------+-----------+
1 row in set (0.00 sec)

mysql> SELECT * FROM t_order;
+----+--------+
| id | price  |
+----+--------+
|  1 | 100.00 |
|  2 | 100.00 |
+----+--------+
2 rows in set (0.00 sec)

上面出現了很是嚴重的錯誤:下單成功了2次,可是帳戶只扣了100。

上面過程是因爲2個操做併發致使的,2個窗口同時執行第一步的時候看到了同樣的數據(看到的餘額都是1000),而後繼續向下執行,最終致使結果出問題了。

上面操做咱們能夠使用樂觀鎖來優化。

樂觀鎖的過程:用指望的值和目標值進行比較,若是相同,則更新目標值,不然什麼也不作。

樂觀鎖相似於java中的cas操做,這塊須要瞭解的能夠點擊:詳解CAS

咱們能夠在資金錶t_funds添加一個version字段,表示版本號,每次更新數據的時候+1,更新數據的時候將version做爲條件去執行update,根據update影響行數來判斷執行是否成功,優化上面的代碼,見示例2

示例2

對示例1進行優化。

建立表:
DROP TABLE IF EXISTS t_funds;
CREATE TABLE t_funds(
  user_id INT PRIMARY KEY COMMENT '用戶id',
  available DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '帳戶餘額',
  version INT DEFAULT 0 COMMENT '版本號,每次更新+1'
) COMMENT '用戶帳戶表';

DROP TABLE IF EXISTS t_order;
CREATE TABLE t_order(
  id int PRIMARY KEY AUTO_INCREMENT COMMENT '訂單id',
  price DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '訂單金額'
)COMMENT '訂單表';
delete from t_funds;
/*插入一條數據,用戶id爲1001,餘額爲1000*/
INSERT INTO t_funds (user_id,available) VALUES (1001,1000);
建立存儲過程:
/*刪除存儲過程*/
DROP PROCEDURE IF EXISTS proc4;
/*聲明結束符爲$*/
DELIMITER $
/*建立存儲過程*/
CREATE PROCEDURE proc4(v_user_id int,v_price decimal(10,2),OUT v_msg varchar(64))
    a:BEGIN
    /*保存當前餘額*/
    DECLARE v_available DECIMAL(10,2);
    /*保存版本號*/
    DECLARE v_version INT DEFAULT 0;
    /*保存影響的行數*/
    DECLARE v_update_count INT DEFAULT 0;


    /*1.查詢餘額,判斷餘額是否夠*/
    select a.available,a.version into v_available,v_version from t_funds a where a.user_id = v_user_id;
    if v_available<=v_price THEN
      SET v_msg='帳戶餘額不足!';
      /*退出*/
      LEAVE a;
    END IF;

    /*模擬耗時5秒*/
    SELECT sleep(5);

    /*2.餘額減去price*/
    SET v_available = v_available - v_price;

    /*3.更新餘額*/
    START TRANSACTION;
    UPDATE t_funds SET available = v_available WHERE user_id = v_user_id AND version = v_version;
    /*獲取上面update影響行數*/
    select ROW_COUNT() INTO v_update_count;

    IF v_update_count=1 THEN
      /*插入訂單明細*/
      INSERT INTO t_order (price) VALUES (v_price);
      SET v_msg='下單成功!';
      /*提交事務*/
      COMMIT;
    ELSE
      SET v_msg='下單失敗,請重試!';
      /*回滾事務*/
      ROLLBACK;
    END IF;
  END $
/*結束符置爲;*/
DELIMITER ;

ROW_COUNT()能夠獲取更新或插入後獲取受影響行數。將受影響行數放在v_update_count中。

而後根據v_update_count是否等於1判斷更新是否成功,若是成功則記錄訂單信息並提交事務,不然回滾事務。

驗證結果:開啓2個cmd窗口,鏈接mysql,執行下面操做:
use javacode2018;
CALL proc4(1001,100,@v_msg);
select @v_msg;
窗口1結果:
mysql> CALL proc4(1001,100,@v_msg);
+----------+
| sleep(5) |
+----------+
|        0 |
+----------+
1 row in set (5.00 sec)

Query OK, 0 rows affected (5.00 sec)

mysql> select @v_msg;
+---------------+
| @v_msg        |
+---------------+
| 下單成功!     |
+---------------+
1 row in set (0.00 sec)
窗口2結果:
mysql> CALL proc4(1001,100,@v_msg);
+----------+
| sleep(5) |
+----------+
|        0 |
+----------+
1 row in set (5.00 sec)

Query OK, 0 rows affected (5.01 sec)

mysql> select @v_msg;
+-------------------------+
| @v_msg                  |
+-------------------------+
| 下單失敗,請重試!        |
+-------------------------+
1 row in set (0.00 sec)

能夠看到第一個窗口下單成功了,窗口2下單失敗了。

再看一下2個表的數據:

mysql> SELECT * FROM t_funds;
+---------+-----------+---------+
| user_id | available | version |
+---------+-----------+---------+
|    1001 |    900.00 |       0 |
+---------+-----------+---------+
1 row in set (0.00 sec)

mysql> SELECT * FROM t_order;
+----+--------+
| id | price  |
+----+--------+
|  1 | 100.00 |
+----+--------+
1 row in set (0.00 sec)

也正常。

總結

  1. 異常分爲Mysql內部異常和外部異常

  2. 內部異常由mysql內部觸發,外部異常是sql的執行結果和指望結果不一致致使的錯誤

  3. sql內部異常捕獲方式

    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET hasSqlError=TRUE;
  4. ROW_COUNT()能夠獲取mysql中insert或者update影響的行數

  5. 掌握使用樂觀鎖(添加版本號)來解決併發修改數據可能出錯的問題

  6. begin end前面能夠加標籤,LEAVE 標籤能夠退出對應的begin end,能夠使用這個來實現return的效果

Mysql系列目錄

  1. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933257&idx=1&sn=0f0086a2465a2fcae13d3fea65064803&chksm=88621bb7bf1592a1ac94fe4107ba1ef26a0fa97e1bf9aea7279009d8bd240f1ef7d27aa10393&token=1876080189&lang=zh_CN#rd">第1篇:mysql基礎知識</a>
  2. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933270&idx=1&sn=409080e17352da2035b0bfdf63ccdfde&chksm=88621ba8bf1592beb2ef6106d6bf9f3eccd48d6814c7031f36e3c8be68821f17cf065129688c&token=1876080189&lang=zh_CN#rd"> 第2篇:詳解mysql數據類型(重點)</a>
  3. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933279&idx=1&sn=f8591b95362cb3c352d895b1289d665a&chksm=88621ba1bf1592b72a43a62e3f310695e8b87f17932d052145622c3edbb70ef8cb987849fc3e&token=516655478&lang=zh_CN#rd"> 第3篇:管理員必備技能(必須掌握)</a>
  4. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933289&idx=1&sn=c4f212c312ea86e08ad322caddd05e38&chksm=88621b97bf159281156ee3be510a1a15234531d2c97d66957e67377829ab23779809ea55bbde&token=1484565200&lang=zh_CN#rd"> 第4篇:DDL常見操做</a>
  5. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933296&idx=1&sn=1c56256d60c5847a944d87c8cfc9c14d&chksm=88621b8ebf159298b0789e2994d2aaf8b582effc7d8c1ba715deaca11c86a9dc8ac730878dc0&token=2000571846&lang=zh_CN#rd"> 第5篇:DML操做彙總(insert,update,delete)</a>
  6. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933300&idx=1&sn=bedef4d430dc76141e42e42ef6acfaa6&chksm=88621b8abf15929caae7904019c946a396885a33855ca465bacdd4187538005ebc3c116888f5&token=1814800041&lang=zh_CN#rd"> 第6篇:select查詢基礎篇</a>
  7. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933305&idx=1&sn=2c5ba0bea6fcdd57d86cecd63541f91a&chksm=88621b87bf1592915086c1e945119fcc95af6aa1127b90ef1b56b018083bfd787ad95efde918&token=1144227002&lang=zh_CN#rd"> 第7篇:玩轉select條件查詢,避免採坑</a>
  8. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933309&idx=1&sn=0f7dfec8bc70e67daa7159ee219325b8&chksm=88621b83bf1592951d949179061f39a1f4266b2879bc9a25af4da3b4b29f69ab1fcc595a462f&token=516674265&lang=zh_CN#rd"> 第8篇:詳解排序和分頁(order by & limit)</a>
  9. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933315&idx=1&sn=4abf2b34a53a85cbd4a3b9ee31fcd398&chksm=88621bfdbf1592eb6a5a13202588dde9068ac7e6391d8dbf3fde33647bf0e044839ba4228216&token=1937580929&lang=zh_CN#rd"> 第9篇:分組查詢詳解(group by & having)</a>
  10. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933321&idx=1&sn=110f6cacb90845bf2327fbcd7acd708c&chksm=88621bf7bf1592e1f1c0f2f01e40d4bd63a48d98daa4ba1157ecad4c6c6520d18b4b2c24c906&token=1096041061&lang=zh_CN#rd"> 第10篇:經常使用的幾十個函數詳解</a>
  11. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933325&idx=1&sn=33274227db275a3570e1e43ccdd4f49c&chksm=88621bf3bf1592e5b75f537e21961c4295fafb782dbf31c0c1cbf9e36f5f4ed44d31cdad68b3&token=1832579722&lang=zh_CN#rd"> 第11篇:深刻了解鏈接查詢及原理</a>
  12. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933342&idx=2&sn=2e41daa0926a9c32d5fddd23590391aa&chksm=88621be0bf1592f66904a184858df7c11129e8b545c7626b8b6c1fcc32d3dfdd9ab21aeda8e5&token=386795745&lang=zh_CN#rd"> 第12篇:子查詢</a>
  13. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933349&idx=1&sn=6e81f9046119cda43f8eb602d1139ef0&chksm=88621bdbbf1592cd9a20ec4715fe9f9459d079b641b04f607999c106cd52453d8c8508d939d7&token=386795745&lang=zh_CN#rd"> 第13篇:細說NULL致使的神坑,讓人防不勝防</a>
  14. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933355&idx=1&sn=b426ad28dfc2a64bba813df5b7c341a4&chksm=88621bd5bf1592c3c08e441398f699d0a4c2303a51519aac169447f22765cc613dfc3d991f8b&token=385335213&lang=zh_CN#rd"> 第14篇:詳解事務</a>
  15. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933370&idx=1&sn=4fe9b8e2aba766988c503737d34d9836&chksm=88621bc4bf1592d2b06d659884bf9881bd207a042ac36176f12808bd72f235e8e3404366259e&token=84059034&lang=zh_CN#rd"> 第15篇:詳解視圖</a>
  16. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933374&idx=1&sn=c294b6a69564a4c98d4c0cb29aeb9ac0&chksm=88621bc0bf1592d650752c44ca50540c1fa0c88c0a514019f1317559e13098de7dac37ddfc05&token=1531009350&lang=zh_CN#rd"> 第16篇:變量詳解</a>
  17. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933382&idx=1&sn=4cf80b4f50c80dcc8171d2128b47cf63&chksm=88621c38bf15952e193177a0ba3e03beeaeed996553ce6900f91518310332e99c915e8be2566&token=1341741305&lang=zh_CN#rd"> 第17篇:存儲過程&自定義函數詳解</a>
  18. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933388&idx=1&sn=59cb1d23845e45894bb72c1e2479f074&chksm=88621c32bf1595241573e4194912ca60a51eb8477f4b49492e26668c9bebc032e9d6672e7fdd&token=1832749573&lang=zh_CN#rd"> 第18篇:流程控制語句</a>
  19. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933392&idx=1&sn=ff29e380b42a0aa7cb1dc7659d745686&chksm=88621c2ebf1595380f99d9c4c89581638a27a5cf356de35d31aa9ae7ac17e63abe0e4adc813c&token=1479782197&lang=zh_CN#rd"> 第19篇:遊標詳解</a>
  20. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933396&idx=1&sn=b6cc5d5287bf43d94e6706fa217aa8cb&chksm=88621c2abf15953c3200c6d4d945ad70bdd2177f020f8e2229ae269153bbc8bf251af83e1135&token=1846211483&lang=zh_CN#rd"> 第20篇:異常捕獲及處理詳解</a>
  21. <a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648933400&idx=1&sn=61af771f10342ee4956efea6749abd71&chksm=88621c26bf15953041a6bc65734edf788af711ff176ad36884fe6411e5c4cfd0bf967e0e33fc&token=877024264&lang=zh_CN#rd">第21篇:什麼是索引?</a>

mysql系列大概有20多篇,喜歡的請關注一下,歡迎你們加我微信itsoku或者留言交流mysql相關技術!

原文出處:https://www.cnblogs.com/itsoku123/p/11645952.html

相關文章
相關標籤/搜索