程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> MYSQL數據庫 >> 關於MYSQL數據庫 >> MySQL 5.0 新特性教程 存儲過程:第四講_MySQL教程

MySQL 5.0 新特性教程 存儲過程:第四講_MySQL教程

編輯:關於MYSQL數據庫

Error Handling 異常處理

  好了,我們現在要講的是異常處理

1. Sample Problem: Log Of Failures 問題樣例:故障記錄

  當INSERT失敗時,我希望能將其記錄在日志文件中我們用來展示出錯處理的問題樣例是很
普通的。我希望得到錯誤的記錄。當INSERT失敗時,我想在另一個文件中記下這些錯誤的
信息,例如出錯時間,出錯原因等。我對插入特別感興趣的原因是它將違反外鍵關聯的約束

2. Sample Problem: Log Of Failures (2)


MySQL> CREATE TABLE t2
s1 INT, PRIMARY KEY (s1))
engine=innodb;//
MySQL> CREATE TABLE t3 (s1 INT, KEY (s1),
FOREIGN KEY (s1) REFERENCES t2 (s1))
engine=innodb;//
MySQL> INSERT INTO t3 VALUES (5);//
...
ERROR 1216 (23000): Cannot add or update a child row: a foreign key
constraint fails(這裡顯示的是系統的出錯信息)

  我開始要創建一個主鍵表,以及一個外鍵表。我們使用的是InnoDB,因此外鍵關聯檢查是打
開的。然後當我向外鍵表中插入非主鍵表中的值時,動作將會失敗。當然這種條件下可以很
快找到錯誤號1216。

3. Sample Problem: Log Of Failures


CREATE TABLE error_log (error_message
CHAR(80))//

  下一步就是建立一個在做插入動作出錯時存儲錯誤的表。

4. Sample Problem: Log Of Errors


CREATE PROCEDURE p22 (parameter1 INT)
BEGIN

