快速回顧 MySQL:遊標、觸發器和事務處理

前提要述:參考書籍《MySQL必知必會》

16.1 遊標

遊標(cursor)是一個存儲在MySQL服務器上的數據庫查詢,它不是一條SELECT語句,而是被該語句檢索出來的結果集。

有時,需要在檢索出來的行中前進或後退一行或多行,這就遊標的用處。在存儲了遊標之後,應用程序可以根據需要滾動或者瀏覽其中的數據。

不像多數DBMS,MySQL遊標只能用於存儲過程(和函數)。

16.1.1 創建遊標

遊標使用DECLARE語句創建。DECLARE命名遊標,類型固定爲CURSOR,並定義相應的SELECT語句,根據需要帶WHERE和其他子句。

檢索所有訂單的SELECT語句:先別創建下面的這個,現在只是演示定義

CREATE PROCEDURE processorders()
BEGIN
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;
END;

16.1.2 使用遊標

使用遊標涉及幾個步驟:

  • 在能夠使用遊標前,必須聲明它。但是還沒有檢索數據。
  • 一旦聲明後,必須打開遊標以供使用。就會把數據實際檢索出來。
  • 對於填有數據的遊標,根據需要取出各行。
  • 在結束遊標使用時,必須關閉遊標。

注意:下面這些開啓和關閉都是在存儲過程中開啓和關閉的,而不是先創建帶有遊標的存儲過程,再去開啓和關閉。

打開遊標使用OPEN關鍵字:

OPEN processorders;

關閉遊標使用OPEN關鍵字:

CLOSE processorders;

在關閉時,會釋放遊標使用的所有內部內存和資源。如果不明確的關閉遊標,MySQL將會在到達END語句時自動關閉它。

舉第一個例子:從遊標中檢索單個行:創建完沒顯示什麼,不急。

CREATE PROCEDURE processorders()
BEGIN
    -- 定義一個變量
    DECLARE o INT;
    -- 創建遊標
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;
    -- 開啓遊標
    OPEN ordernumbers;
    -- 檢索行
    FETCH ordernumbers INTO o;
    -- 關閉遊標
    CLOSE ordernumbers;
END;

解釋:
在一個遊標被打開後,可以使用FETCH語句分別訪問它的每一行。FETCH指定檢索什麼數據(所需的列),檢索出來的數據存儲在什麼地方。它還向前移動遊標中的內部行指針,使得下一條FETCH語句檢索下一行(不重複讀取同一行。)

第二個例子:循環檢索數據:

CREATE PROCEDURE processorders()
BEGIN
    -- 定義變量
    DECLARE done BOOLEAN DEFAULT 0;
    DECLARE o INT;
    -- 創建遊標
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;
    -- 規定循環的終止條件,使得done爲真
    DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
    -- 開啓遊標
    OPEN ordernumbers;
    -- 循環開始
    REPEAT
        -- 檢索行
        FETCH ordernumbers INTO o;
    -- 每次判斷是否到達終止條件    
    UNTIL done END REPEAT;
    -- 關閉遊標
    CLOSE ordernumbers;
END;

解釋:這個例子中的FETCH是在REPEAT內,因此它反覆執行直到done爲真(由UNTIL done END REPEAT;規定)。並且先把變量done默認值設爲0,表示假(false)。而定義下面的語句來使得done爲1,即真:

DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;

該語句定義了 CONTINUE HANDLER ,它是在條件出現時被執行的代碼。這裏,它指出SQLSTATE '0200’出現時,SET done=1。而SQLSTATE '0200’是一個未找到的條件,當REPEAT由於沒有更多的行供循環而不能繼續時,出現這個條件。

對於DECLARE語句定義的局部變量必須在定義任意遊標或句柄(句柄就是上面那句CONTINUE HANDLER)之前定義,而句柄必須在遊標之後定義。不遵守此順序會報錯。

上面的只是演示,並不能看到實際效果。下面給個實際效果:

CREATE PROCEDURE processorders()
BEGIN
    -- 定義變量
    DECLARE done BOOLEAN DEFAULT 0;
    DECLARE o INT;
    DECLARE t DECIMAL(8,2);
    -- 創建遊標
    DECLARE ordernumbers CURSOR
    FOR
    SELECT order_num FROM orders;
    -- 規定循環的終止條件,使得done爲真
    DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
    -- 創建一個存儲結果數據的表
    CREATE TABLE IF NOT EXISTS ordertotals
        (order_num INT, total DECIMAL(8,2));
    -- 開啓遊標
    OPEN ordernumbers;
    -- 循環開始
    REPEAT
        -- 檢索行
        FETCH ordernumbers INTO o;
        -- 調用之前是否含有營業稅的合計的存儲過程
        CALL ordertotal(o, 1, t);
        -- 把結果數據插入剛剛創建的表
        INSERT INTO ordertotals(order_num, total) VALUES(o, t);
    -- 每次判斷是否到達終止條件    
    UNTIL done END REPEAT;
    -- 關閉遊標
    CLOSE ordernumbers;
