與資料庫共舞的文章系列來到了尾聲,今天我們要來聊聊一個關聯式資料庫中很重要、但是很複雜的觀念: 資料庫交易 (transactions)。這個交易跟金錢沒有關係,它講的是資料一來一往的互動過程。這個題目其實非常大,但和之前一樣,我盡可能解釋一些重要的觀念,讓有興趣的讀者可以更輕易就深入的了解它到底在做什麼。
資料庫的操作大部分不只會局限在一個單一行為。一般來說它是一連串的指令,每個使令分別發生在時間軸上面。讓我們來看下面的例子:
假設我想去聽告五人的小巨蛋演唱會,左邊T是時間軸,開買當下我點進售票網站,這時候網站的資料庫會執行一個 Select—From—Where 的指令,讓我看到空的位子。我猶豫了3秒,然後手刀下單買下了第二排中間的位子。這時候 MySQL 就需要執行一個 Update 資料庫的指令來完成我的搶票任務。兩邊的兩個行為,都是為了要完成買票這個目的,我們就稱之為這樣的流程為一筆交易。資料庫管理系統中的交易,和資料庫中的SQL指令一樣,都希望可以具備ACID 的特徵 (忘記了快去複習 Day 1!) ,最簡單的例子就是如果再我猶豫的時候停電了! 這時候這個交易應該被終止,讓下次從新開始。這樣子的行為就是最易開始提到的原子性 (Atomicity),不能再分割。
這時候你的哈瓜朋友也來搶門票,但是它驗證碼一直打錯晚了20秒登入,正巧就在你猶豫的三秒它執行了看售票網站座位的行為。這時候它也猶豫了三秒按下購買第二排中間的那個座位。要維持這兩操作的正確性就必須要確保兩個操作是獨立的 (Isolation)。其實系統或許應該要在哈瓜棚有猶豫點下購買前標註這個位子有人正在結帳? 這個就是資料庫的一致性 (Consistency) 。最後一個自己推。
其實我們以前下的每一個SQL指令都是只有一個指令的交易。我們其實可以手動定義MySQL的交易。
首先我們要先把這個預設在GUI介面中關掉 (它太聰明了),把它反選。
-- created a new table for this example
-- Create table pets (PetID varchar(4) Not Null);
--Insert into pets (PetID) Values ("P1");
--Insert into pets (PetID) Values ("P2");
--Insert into pets (PetID) Values ("P3");
--Insert into pets (PetID) Values ("P4");
--Insert into pets (PetID) Values ("P5");
---------------------------------------------------------
Start Transaction;
Select * from pets;
Delete from pets;
Rollback;
這邊我們執行的四個指令。第一個指令開始交易,這個指令執行之後,MySQL 會知道現在開始的指令先暫時不要執行,因為這是一個系列的行為。接下來我們做了一個很普通的資料查詢。第三行我們把整個資料刪除。最後一行我打了 rollback
來中斷這一個交易。這時候當我們重新執行第二行會發現整個關係表完好的保存在那邊。這就是因為我的交易被中斷並且復原了。同理可證,如果剛剛我們把 rollback
換成 commit
就會真的把那個表格刪掉。
只要兩個交易同時發生,就會有可能發生下面的狀況。這邊每個狀況都可以透過MySQL中的 SET ISOLATION LEVEL
來實現,這邊我就不深入討論。但是有時候某些衝突是對應用程式有幫助的,只是你要知道你正在尋找什麼。
第一種衝突發生在當地一個交易還在執行的過程中,第二個查詢讀取到的是交易中的改變。例如,演唱會我已經買票了但是還沒完成付款,你會先看到我已經把位子選走的資訊。這時候就會發生衝突,因為你看到的是未完成的交易資訊。
和上面不同的是,當第一個交易讀取了資料庫兩次,在這之中,第二筆交易做了資料上的更動,並完成了交易,這會導致第一筆交易讀取到的兩種結果不一樣。
這種衝突發生在,當第一個交易中更改了部分資料的數值,在交易完成前,第二筆交易對同一個資料做了更動,導致第一筆改變的資料被覆寫。
和上面很像,幻影讀取會發生在,當第一筆交易發生兩筆讀取,兩次讀取之間,第二筆交易插入新的資料,這時候就會發現兩個讀取回傳不同的資料庫,就好像看到幻影一樣。
在不同的應用中,我們可以容忍(甚至是需要)的衝突等級會不一樣,所以MySQL 提供了四種交易的讀寫等級:
有興趣的讀者搜尋一下應該就會使用了。