事務就是一組原子性的SQL查詢,或者說一個獨立的工做單元。若是數據庫引擎可以成功地對數據庫應用該組查詢的所有語句,那麼就執行該組查詢。若是其中有任何一條語句由於崩潰或其餘緣由沒法執行,那麼全部的語句都不會執行。也就是說,事務內的語句,要麼所有執行成功,要麼所有執行失敗。在整個過程當中,不管事務是否成功完成,總能確保數據的完整性。mysql
銀行應用是解釋事務必要性的一個經典例子。
假設一個銀行的數據庫有兩張表:支票(checking)表和儲蓄(savings)表。如今要從用戶的支票帳戶轉移 200 元到他的儲蓄帳戶,那麼須要至少三個步驟:算法
上述三個步驟的操做必須打包在一個事務中,任何一個步驟失敗,則必須回滾全部的步驟。sql
能夠用 START TRANSACTION
語句開始一個事務,而後要麼使用 COMMIT
提交事務將修改的數據持久保留,要麼使用 ROLLBACK
撤銷全部的修改。
事務SQL的樣本以下:數據庫
/* 開始事務 */ START TRANSACTION; /* 檢查支票帳戶的餘額高於 200 元 */ SELECT balance FROM checking WHERE customer_id=10233276; /* 從支票帳戶餘額中減去 200 元 */ UPDATE checking SET balance=balance-200.00 WHERE customer_id=10233276; /* 在儲蓄帳戶餘額中增長 200 元 */ UPDATE savings SET balance=balance+200.00 WHERE customer_id=10233276; /* 提交事務 */ COMMIT;
ACID表示原子性 (atomicity)、一致性(consistency)、隔離性(isolation)和持久性(durability)。一個運行良好的事務處理系統,必須具有這些標準特徵。服務器
一個事務必須被視爲一個不可分割的最小工做單元,整個事務中的全部操做要麼所有提交成功,要麼所有失敗回滾。對於一個事務來講,不可能只執行其中的一部分操做,這就是事務的原子性。併發
數據庫老是從一個一致性的狀態轉換到另一個一致性的狀態。在前面的例子中,一致性確保了,即便在執行第3、四條語句之間時系統崩潰,支票帳戶中也不會損失200元,由於事務最終沒有提交,因此事務中所作的修改也不會保存到數據庫中。性能
一般來講,一個事務所作的修改在最終提交之前,對其餘事務是不可見的。在前面的例子中,當執行完第三條語句、第四條語句還未開始時,此時有另一個帳戶彙總程序開始運行,則其看到的支票帳戶的餘額並無被減去200元。atom
一旦事務提交,則其所作的修改就會永久保存到數據庫中。此時即便系統崩潰,修改的數據也不會丟失。設計
在 READ UNCOMMITTED
級別,事務中的修改,即便沒有提交,對其餘事務也都是可見的。事務能夠讀取未提交的數據,這也被稱爲髒讀(Dirty Read) 。這個級別會致使不少問題,從性能上來講,READ UNCOMMITTED
不會比其餘的級別好太多,但卻缺少其餘級別的不少好處,除非真的有很是必要的理由,在實際應用中通常不多使用。code
大多數數據庫系統的默認隔離級別都是 READ COMMITTED
(但MysQL不是)。READ COMMITTED
知足前面提到的隔離性的簡單定義:一個事務開始時,只能「看見」已經提交的事務所作的修改。換句話說,一個事務從開始直到提交以前,所作的任何修改對其餘事務都是不可見的。這個級別有時候也叫作不可重複讀(nonrepeatable read
),由於兩次執行一樣的查詢,可能會獲得不同的結果。
REPEATABLE READ
解決了髒讀的問題。該級別保證了在同一個事務中屢次讀取一樣記錄的結果是一致的。可是理論上,可重複讀隔離級別仍是沒法解決另一個幻讀(Phantom Read
)的問題。所謂幻讀,指的是當某個事務在讀取某個範圍內的記錄時,另一個事務又在該範圍內插入了新的記錄,當以前的事務再次讀取該範圍的記錄時,會產生幻行(Phantom Row
)。InnoDB
和 XtraDB
存儲引擎經過多版本併發控制(MVCC, Multiversion Concurrency Control)解決了幻讀的問題。
可重複讀是MySQL的默認事務隔離級別。
SERIALIZABLE
是最高的隔離級別。它經過強制事務串行執行,避免了前面說的幻讀的問題。簡單來講,SERIALIZABLE
會在讀取的每一行數據上都加鎖,因此可能致使大量的超時和鎖爭用的問題。實際應用中也不多用到這個隔離級別,只有在很是須要確保數據的一致性並且能夠接受沒有併發的狀況下,才考慮採用該級別。
隔離級別 | 髒讀可能性 | 不可重複讀可能性 | 幻讀可能性 | 加鎖讀 |
---|---|---|---|---|
READ UNCOMMITTED | √ | √ | √ | × |
READ COMMITTED | × | √ | √ | × |
REPEATABLE READ | × | × | √ | × |
SERIALIZABLE | × | × | × | √ |
死鎖是指兩個或者多個事務在同一資源上相互佔用,並請求鎖定對方佔用的資源,從而致使惡性循環的現象。當多個事務試圖以不一樣的順序鎖定資源時,就可能會產生死鎖。多個事務同時鎖定同一個資源時,也會產生死鎖。
設想下面兩個事務同時處理 StockPrice 表:
START TRANSACTION; UPDATE StockPrice SET close=45.50 WHERE stock_id=4 and date='2002-05-01'; UPDATE StockPrice SET close=19.80 WHERE stock_id=3 and date='2002-05-02'; COMMIT;
START TRANSACTION; UPDATE StockPrice SET high=20.12 WHERE stock_id=3 and date='2002-05-02'; UPDATE StockPrice SET high=47.20 WHERE stock_id=4 and date='2002-05-01'; COMMIT;
若是湊巧,兩個事務都執行了第一條 UPDATE
語句,更新了一行數據,同時也鎖定了該行數據,接着每一個事務都嘗試去執行第二條 UPDATE
語句,卻發現該行已經被對方鎖定,而後兩個事務都等待對方釋放鎖,同時又持有對方須要的鎖,則陷入死循環。除非有外部因素介入纔可能解除死鎖。
爲了解決死鎖問題,數據庫系統實現了各類死鎖檢測和死鎖超時機制。越複雜的系統,好比 InnoDB
存儲引擎,越能檢測到死鎖的循環依賴,並當即返回一個錯誤。這種解決方式頗有效,不然死鎖會致使出現很是慢的查詢。還有一種解決方式,就是當查詢的時間達到鎖等待超時的設定後放棄鎖請求,這種方式一般來講不太好。InnoDB
目前處理死鎖的方法是,將持有最少行級排他鎖的事務進行回滾(這是相對比較簡單的死鎖回滾算法)。
鎖的行爲和順序是和存儲引擎相關的。以一樣的順序執行語句,有些存儲引擎會產生死鎖,有些則不會。死鎖的產生有雙重緣由:有些是由於真正的數據衝突,這種狀況一般很難避免,但有些則徹底是因爲存儲引擎的實現方式致使的。死鎖發生之後,只有部分或者徹底回滾其中一個事務,才能打破死鎖。對於事務型的系統,這是沒法避免的,因此應用程序在設計時必須考慮如何處理死鎖。大多數狀況下只須要從新執行因死鎖回滾的事務便可。
MySQL默認採用自動提交(AUTOCOMMIT
)模式。也就是說,若是不是顯式地開始一個事務,則每一個查詢都被看成一個事務執行提交操做。在當前鏈接中,能夠經過設置 AUTOCOMMIT
變量來啓用或者禁用自動提交模式:
mysql> SHOW VARIABLES LIKE 'AUTOCOMMIT'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.00 sec) mysql> SET AUTOCOMMIT=0; Query OK, 0 rows affected (0.00 sec) mysql> SHOW VARIABLES LIKE 'AUTOCOMMIT'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | OFF | +---------------+-------+ 1 row in set (0.00 sec) mysql]> SET AUTOCOMMIT=1; Query OK, 0 rows affected (0.00 sec) mysql> SHOW VARIABLES LIKE 'AUTOCOMMIT'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.00 sec)
1 或者 ON 表示啓用,0 或者 OFF表示禁用。當 AUTOCOMMIT=0
時,全部的查詢都是在一個事務中,直到顯式地執行 COMMIT
提交或者 ROLLBACK
回滾,該事務結束,同時又開始了另外一個新事務。修改 AUTOCOMMIT
對非事務型的表,好比 MyISAM
或者內存表,不會有任何影響。對這類表來講,沒有 COMMIT
或者 ROLLBACK
的概念,也能夠說是至關於一直處於 AUTOCOMMIT
啓用的模式。
另外還有一些命令,在執行以前會強制執行 COMMIT
提交當前的活動事務。典型的例子,在數據定義語言(DDL)中,若是是會致使大量數據改變的操做,好比 ALTER TABLE
,就是如此。另外還有 LOCK TABLES
等其餘語句也會致使一樣的結果。若是有須要,請檢查對應版本的官方文檔來確認全部可能致使自動提交的語句列表。MySQL能夠經過執行 SET TRANSACTION ISOLATION LEVEL
命令來設置隔離級別。新的隔離級別會在下一個事務開始的時候生效。能夠在配置文件中設置整個數據庫的隔離級別,也能夠只改變當前會話的隔離級別:
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
InnoDB採用的是兩階段鎖定協議(two-phase locking protocol)。在事務執行過程當中,隨時均可以執行鎖定,鎖只有在執行 COMMIT
或者 ROLLBACK
的時候纔會釋放,而且全部的鎖是在同一時刻被釋放。前面描述的鎖定都是隱式鎖定,InnoDB
會根據隔離級別在須要的時候自動加鎖。
另外,InnoDB
也支持經過特定的語句進行顯式鎖定,這些語句不屬於SQL規範(這些鎖定提示常常被濫用,實際上應當儘可能避免使用)
MySQL 也支持 LOCK TABLES
和 UNLOCK TABLES
語句,這是在服務器層實現的,和存儲引擎無關。它們有本身的用途,但並不能替代事務處理。若是應用須要用到事務,仍是應該選擇事務型存儲引擎。
常常能夠發現,應用已經將表從 MyISAM
轉換到 InnoDB
,但仍是顯式地使用 LOCK TABLES
語句。這不但沒有必要,還會嚴重影響性能,實際上 InnoDB
的行級鎖工做得更好。
LOCK TABLES
和事務之間相互影響的話,狀況會變得很是複雜,在某些 MySQL 版本中甚至會產生沒法預料的結果。所以,建議除了事務中禁用了 AUTOCOMMIT
,可使用 LOCK TABLES
以外,其餘任什麼時候候都不要顯式地執行 LOCK TABLES
,無論使用的是什麼存儲引擎。
參考《高性能MySQL》