END;

解釋:此存儲過程不返回結果,但能創建和填充另一個表,名爲ordertotals。該表將保存存儲過程生成的結果。FETCH取每個order_num,然後用CALL執行另一個存儲過程(前面存儲過程的章節最後創建的)來計算每個訂單的帶稅的合計(結果存儲到t)。最後,用INSERT保存每個訂單的訂單號和合計。

CALL processorders();
SELECT * FROM ordertotals;

小結:

  • 可以看出該過程確實有點難寫,而且很容易寫錯。遊標的用途之一就是上面最後一個實例,也可以用來把一個表的數據複製到另一個表(假設表中還有其他不同的字段)中(假設有很多條數據,使用普通的INSERT一條一條執行插入是非常慢的。前面也說了,存儲過程比單獨使用SQL語句執行快,現在加上游標,就可以循環插入)。
  • 先定義遊標(DECLARE … CURSOR FOR sql語句),再啓動(OPEN …),最後再關閉(CLOSE …)。

16.2 觸發器

如果你想要某條語句在事件發生(比如插入、刪除等操作)時自動執行,要怎麼辦?這就需要觸發器。觸發器是MySQL響應以下任意語句而自動執行的一條MySQL語句(或位於BEGIN和END語句之間的一組語句):

  • DELETE;
  • INSERT;
  • UPDATE。

其他MySQL語句不支持觸發器。

16.2.1 創建觸發器

在創建觸發器,需要給出4條信息:

  • 唯一的觸發器名;
  • 觸發器關聯的表;
  • 觸發器應該響應的活動(DELETE、INSERT或UPDATE);
  • 觸發器何時執行(處理之前或處理之後)。

觸發器使用CREATE TRIGGER語句創建,比如:

CREATE TRIGGER newproduct AFTER INSERT ON products
FOR EACH ROW SELECT 'Product added';

解釋:觸發器可在一個操作之前或之後執行,這裏出給了AFTER INSERT,所以此觸發器將在INSERT語句成功執行後執行。這個觸發器還這指定FOR EACH NOW,因此代碼對每個插入的行執行。所以,這個例子的意思是:文本Product added將對products表每個插入的行顯示一次。

只有表才支持觸發器,視圖不支持(臨時表也不支持)。

小結創建觸發器所需的關鍵字:

  • CREATE TRIGGER 唯一觸發器名
  • AFTER INSERT ON table_name:在插入成功後執行
  • BEFORE INSERT ON table_name:在插入成功之前執行
  • 還有其他的DELECT、UPDATE也是上面的寫法
  • FOR EACH ROW:對每個插入的行執行
  • 最後跟上SQL語句。

觸發器按每個表每個事件每次地定義,每個表每個事件每次只允許一個觸發器。因此,可得每個表最多支持6個觸發器(INSERT、UPDATE、DELETE,三個的之前和之後)。單一的觸發器不能於多個事件或多個表關聯。

注意:如果BEFORE觸發器失敗(執行之前),則MySQL將不執行請求的操作。此外,如果BEFORE觸發器或語句本身失敗,MySQL將不執行AFTER觸發器(如果有的話)。

16.2.2 刪除觸發器

使用DROP TRIGGER語句刪除:

DROP TRIGGER newproduct;

注意:觸發器不能更新或覆蓋。所以要修改觸發器,必須先刪除。


下面介紹幾種類型的觸發器。

16.2.3 INSERT觸發器

需要注意以下幾點:

  • 在INSERT觸發器代碼內,可引用一個名爲NEW的虛擬表,訪問被插入的行。
  • 在BEFORE INSERT觸發器中,NEW中的值也可以被更新(允許更改被插入的值)。
  • 對於AUTO_INCREMENT列,NEW在INSERT執行之前包含0,在INSERT執行之後包含新的自動生成值。

例子:下面是基於MySQL版本5.0的寫法,5.0之後按下面的寫法會報錯。

CREATE TRIGGER neworder AFTER INSERT ON orders
FOR EACH ROW SELECT NEW.order_num;

執行上面語句會報錯:Not allowed to return a result set from a trigger

解決方法:加上 into @ee,因爲從MySQL5以後不支持觸發器返回結果集,即存儲在臨時變量中,還可以調用。

即:

CREATE TRIGGER neworder AFTER INSERT ON orders
FOR EACH ROW SELECT NEW.order_num into @ee, @ee;

CREATE TRIGGER newproduct AFTER INSERT ON products
FOR EACH ROW SELECT ‘Product added’ INTO @ee;
解釋:創建一個名爲neworder的觸發器,按照AFTER INSERT ON orders執行。在插入一個新訂單到orders表之後,MySQL生成一個新的訂單號並保存到order_num中。觸發器從NEW.order_num取得這個值並返回它。對於NEW的虛擬表,必須是AFTER INSERT執行,因爲在BEFORE INSERT語句執行之前,新order_num還沒生成。

使用:

INSERT INTO orders(order_date, cust_id)
VALUES(Now(), 10001);

因爲觸發器把結果存儲在變量中,所以插入時顯示不出什麼,得使用:

SELECT @ee;

輸出:

+-------+
| @ee   |
+-------+
| 20012 |
+-------+
1 row in set (0.06 sec)

通常,將BEFORE用於數據驗證和淨化(目的是保證插入表中的數據確實是需要的數據)。UPDATE也一樣。

16.2.3 DELETE觸發器

需要知道以下幾點:

  • 在DELETE觸發器代碼內,可以引用一個名爲OLD的虛擬表,訪問被刪除的行。
  • OLD中的值全都是隻讀的,不能更新。

例子:使用OLD保存將要被刪除的行道一個存檔表中:

CREATE TRIGGER deleteorder BEFORE DELETE ON orders
FOR EACH ROW
BEGIN
    INSERT INTO archive_orders(order_num, order_date, cust_id) VALUES(OLD.order_num, OLD.order_date, OLD.cust_id);
END;

解釋:在任意訂單被刪除前將執行此觸發器。使用一條INSERT語句將OLD中的值(要被刪除的訂單)保存道一個名爲archive_orders的存檔表中(需要手動創建與orders相同的列的archive_orders的表)

使用BEFORE DELETE觸發器的優點:如果由於某種原因,訂單不能存檔,DELETE本身將被放棄。

使用BEGIN END的好處是觸發器能容納多條SQL語句(在BEGIN END塊中一條挨着一條)。

16.2.4 UPDATE觸發器

需要知道以下幾點:

  • 在UPDATE觸發器代碼內,可以引用一個名爲OLD的虛擬表訪問修改前的值,引用一個名爲NEW的虛擬表訪問更新後的值。
  • 在BEFORE UPDATE觸發器中,NEW中的值可能也被更新(允許更改將要用於UPDATE語句中的值)。
  • OLD中的值全都是隻讀的,不能更新。

下面的例子保證州名縮寫總是大寫:

CREATE TRIGGER updatevendor BEFORE UPDATE ON vendors
FOR EACH ROW SET NEW.vend_state = UPPER(NEW.vend_state);

任何數據淨化都需要在UPDATE語句之前進行。像這個例子,每次更新一個行時,NEW.vend_state中的值(將用來更新錶行的值)都用UPPER(NEW.vend_state)替換。


小結:

  • 與其他DBMS相比,MySQL 5中支持的觸發器相當初級。5以後會加強。
  • 創建觸發器可能需要特殊的安全訪問權限,但是,觸發器的執行是自動的。如果INSERT、UPDATE、DELETE語句能夠執行,則相關的觸發器也能執行。
  • 應該使用觸發器來保證數據的一致性(大小寫、格式等)。而且是透明進行的,與客戶機應用無關。
  • 觸發器是創建審計跟蹤。使用觸發器,把更改記錄到另一表很容易。就像上面的例子。
  • MySQL觸發器中不支持CALL語句(5.0版本)。這表示不能從觸發器內調用存儲過程。所需的存儲過程代碼需要複製到觸發器內。

16.3 事務處理

說到事務,就要知道引擎。使用InnoBD是支持事務處理的。

事務處理(transaction processing)可以用來維護數據庫的完整性,它保證成批的MySQL操作要麼完全執行,要麼完全不執行。

轉載菜鳥教程
一般來說,事務是必須滿足4個條件(ACID)::原子性(Atomicity,或稱不可分割性)、一致性(Consistency)、隔離性(Isolation,又稱獨立性)、持久性(Durability)。

  • 原子性:一個事務(transaction)中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。

  • 一致性:在事務開始之前和事務結束以後,數據庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及後續數據庫可以自發性地完成預定的工作。

  • 隔離性:數據庫允許多個併發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致數據的不一致。事務隔離分爲不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和串行化(Serializable)。

  • 持久性:事務處理結束後,對數據的修改就是永久的,即便系統故障也不會丟失。

