1.3 事務
在理解事務的概念之前,接觸資料庫系統的其他高階特性還言之過早。事務就是一組原子性的SQL查詢
,或者說一個獨立的工作單元。如果資料庫引擎能夠成功地對資料庫應用該組查詢的全部語句,那麼就執行該組查詢。如果其中有任何一條語句因為崩潰或其他原因無法執行,那麼所有的語句都不會執行。也就是說,事務內的語句,要麼全部執行成功,要麼全部執行失敗。
本節的內容並非專屬於MySQL,如果讀者已經熟悉了事務的ACID的概念,可以直接跳轉到1.3.4節。
銀行應用是解釋事務必要性的一個經典例子。假設一個銀行的資料庫有兩張表:支票(checking )表和儲蓄(savings )表。現在要從使用者Jane的支票賬戶轉移200美元到她的儲蓄賬戶,那麼需要至少三個步驟:
-
檢查支票賬戶的餘額高於200美元。
-
從支票賬戶餘額中減去200美元。
-
在儲蓄賬戶餘額中增加200美元。
上述三個步驟的操作必須打包在一個事務中,任何一個步驟失敗,則必須回滾所有的步驟。
可以用START TRANSACTION 語句開始一個事務,然後要麼使用COMMIT 提交事務將修改的資料持久保留,要麼使用ROLLBACK 撤銷所有的修改。事務SQL的樣本如下:
1 START TRANSACTION;
2 SELECT balance FROM checking WHERE customer_id = 10233276;
3 UPDATE checking SET balance = balance - 200.00 WHERE customer_id = 10233276;
4 UPDATE savings SET balance = balance + 200.00 WHERE customer_id = 10233276;
5 COMMIT;
單純的事務概念並不是故事的全部。試想一下,如果執行到第四條語句時伺服器崩潰了,會發生什麼?天知道,使用者可能會損失200美元。再假如,在執行到第三條語句和第四條語句之間時,另外一個程序要刪除支票賬戶的所有餘額,那麼結果可能就是銀行在不知道這個邏輯的情況下白白給了Jane 200美元。除非系統透過嚴格的ACID測試,否則空談事務的概念是不夠的。
ACID表示原子性(atomicity)
、一致性(consistency)
、隔離性(isolation)
和永續性(durability)
。一個執行良好的事務處理系統,必須具備這些標準特徵。
原子性(atomicity)
一個事務必須被視為一個不可分割的最小工作單元,整個事務中的所有操作要麼全部提交成功,要麼全部失敗回滾,對於一個事務來說,不可能只執行其中的一部分操作,這就是事務的原子性。
一致性(consistency)
資料庫總是從一個一致性的狀態轉換到另外一個一致性的狀態。在前面的例子中,一致性確保了,即使在執行第三、四條語句之間時系統崩潰,支票賬戶中也不會損失200美元,因為事務最終沒有提交,所以事務中所做的修改也不會儲存到資料庫中。
隔離性(isolation)
通常來說,一個事務所做的修改
在最終提交以前
,對其他事務是不可見
的。在前面的例子中,當執行完第三條語句、第四條語句還未開始時,此時有另外一個賬戶彙總程式開始執行,則其看到的支票賬戶的餘額並沒有被減去200美元。後面我們討論隔離級別(Isolation level)的時候,會發現為什麼我們要說“通常來說”是不可見的。
永續性(durability)
一旦事務提交,則其所做的修改就會永久儲存到資料庫中。此時即使系統崩潰,修改的資料也不會丟失。永續性是個有點模糊的概念,因為實際上永續性也分很多不同的級別。有些永續性策略能夠提供非常強的安全保障,而有些則未必。而且不可能有能做到100%的永續性保證的策略(如果資料庫本身就能做到真正的永續性,那麼備份又怎麼能增加永續性呢?)。在後面的一些章節中,我們會繼續討論MySQL中永續性的真正含義。
事務的ACID特性可以確保銀行不會弄丟你的錢。而在應用邏輯中,要實現這一點非常難,甚至可以說是不可能完成的任務。一個相容 ACID的資料庫系統,需要做很多複雜但可能使用者並沒有覺察到的工作,才能確保ACID的實現。就像鎖粒度的升級會增加系統開銷一樣,這種事務處理過程中額外的安全性,也會需要資料庫系統做更多的額外工作。一個實現了ACID的資料庫,相比沒有實現ACID的資料庫,通常會需要更強的CPU處理能力、更大的記憶體和更多的磁碟空間。正如本章不斷重複的,這也正是MySQL的儲存引擎架構可以發揮優勢的地方。使用者可以根據業務是否需要事務處理,來選擇合適的儲存引擎。對於一些不需要事務的查詢類應用,選擇一個非事務型的儲存引擎,可以獲得更高的效能。即使儲存引擎不支援事務,也可以透過LOCK ABLES 語句為應用提供一定程度的保護,這些選擇使用者都可以自主決定。
1.3.1 隔離級別
下面介紹四種因為事務的併發產生的問題:
髒寫
假設銀行賬戶中有100元,
有事務A,B
A事務把餘額改為200,但還未提交
這時,B事務把餘額改為300,提交,
如果A發生了回滾,那麼賬戶餘額回到100元,事務B無效。
髒寫就是已經提交的事務的寫操作
因為另一個還未提交的事務的回滾而失去效果,請注意這個失效的事務應該是嵌在另一個事務的中間。
髒讀
髒讀與髒寫十分類似,
髒讀是因為另一個未完成的事務,發生了回滾,導致已提交的事務讀出
的資料無效。
繼續銀行賬戶的例子,
如果銀行賬務中有100元,
有事務AB,
事務A首先開啟,修改了賬戶餘額為200,這時,
事務B開啟,讀到賬戶餘額為200,事務B結束。
然後事務A發生了回滾,200這個數字失效,也就是B讀到的是一個無效的數字,我們稱之為髒讀。
(髒讀,髒寫都是因為可以看到未提交的資料)
不可重複讀
繼續之前銀行賬戶的例子,
有事務AB,銀行賬戶中有100元
事務A開啟,讀出100元,
事務B開啟,將100元修改為200元,
這時A又一次讀取餘額,發現餘額竟然是200元。
在一個事務之內,讀到了不一致的值
,我們稱之為不可重複讀。
不可重複讀是因為在一個事務兩次讀操作的中間
有另一個事務進行了寫操作。
幻讀
與不可重複讀十分類似,
但是這次要換一個例子,假設有一個班級的成績單,其中有10人及格,
開啟事務A,讀到10人及格,
這時開啟事務B,又向成績表中寫入了10條及格的資料,
事務A再次查詢及格記錄,發現竟然有20人及格。
一個事務兩次讀取的資料量
不一致,我們稱之為幻讀。
好像出現了幻覺。
隔離性其實比想象的要複雜。在SQL標準中定義了四種隔離級別,每一種級別都規定了一個事務中所做的修改,哪些在事務內和事務間是可見的,哪些是不可見的。較低階別的隔離通常可以執行更高的併發,系統的開銷也更低。每種儲存引擎實現的隔離級別不盡相同。如果熟悉其他的資料庫產品,可能會發現某些特性和你期望的會有些不一樣(但本節不打算討論更詳細的內容)。讀者可以根據所選擇的儲存引擎,查閱相關的手冊。
下面簡單地介紹一下四種隔離級別。
READ UNCOMMITTED(未提交讀)
有回滾的風險
在READ UNCOMMITTED 級別,事務中的修改,即使沒有提交,對其他事務也都是可見的。事務可以讀取未提交的資料,這也被稱為髒讀(Dirty Read)
(萬一這個未提交的資料後面發生了回滾
,那麼這次讀的資料就是無效的,即髒資料
)。這個級別會導致很多問題,從效能上來說,READ UNCOMMITTED 不會比其他的級別好太多,但卻缺乏其他級別的很多好處,除非真的有非常必要的理由,在實際應用中一般很少使用。
READ COMMITTED(提交讀)
沒有考慮事務發生的先後順序
大多數資料庫系統的預設隔離級別都是READ COMMITTED (但MySQL不是)。READ COMMITTED 滿足前面提到的隔離性的簡單定義:一個事務開始時,只能“看見”已經提交的事務所做的修改。換句話說,一個事務從開始直到提交之前,所做的任何修改對其他事務都是不可見的。這個級別有時候也叫做不可重複讀(nonrepeatable read)
,(可以看到已經提交的修改,意味著在一個事務的執行過程中,第一次讀的資料和第二次讀的資料之間可能有另一個事務對這個資料進行了修改並提交,第二次讀到的資料就是已經被修改提交的資料)因為兩次執行同樣的查詢,可能會得到不一樣的結果。
REPEATABLE READ(可重複讀)
REPEATABLE READ 解決了髒讀的問題。該級別保證了在同一個事務中多次讀取同樣記錄的結果是一致的。但是理論上,可重複讀隔離級別還是無法解決另外一個幻讀(Phantom Read)的問題。所謂幻讀,指的是當某個事務在讀取某個範圍內的記錄時,另外一個事務又在該範圍內插入了新的記錄,當之前的事務再次讀取該範圍的記錄時,會產生幻行(Phantom Row)。InnoDB和XtraDB儲存引擎透過多版本併發控制(MVCC,Multiversion Concurrency Control)解決了幻讀的問題。本章稍後會做進一步的討論。
可重複讀
是MySQL的預設事務隔離級別。
SERIALIZABLE(可序列化)
SERIALIZABLE 是最高的隔離級別。它透過強制事務序列執行,避免了前面說的幻讀的問題。簡單來說,SERIALIZABLE 會在讀取的每一行資料上都加鎖,所以可能導致大量的超時和鎖爭用的問題。實際應用中也很少用到這個隔離級別,只有在非常需要確保資料的一致性而且可以接受沒有併發的情況下,才考慮採用該級別。
![](E:\面試資料\高效能Mysql\圖片附件\sql 隔離級別.png)