最近買了《高性能MySQL》這本書回來看,從中收益頗多!我來一吐為快! 我們都知道事務,那麼在什麼情況下我們需要使用事務呢? 銀行應用是解釋事務的一個經典例子。假設一個銀行的資料庫有兩張表:支票(checking)和儲蓄(savings)表。現在johnson要從支票賬戶中轉移200塊大洋到儲蓄表中 ...
最近買了《高性能MySQL》這本書回來看,從中收益頗多!我來一吐為快!
我們都知道事務,那麼在什麼情況下我們需要使用事務呢?
銀行應用是解釋事務的一個經典例子。假設一個銀行的資料庫有兩張表:支票(checking)和儲蓄(savings)表。現在johnson要從支票賬戶中轉移200塊大洋到儲蓄表中,那麼至少需要三個步驟:
- 檢查支票賬戶餘額是否高於200塊大洋
- 支票賬戶減少200塊大洋
- 儲蓄賬戶中增加200塊大洋
試想一下,如果上面步驟執行到第二步,突然因為什麼原因而終止了,顧客支票賬戶中莫名其妙的減少了200塊大洋。如果顧客恰好是一位情緒激動的大媽,那你就等著大媽帶著平底鍋和四級頭去銀行找你吧!
所以為了避免這種情況,就必須用到事務,上述三個步驟中有任何一個執行失敗,就必須回滾所有的步驟,以免有大媽找上門。事務SQL如下所示:
- START TRANSACTION;
- SELECT balance FROM checking WHERE customer_id=123456;
- UPDATE checking SET balance = balance - 200 WHERE customer_id=123456;
- UPDATE savings SET balance = balance + 200 WHERE customer_id=123456;
- COMMIT;
事務之所以可靠,當然離不開ACID特性:
- 原子性(atomicity):整個事務中的操作要麼全部成功,要麼全部失敗。
- 一致性(consistency):資料庫總是從一個一致性狀態轉換到另一個一致性狀態。比如上面所說的,事務開始前和執行後,顧客johnson在銀行的總賬戶餘額是一樣的。
- 隔離性(isolation):通常來說,一個事務所做的修改在提交之前,其他事務是不可見的。也就是說事務間是相互隔離的。
- 持久性(durability):事務在提交之後,對資料庫數據所做的修改是永久性的。
細心的人可能會註意到。在討論隔離性的時候,我用了“通常來說”,下麵就讓我們討論下事務的隔離級別。
隔離級別 | 臟讀可能性 | 不可重覆讀可能性 | 幻讀可能性 | 加鎖讀 |
READ UNCOMMITTED | YES | YES | YES | NO |
READ COMMITTED | NO | YES | YES | NO |
REPEATABLE READ | NO | NO | YES | NO |
SERIALIZABLE | NO | NO | NO | YES |
- 未提交讀(READ UNCOMMITTED):事務中的修改,即使沒有提交,其他事務也可以讀到,這就有可能造成了臟讀。
- 提交讀(READ COMMITTED):大多數資料庫系統預設實用的隔離級別就是這種,但mysql不是。READ COMMITTED就是在事務提交前,所做的修改對其他事務是不可見的。但READ COMMITTED可能會造成不可重覆讀。就是在一個事務中,同樣的查詢語句,可能會得到不一樣的結果。其實就是在兩次查詢中間,另一個事務修改了查詢結果的值。
- 可重覆讀(REPETABLE READ):REPETABLE READ解決了臟讀和不可重覆讀的問題,但理論上,REPETABLE READ無法解決幻讀的問題。幻讀就是指,一個事務在讀取某一範圍的值時,另一個事務恰好在該範圍內插入了新紀錄,那麼當你再次讀取該範圍的值時,就會產生幻行。這與不可重覆讀有點像,只不過不可重覆讀時UPDATE,而幻讀時INSERT。
- 可串列化(SERIALIZABLE):SERIALIZABLE讀取每一行數據都要加鎖,強制事務串列執行,所以可能導致大量的超時和鎖爭用問題。
到這裡,如果還不是太懂,你需要細細消化下前面的內容,這時可以打開mysql,將隔離級別設置為READ COMMITTED。然後試試它是不是解決了臟讀,會不會出現不可重覆讀?再將隔離級別設置為REPETABLE READ。看看REPETABLE READ是不是解決了不可重覆讀,會不會出現幻讀?
SET session transaction isolation level read committed;
如果你真的實驗了,會發現mysql的REPETABLE READ隔離級別並不會出現幻讀的現象。那你有沒有想過mysql的事務是怎麼實現的呢?
你肯定聽說mysql的表鎖和行鎖,那你可能以為事務是基於行鎖實現的。其實並沒有那麼簡單,為了提高併發性能,mysql的大多是事務引擎都同時實現了多版本併發控制(MVCC)。它在很多情況下避免了加鎖操作,所以開銷更低。MVCC大都實現了非阻塞的讀操作,寫操作也只鎖定必要的行。
那麼InnoDB中的MVCC是如何工作的呢?其實是通過在每行數據後面增加兩個列,一個是創建版本號,一個是刪除版本號。裡面存儲的是系統版本號,你開啟一個事務系統版本號就會遞增。事務開始時刻的系統版本號就作為事務版本號,用來和查詢的每行記錄的版本號做比較。下麵看下REPETABLE READ隔離界別下,MVCC具體是如何操作的。
- SELECT查詢出的數據需要滿足2個條件 1、創建版本號 <= 系統版本號 2、刪除版本號為空或刪除版本號>系統版本號
- INSERT 為新插入的每一行保存當前事務版本號為行的創建版本號
- UPDATE 為插入的一行新記錄保存當前事務版本號為行的創建版本號,同時保存當前事務版本號為原來的行的刪除版本號
- DELETE 為刪除的每一行保存當前事務版本號為行的刪除版本號
保存這兩個額外的系統版本號,可以使大多數讀操作都不用加鎖,這樣性能就會更好。但需要額外的存儲空間和一些額外的檢查工作,這也相當於用空間換時間。
在某些情況下我們還是需要用的鎖。InnoDB採用兩段鎖協議。在事務執行過程中隨時都可以加鎖,事務提交或回滾時同時釋放所有鎖。這些鎖一般都是隱式鎖定,InnoDB會根據需要自動加鎖。當然,你也可以通過SQL語句自己加鎖:
SELECT ..... LOCK IN SHARE MODE; 樂觀鎖
SELECT ..... FOR UPDATE; 悲觀鎖
個人建議,除非你明確知道自己在乾什麼,否則輕易不要顯式加鎖,只會事倍功半!!!