DECLARE EXIT HANDLER FOR 1216
INSERT INTO error_log VALUES
(CONCAT('Time: ',current_date,
'. Foreign Key Reference Failure For
Value = ',parameter1));
INSERT INTO t3 VALUES (parameter1);
END;//

  上面就是我們的程序。這裡的第一個語句DECLARE EXIT HANDLER是用來處理異常的。意思是如果錯誤1215發生了,這個程序將會在錯誤記錄表中插入一行。EXIT意思是當動作成功提交後退出這個復合語句。

5. Sample Problem: Log Of Errors


CALL p22 (5) //

  調用這個存儲過程會失敗,這很正常,因為5值並沒有在主鍵表中出現。但是沒有錯誤信息
返回因為出錯處理已經包含在過程中了。t3表中沒有增加任何東西,但是error_log表中記錄
下了一些信息,這就告訴我們INSERT into table t3動作失敗。


DECLARE HANDLER syntax 聲明異常處理的語法


DECLARE
{ EXIT | CONTINUE }
HANDLER FOR
{ error-number | { SQLSTATE error-string } | condition }
SQL statement

  上面就是錯誤處理的用法,也就是一段當程序出錯後自動觸發的代碼。MySQL允許兩種處理器,一種是EXIT處理,我們剛才所用的就是這種。另一種就是我們將要演示的,CONTINUE處理,它跟EXIT處理類似,不同在於它執行後,原主程序仍然繼續運行,那麼這個復合語句就沒有出口了。

1. DECLARE CONTINUE HANDLER example CONTINUE處理例子


CREATE TABLE t4 (s1 int,primary key(s1));//
CREATE PROCEDURE p23 ()
BEGIN
DECLARE CONTINUE HANDLER
FOR SQLSTATE '23000' SET @x2 = 1;
SET @x = 1;
INSERT INTO t4 VALUES (1);
SET @x = 2;
INSERT INTO t4 VALUES (1);
SET @x = 3;
END;//

  這是MySQL參考手冊上的CONTINUE處理的例子,這個例子十分好,所以我把它拷貝到這裡。
  通過這個例子我們可以看出CONTINUE處理是如何工作的。

2. DECLARE CONTINUE HANDLER聲明CONTINUE異常處理


CREATE TABLE t4 (s1 int,primary key(s1));//
CREATE PROCEDURE p23 ()
BEGIN
DECLARE CONTINUE HANDLER
FOR SQLSTATE '23000' SET @x2 = 1; <--
SET @x = 1;
INSERT INTO t4 VALUES (1);
SET @x = 2;
INSERT INTO t4 VALUES (1);
SET @x = 3;
END;//

  這次我將為SQLSTATE值定義一個處理程序。還記得前面我們使用的MySQL錯誤代碼1216嗎?
  事實上這裡的23000SQLSTATE是更常用的,當外鍵約束出錯或主鍵約束出錯就被調用了。


3. DECLARE CONTINUE HANDLER


CREATE TABLE t4 (s1 int,primary key(s1));//
CREATE PROCEDURE p23 ()
BEGIN
DECLARE CONTINUE HANDLER
FOR SQLSTATE '23000' SET @x2 = 1;
SET @x = 1; <--
INSERT INTO t4 VALUES (1);
SET @x = 2;
INSERT INTO t4 VALUES (1);
SET @x = 3;
END;//

  這個存儲過程的第一個執行的語句是"SET @x = 1"。

4. DECLARE CONTINUE HANDLER example


CREATE TABLE t4 (s1 int,primary key(s1));//
CREATE PROCEDURE p23 ()
BEGIN
DECLARE CONTINUE HANDLER
FOR SQLSTATE '23000' SET @x2 = 1;
SET @x = 1;
INSERT INTO t4 VALUES (1);
SET @x = 2;
INSERT INTO t4 VALUES (1); <--
SET @x = 3;
END;//

  運行後值1被插入到主鍵表中。


5. DECLARE CONTINUE HANDLER


CREATE TABLE t4 (s1 int,primary key(s1));//
CREATE PROCEDURE p23 ()
BEGIN
DECLARE CONTINUE HANDLER
FOR SQLSTATE '23000' SET @x2 = 1;
SET @x = 1;
INSERT INTO t4 VALUES (1);
SET @x = 2; <--
INSERT INTO t4 VALUES (1);
SET @x = 3;
END;//

  然後@x的值變為2。

6. DECLARE CONTINUE HANDLER example


CREATE TABLE t4 (s1 int,primary key(s1));//
CREATE PROCEDURE p23 ()
BEGIN
DECLARE CONTINUE HANDLER
FOR SQLSTATE '23000' SET @x2 = 1;
SET @x = 1;
INSERT INTO t4 VALUES (1);
SET @x = 2;
INSERT INTO t4 VALUES (1); <--
SET @x = 3;
END;//

  然後程序嘗試再次往主鍵表中插入數值,但失敗了,因為主鍵有唯一性限制。

7. DECLARE CONTINUE HANDLER example


CREATE TABLE t4 (s1 int,primary key(s1));//
CREATE PROCEDURE p23 ()
BEGIN
DECLARE CONTINUE HANDLER

FOR SQLSTATE '23000' SET @x2 = 1; <--
SET @x = 1;
INSERT INTO t4 VALUES (1);
SET @x = 2;
INSERT INTO t4 VALUES (1);
SET @x = 3;
END;//

  由於插入失敗,錯誤處理程序被觸發,開始進行錯誤處理。下一個執行的語句是錯誤處理的語句,@x2被設為2。


8. DECLARE CONTINUE HANDLER example


CREATE TABLE t4 (s1 int,primary key(s1));//
CREATE PROCEDURE p23 ()
BEGIN
DECLARE CONTINUE HANDLER
FOR SQLSTATE '23000' SET @x2 = 1;
SET @x = 1;
INSERT INTO t4 VALUES (1);
SET @x = 2;
INSERT INTO t4 VALUES (1);
SET @x = 3; <--
END;//

  到這裡並沒有結束,因為這是CONTINUE異常處理。所以執行返回到失敗的插入語句之後,繼續執行將@x設定為3動作。

9. DECLARE CONTINUE HANDLER example


MySQL> CALL p23()//
Query OK, 0 rows affected (0.00 sec)
MySQL> SELECT @x, @x2//

+------+------+
| @x | @x2 |
+------+------+
| 3 | 1 |
+------+------+
1 row in set (0.00 sec)

  運行過程後我們觀察@x的值,很確定的可以知道是3,觀察@x2的值,為1。從這裡可以判斷程序運行無誤,完全按照我們的思路進行。大家可以花點時間去調整錯誤處理器,讓檢查放在語句段的首部,而不是放在可能出現錯誤的地方,雖然那樣看起來程序很紊亂,跳來跳去的感覺。但是這樣的代碼很安全也很清楚。

1. DECLARE CONDITION


CREATE PROCEDURE p24 ()
BEGIN
DECLARE `Constraint Violation`
CONDITION FOR SQLSTATE '23000';
DECLARE EXIT HANDLER FOR
`Constraint Violation` ROLLBACK;
START TRANSACTION;
INSERT INTO t2 VALUES (1);
INSERT INTO t2 VALUES (1);
COMMIT;
END; //

  這是另外一個錯誤處理的例子,在前面的基礎上修改的。事實上你可給SQLSTATE或者錯誤代碼其他的名字,你就可以在處理中使用自己定義的名字了。下面看它是怎麼實現的:我把表t2定義為InnoDB表,所以對這個表的插入操作都會ROLLBACK(回滾),ROLLBACK(回滾事務)也是恰好會發生的。因為對主鍵插入兩個同樣的值會導致SQLSTATE 23000錯誤發生,這裡SQLSTATE 23000是約束錯誤。

2. DECLARE CONDITION聲明條件


CREATE PROCEDURE p24 ()
BEGIN
DECLARE `Constraint Violation`
CONDITION FOR SQLSTATE '23000';
DECLARE EXIT HANDLER FOR
`Constraint Violation` ROLLBACK;
START TRANSACTION;
INSERT INTO t2 VALUES (1);
INSERT INTO t2 VALUES (1);
COMMIT;
END; //

  這個約束錯誤會導致ROLLBACK(回滾事務)和SQLSTATE 23000錯誤發生。


3. DECLARE CONDITION


MySQL> CALL p24()//
Query OK, 0 rows affected (0.28 sec)


MySQL> SELECT * FROM t2//
Empty set (0.00 sec)

  我們調用這個存儲過程看結果是什麼,從上面結果我們看到表t2沒有插入任何記錄。全部事務都回滾了。這正是我們想要的。

4. DECLARE CONDITION


MySQL> CREATE PROCEDURE p9 ()
-> BEGIN
-> DECLARE EXIT HANDLER FOR NOT FOUND BEGIN END;
-> DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN END;
-> DECLARE EXIT HANDLER FOR SQLWARNING BEGIN END;
-> END;//
Query OK, 0 rows affected (0.00 sec)

  這裡是三個預聲明的條件:NOT FOUND (找不到行), SQLEXCEPTION (錯誤),SQLWARNING (警告或注釋)。因為它們是預聲明的,因此不需要聲明條件就可以使用。不過如果你去做這樣的聲明:"DECLARE SQLEXCEPTION CONDITION ...",你將會得到錯誤信息提示。


Cursors 游標


  游標實現功能摘要:

DECLARE cursor-name CURSOR FOR SELECT ...;
OPEN cursor-name;
FETCH cursor-name INTO variable [, variable];
CLOSE cursor-name;

  現在我們開始著眼游標了。雖然我們的存儲過程中的游標語法還並沒有完整的實現,但是已經可以完成基本的事務如聲明游標,打開游標,從游標裡讀取,關閉游標。

1. Cursor Example


CREATE PROCEDURE p25 (OUT return_val INT)
BEGIN
DECLARE a,b INT;
DECLARE cur_1 CURSOR FOR SELECT s1 FROM t;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET b = 1;
OPEN cur_1;
REPEAT
FETCH cur_1 INTO a;
UNTIL b = 1
END REPEAT;
CLOSE cur_1;
SET return_val = a;
END;//

  我們看一下包含游標的存儲過程的新例子。

2. Cursor Example


CREATE PROCEDURE p25 (OUT return_val INT)
BEGIN
DECLARE a,b INT; <--
DECLARE cur_1 CURSOR FOR SELECT s1 FROM t;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET b = 1;
OPEN cur_1;
REPEAT
FETCH cur_1 INTO a;
UNTIL b = 1
END REPEAT;
CLOSE cur_1;
SET return_val = a;
END;//

  這個過程開始聲明了三個變量。附帶說一下,順序是十分重要的。首先要進行變量聲明,然後聲明條件,隨後聲明游標,再後面才是聲明錯誤處理器。如果你沒有按順序聲明,系統會提示錯誤信息。


3. Cursor Example


CREATE PROCEDURE p25 (OUT return_val INT)
BEGIN
DECLARE a,b INT;
DECLARE cur_1 CURSOR FOR SELECT s1 FROM t; <--
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET b = 1;
OPEN cur_1;
REPEAT
FETCH cur_1 INTO a;
UNTIL b = 1
END REPEAT;
CLOSE cur_1;
SET return_val = a;
END;//

  程序第二步聲明了游標cur_1,如果你使用過嵌入式SQL的話,就知道這和嵌入式SQL差不多。


4. Cursor Example


CREATE PROCEDURE p25 (OUT return_val INT)
BEGIN
DECLARE a,b INT;
DECLARE cur_1 CURSOR FOR SELECT s1 FROM t;
DECLARE CONTINUE HANDLER FOR NOT FOUND <--


SET b = 1; <--
OPEN cur_1;
REPEAT
FETCH cur_1 INTO a;
UNTIL b = 1
END REPEAT;
CLOSE cur_1;
SET return_val = a;
END;//

  最後進行的是錯誤處理器的聲明。這個CONTINUE處理沒有引用SQL錯誤代碼和SQLSTATE值。它使用的是NOT FOUND系統返回值,這和SQLSTATE 02000是一樣的。

5. Cursor Example


CREATE PROCEDURE p25 (OUT return_val INT)
BEGIN
DECLARE a,b INT;
DECLARE cur_1 CURSOR FOR SELECT s1 FROM t;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET b = 1;
OPEN cur_1; <--
REPEAT
FETCH cur_1 INTO a;
UNTIL b = 1
END REPEAT;
CLOSE cur_1;
SET return_val = a;
END;//

  過程第一個可執行的語句是OPEN cur_1,它與SELECT s1 FROM t語句是關聯的,過程將執行SELECT s1 FROM t,返回一個結果集。


6. Cursor Example


CREATE PROCEDURE p25 (OUT return_val INT)
BEGIN
DECLARE a,b INT;
DECLARE cur_1 CURSOR FOR SELECT s1 FROM t;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET b = 1;
OPEN cur_1;
REPEAT
FETCH cur_1 INTO a; <--
UNTIL b = 1
END REPEAT;
CLOSE cur_1;
SET return_val = a;
END;//

  這裡第一個FETCH語句會獲得一行從SELECT產生的結果集中檢索出來的值,然而表t中有多行,因此這個語句會被執行多次,當然這是因為語句在循環塊內。


7. Cursor Example


CREATE PROCEDURE p25 (OUT return_val INT)
BEGIN
DECLARE a,b INT;
DECLARE cur_1 CURSOR FOR SELECT s1 FROM t;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET b = 1; <--
OPEN cur_1;
REPEAT
FETCH cur_1 INTO a;
UNTIL b = 1
END REPEAT;
CLOSE cur_1;
SET return_val = a;
END;//

  最後當MySQL的FETCH沒有獲得行時,CONTINUE處理被觸發,將變量b賦值為1。

8. Cursor Example


CREATE PROCEDURE p25 (OUT return_val INT)
BEGIN
DECLARE a,b INT;
DECLARE cur_1 CURSOR FOR SELECT s1 FROM t;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET b = 1;
OPEN cur_1;
REPEAT
FETCH cur_1 INTO a;
UNTIL b = 1
END REPEAT;
CLOSE cur_1; <--
SET return_val = a;
END;//

  到了這一步UNTIL b=1條件就為真,循環結束。在這裡我們可以自己編寫代碼關閉游標,也可以由系統執行,系統會在復合語句結束時自動關閉游標,但是最好不要太依賴系統的自動關閉行為(譯注:這可能跟Java的Gc一樣,不可信)。


9. Cursor Example


CREATE PROCEDURE p25 (OUT return_val INT)
BEGIN
DECLARE a,b INT;
DECLARE cur_1 CURSOR FOR SELECT s1 FROM t;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET b = 1;
OPEN cur_1;
REPEAT
FETCH cur_1 INTO a;
UNTIL b = 1
END REPEAT;
CLOSE cur_1;
SET return_val = a; <--
END;//

  這個例程中我們為輸出參數指派了一個局部變量,這樣在過程結束後的結果仍能使用。


10. Cursor Example


CREATE PROCEDURE p25 (OUT return_val INT)
BEGIN
DECLARE a,b INT;
DECLARE cur_1 CURSOR FOR SELECT s1 FROM t;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET b = 1;
OPEN cur_1;
REPEAT
FETCH cur_1 INTO a;
UNTIL b = 1
END REPEAT;
CLOSE cur_1;
SET return_val = a;
END;//


MySQL> CALL p25(@return_val)//
Query OK, 0 rows affected (0.00 sec)
MySQL> SELECT @return_val//
+-------------+
| @return_val |
+-------------+
| 5 |
+-------------+
1 row in set (0.00 sec)

  上面是過程調用後的結果。可以看到return_val參數獲得了數值5,因為這是表t的最後一行。
  由此可以知道游標工作正常,出錯處理也工作正常。

Cursor Characteristics 游標的特性

  摘要:
  READ ONLY只讀屬性
  NOT SCROLLABLE順序讀取
  ASENSITIVE敏感

  在5.0版的MySQL中,你只可以從游標中取值,不能對其進行更新。因為游標是(READONLY)只讀的。你可以這樣做:


FETCH cursor1 INTO variable1;
UPDATE t1 SET column1 = 'value1' WHERE CURRENT OF cursor1;

  游標也是不可以滾動的,只允許逐一讀取下一行,不能在結果集中前進或後退。下面代碼就是錯誤的:


FETCH PRIOR cursor1 INTO variable1;
FETCH ABSOLUTE 55 cursor1 INTO variable1;

  同時也不允許在已打開游標進行操作的表上執行updates事務,因為游標是(ASENSITIVE)敏感的。因為如果你不阻止update事務,那就不知道結果會變成什麼。如果你使用的是InnoDB而不是MyISAM存儲引擎的話,結果也會不一樣。

Security 安全措施

  摘要
  Privileges (1) CREATE ROUTINE
  Privileges (2) EXECUTE
  Privileges (3) GRANT SHOW ROUTINE?
  Privileges (4) INVOKERS AND DEFINERS

  這裡我們要討論一些關於特權和安全相關的問題。但因為在MySQL安全措施的功能並沒有完全,所以我們不會對其進行過多討論。

1. Privileges CREATE ROUTINE


GRANT CREATE ROUTINE
ON database-name . *
TO user(s)
[WITH GRANT OPTION];

  現在用root就可以了

  在這裡要介紹的特權是CREATE ROUTINE,它不僅同其他特權一樣可以創建存儲過程和函數,還可以創建視圖和表。Root用戶擁有這種特權,同時還有ALTER ROUTINE特權。

2. Privileges EXECUTE


GRANT EXECUTE ON p TO peter
[WITH GRANT OPTION];

  上面的特權是決定你是否可以使用或執行存儲過程的特權,過程創建者默認擁有這個特權。

3. Privileges SHOW ROUTINE?


GRANT SHOW ROUTINE ON db6.* TO joey
[WITH GRANT OPTION];

 因為我們已經有控制視圖的特權了:GRANT SHOW VIEW。所以在這個基礎上,為了保證兼容,日後可能會添加GRANT SHOW ROUTINE特權。這樣做是不太符合標准的,在寫本書的時候,MySQL還沒實現這個功能。

4. Privileges Invokers and Definers 特權調用者和定義者


CREATE PROCEDURE p26 ()
SQL SECURITY INVOKER
SELECT COUNT(*) FROM t //
CREATE PROCEDURE p27 ()
SQL SECURITY DEFINER
SELECT COUNT(*) FROM t //
GRANT INSERT ON db5.* TO peter; //

  現在我們測試一下SQL SECURITY子句吧。Security是我們前面提到的程序特性的一部分。你root用戶,將插入權賦給了peter。然後使用peter登陸進行新的工作,我們看peter可以怎麼使用存儲過程,注意:peter沒有對表t的select權力,只有root用戶有。

5. Privileges Invokers and Definers


/* Logged on with current_user = peter */使用帳戶peter登陸

MySQL> CALL p26();
ERROR 1142 (42000): select command denIEd to user
'peter'@'localhost' for table 't'
MySQL> CALL p27();
+----------+
| COUNT(*) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)

  當peter嘗試調用含有調用保密措施的過程p26時會失敗。那是因為peter沒有對表的select的權力。

  但是當petre調用含有定義保密措施的過程時就能成功。原因是root有select權力,Peter有root的權力,因此過程可以執行。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved