事務:就是一組原子性的SQL查詢,或者說一個獨立的工作單元。

如果資料庫引擎能夠成功的對資料庫應用改組查詢的全部語句,那麼就執行該族語句。如果其中有任何一條語句因為崩潰或其他原因無法執行,那麼所有的語句都不會執行。換句話說就是,事務內的語句,要麼全部執行成功,要麼全部執行失敗。

銀行應用是解釋事務必要性的一個經典例子。假設一個銀行的資料庫有兩張表:支票(checking)表和儲蓄(savings)表。現在要從使用者張三的支票賬戶轉移¥200到他的儲蓄賬戶中,那麼需要至少三個步驟:

檢查支票賬戶的餘額是否高於¥200;

從支票賬戶餘額中減去¥200;

在儲蓄賬戶餘額中增加¥200;

上述三個步驟的操作必須在一個食物中,任何一個步驟失敗,則必須回滾搜有步驟。

可使用START TRANSACTION 語句開始一個事務,然後要麼使用COMMIT提交事務,要麼使用ROLLBACK回滾事務。

START TRANSACTION;

SELECTbalance FROM checking WHERE customer_id=10233276;

UPDATE checking SET balance = balance - 200 WHERE customer_id= 10233276;

UPDATE savings SET balance = balance + 200 WHERE customer_id= 10233276;

COMMIT;

事務的ACID

原子性(atomicity):

一個事務必須被視為一個不可分割的最小工作單元,整個事務中的所有操作要麼全部提交成功,要麼全部失敗回滾,對於一個事務來說,不可能只執行其中的一部分操作,這就是事務的原子性。

一致性(consistency):

資料庫總是從一個一致性的狀態轉換到另外一個一致性的狀態。

隔離性(isolation):

通常來說,一個事務所做的修改在最終提交之前,對其他事務是不可見的。

永續性(durability):

一旦事務提交,則其所作的修改就會永久儲存到資料庫中。

事務的ACID特性可以確保銀行不會弄丟你的錢。

四種隔離級別

READ UNCOMMITTED(未提交讀):在該級別,事務中的修改即使沒有提交,對其他事務也是可見的。事務可以讀取未提交的資料,稱為髒讀(Dirty Read)。實際應用中很少使用。

READ COMMITTED(提交讀):大多數資料庫系統的預設隔離級別都是該級別一個事務從開始直到提交之前,所做的任何修改對其他事務都不可見。該級別在執行兩次同樣的查詢,可能會得到不一樣的結果。

REPEATABLE READ(可重複讀):解決了髒讀的問題,保證了在同一事物中多次讀取同樣記錄的結果是一致的。但無法避免幻讀。所謂幻讀,指的是當某個事務在讀取某個範圍內的記錄時,另外的事務又在該範圍內插入了新的記錄,當之前的事務再次讀取該範圍的記錄時,會產生幻行。InnoDB透過多版本控制(mvcc)解決了幻讀的問題。

SERIALIZATBLE(可序列化):最高的隔離級別,透過強制事務的序列化,避免了前面所說的幻讀的問題。簡單來說,SERIALIZATBLE會再讀取的每一行資料上都加鎖,可能會導致大量的超時和鎖競爭的問題,實際應用中很少使用。

鎖機制:按照維度區分

型別維度

共享鎖(讀鎖 / S 鎖)

排它鎖(寫鎖 / X 鎖)

型別細分:

意向共享鎖

意向排他(互斥)鎖

悲觀鎖(使用鎖,即 for update)

樂觀鎖(使用版本號欄位,類似 CAS 機制,即使用者自己控制。缺點:併發很高的時候,多了很多無用的重試)

2. 鎖的粒度(粒度維度)

表鎖(table lock):MySQL中最基本的鎖策略,並且是開銷最小的策略。在特定場景中,表鎖也可能有良好的效能。例如,READ LOCAL表鎖支援某些型別的併發寫操作。另外,寫鎖也比讀鎖有更高的優先順序,因此一個寫鎖請求可能會被插入到讀鎖佇列的前面。

頁鎖(Mysql BerkeleyDB 引擎)

行鎖(InnoDB):行級鎖可以最大程度的支援併發處理(同時帶來了最大的鎖開銷)。行級鎖只在儲存引擎層實現。

3. 鎖的演算法(演算法維度)

Record Lock(單行記錄)

Gap Lock(間隙鎖,鎖定一個範圍,但不包含鎖定記錄)

Next-Key Lock(Record Lock + Gap Lock,鎖定一個範圍,並且鎖定記錄本身, MySql 防止幻讀,就是使用此鎖實現)

多版本併發控制

MySQL的大多數事務型儲存引擎實現的都不是簡單的行級鎖。基於提升併發效能的考慮,他們一般都會同時實現了多版本併發控制(MVCC)。

MVCC可以認為是行級鎖的一個變種,但是它在很多情況下避免了加鎖操作,因此開銷更低。雖然實現機制有所不同,但大都實現了非阻塞的 讀操作,寫操作也能只鎖定必要的行。

MVCC的實現,是透過儲存資料在某個時間點的快照來實現的。也就是說,不管需要執行多長時間,每個事物看到的資料都是一致的。根據事務開始的時間不同,每個事物對同一張表,同一時刻看到的資料可能是不一樣的。

不同儲存引擎的MVCC是現實不同的,典型的有樂觀併發控制和悲觀併發控制。我們透過InnoDB來說明MVCC是如何工作的。

InnoDB的MVCC,是透過在每行記錄後i按儲存兩個隱藏的列來實現的。這兩個列,一個儲存了行的建立時間,一個儲存行的過期時間(或刪除時間)。當然儲存的並不是實際的時間值,而是系統版本號。每開始一個新的事務,系統版本號都會自動遞增。事務開始時刻的系統版本號會作為事務的版本號,用來和查詢到的每行記錄的版本號進行比較。下面是在REPEARABLE READ隔離級別下,MVCC具體是如何操作的。

SELECT

InnoDB會根據以下兩個條件檢查每行記錄:

InnoDB只查詢版本早於當前事務版本的資料行,這樣可以雀斑事務讀取的行,要麼是在事務開始前就已經存在的,要麼是事物自身插入或修改過的。

行的刪除版本 要麼未定義,要麼大於當前事務版本號。這可以確保事務讀取到的行,在事務開始之前未被刪除。

只有符合上述兩個條件的記錄,才能返回作為查詢結果。

INSERT

InnoDB為新插入的每一行儲存當前系統版本號為行版本號。

DELETE

InnoDB為刪除的每一行儲存當前系統版本號作為行刪除標識。

UPDATE

InnoDB為插入一行新紀錄,儲存當前系統版本號作為行版本號,同時儲存當前系統版本號到原來的行作為行刪除標識。

儲存這兩個額外的系統版本號,是大多數讀操作都可以不用加鎖。這樣設計使得讀資料操作很簡單,效能很好,並且也能保證只會讀取到符合標準的行。不足之處是每一行記錄都需要額外的儲存空間,需要做更多的行檢查工作,以及一些額外的維護工作。

MVCC只在REPEARABLE READ 和 READ COMMITTED兩個隔離級別下工作。其他兩個隔離級別都和MVCC不相容,因為READ UNCOMMITTED總是讀取最新的資料行,而不是符合當前事務版本的資料行。而SERIAIZABLE則會對所有讀取的行都加鎖。