事務的幾個術語:

  • 事務(transaction)指一組SQL語句;
  • 回退(rollback)指撤銷指定SQL語句的過程;
  • 提交(commit)指將未存儲的SQL語句結果寫入數據庫表中;
  • 保留點(savepoint)指事務處理中設置的臨時佔位符(place-holder),你可以對它發佈回退(與回退整個事務處理不同)。

最常說的例子:就是轉賬時,假設我支付寶轉賬給我朋友100塊,那麼我餘額就會少掉100塊,而我朋友的餘額就會多出一百塊。而在該過程中,因爲系統某些原因,我轉賬後我的朋友沒有收到錢,而我的餘額已經少掉100塊了,如果沒有事務的處理,那麼過程就是這樣,我的100塊都不知道去哪了;如果使用事務,那麼這就屬於轉賬失敗,那麼我的100塊會回到我的餘額中。而朋友也不會收到我的100塊。

16.3.1 使用事務處理

管理事務處理的關鍵在於將SQL語句組分解爲邏輯塊,並明確規定數據何時應該回退,何時不應該回退。

MySQL使用下面的語句來標識一個事務的開始:

START TRANSACTION;

1. 使用ROLLBACK

MySQL的ROLLBACK命令用來回退MySQL語句,例子:

SELECT * FROM ordertotals;
START TRANSACTION;
DELETE FROM ordertotals;
SELECT * FROM ordertotals;
ROLLBACK;
SELECT * FROM ordertotals;

解釋:使用ROLLBACK語句回退START TRANSACTION之後的所有語句。所以,第一次查詢是有數據,第二次刪除表數據後再查詢就沒有數據,之後回退,再查詢,又有數據。

事務處理用來管理INSERT、UPDATE和DELETE語句,不能回退SELECT語句。也不能回退CREATE或DROP操作,但在事務處理塊中可以使用這兩條語句,但不會回退。

2. 使用COMMIT

一般的MySQL語句都是直接針對數據庫表執行和編寫的。這就是所謂的隱式提交(implicit commit),即提交(保存)操作是自動進行的。

但是,在事務處理塊中,提交不會隱式地進行。所以需要明確提交,使用COMMIT語句。如下:

START TRANSACTION;
DELETE FROM orderitems WHERE order_num = 20010;
DELETE FROM orders WHERE order_num = 20010;
COMMIT;

解釋:只有兩條語句都成功執行時,纔會執行COMMIT。否則有任意一條錯誤,都不會COMMIT。但是我執行的時候,第二條硬改成錯誤的,結果COMMIT時第一條的還是執行了,我很奇怪?就網上了解:我自己的瞭解就是上面那兩句,如果兩條語句之一有錯誤,在COMMIT時就都不執行。下面是參考:對mysql事務提交、回滾的錯誤理解

事務的回滾不是這麼理解的,正確的理解應該是:如果事務中所有sql語句執行正確則需要自己手動提交commit;否則有任何一條執行錯誤,需要自己提交一條rollback,這時會回滾所有操作,而不是commit會給你自動判斷和回滾。

3. 使用保留點

前面講的都是會回退到定義事務時,而有時需要回退部分事務處理,所以需要保留點,能在事務處理塊中合適的位置放置佔位符,那麼需要回退時,可以回退到某個佔位符。

佔位符使用SAVEPOINT語句創建,比如:

SAVEPOINT delete1;

每一個保留點都取它標識它的唯一名,以便在回退時,MysQL知道要回退到何處。下面是使用:

ROLLBACK TO delete1;

可以在MysQL代碼中設置任意多的保留點,越多越好,就越能按自己的意願靈活地進行回退。

釋放保留點:保留點在事務處理完成(執行一條ROLLBACK或COMMIT)後自動釋放。也可以使用RELEASE SAVEPOINT明確地釋放保留點。

默認的MySQL行爲是自動提交所以更改。任何時候執行一條SQL語句,該語句實際上都是針對表執行的,而且所做的更改立即生效。爲讓MySQL不自動提交更改,需要使用下面的語句:

SET autocommit=0;

autocommit標誌決定釋放自動提交更改,不管有沒有COMMIT語句。設置autocommit爲0(假)指示MySQL不自動提交更改。直到被設置autocommit爲真時。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章