對 MySQL 來講,事務一般是一組包含對數據庫操做的集合。在執行時,只有在該組內的事務都執行成功,這個事務纔算執行成功,不然就算失敗。MySQL 中,事務支持是在引擎層實現的,像 MySQL 原生的 MyISAM 引擎就不支持事務,這也是被 InooDB 取代的重要緣由。mysql
爲何要有事務呢,舉個例子來講,你的帳戶有 100 元,如今想給朋友轉帳 100 元。其中就會包含兩個很重要的操做,你的帳戶減 100 元,朋友帳戶多 100 元。因爲轉帳過程當中出現失敗是很常見的,假設操做不包含在事務內,你的帳戶減錢操做成功,朋友帳戶加錢操做失敗。就會出現,你的帳戶扣錢,對方沒有收到錢的狀況。sql
再好比,在發起轉帳操做時,因爲系統須要進行像查詢餘額,計算,更新餘額的操做,若是在等待時間內,又發起了轉帳操做,但目前更新餘額的操做尚未成功,就會出現你的 100 元,能夠給別人轉帳屢次的狀況,這對於銀行來講是確定不容許的。數據庫
對於一個事務來講一般要知足四個特性,也就是一般所說的 ACID:併發
Atomicity - 保證在一個工做單元(就是一組操做)中全部的操做都執行成功,不然的話當前這個事務就會失敗,以前的操做都被會回滾。框架
Consistency - 保證一個事務被成功提交後,數據庫的狀態是從一致性狀態變成另外一個一致性狀態。性能
Isolation - 保證每一個事務中的操做時是獨立的,對於其餘事務沒有影響。日誌
Durability - 對於已經提交的事務,即便在數據庫損壞的狀況下,也不會形成數據的丟失和損壞。code
當數據庫中有多個事務同時執行時,就可能會出現髒讀,幻讀,不可重複讀的問題,爲了解決這些問題,就出現了"隔離級別"的概念。orm
髒讀:事務 A 中訪問了事務 B 中未提交的數據。這裏 Transaction 1 讀到了,Transaction 2 中未提交的年齡數據。blog
不可重複讀:事務 A 中屢次查詢同一數據,但因爲事務 B 在事務 A 兩次查詢中,修改了改數據的值,致使兩次查詢的結果不同。下面 Transaction 1 中的兩次查詢查詢結果是一致的,第二次讀到的 age 已經被修改的內容。
幻讀:一般發生在事務 B 對事務 A 正在讀取的內容,添加或刪除了一條數據。形成數據莫名出現或者消失的狀況。這裏 Transaction 1 中,兩次查詢的結果並不一致,第二次查詢會多出一條記錄。
隔離級別 | 解釋 | 可能出現的問題 |
---|---|---|
讀未提交 | 讀未提交是指,一個事務還沒提交時,它作的變動就能被別的事務看到。 | 髒讀,不可重複讀,幻讀 |
讀提交 | 讀提交是指,一個事務提交以後,它作的變動纔會被其餘事務看到。 | 不可重複讀,幻讀 |
可重複讀 | 可重複讀是指,一個事務執行過程當中看到的數據,老是跟這個事務在啓動時看到的數據是一致的。固然在可重複讀隔離級別下,未提交變動對其餘事務也是不可見的。 | 幻讀 |
串行化 | 串行化,顧名思義是對於同一行記錄,「寫」會加「寫鎖」,「讀」會加「讀鎖」。當出現讀寫鎖衝突的時候,後訪問的事務必須等前一個事務執行完成,才能繼續執行。 | 無 |
在 MySQL 中 RR 級別引入了間隙鎖,解決了幻讀的問題。
舉一個實際的例子,來看一下這四種隔離級別對應的結果,假設表結構以下:
mysql> create table T(c int) engine=InnoDB; insert into T(c) values(1);
此時發生的事務以下:
隔離級別 | 返回結果 |
---|---|
讀未提交 | V1 是 2。事務 B 雖然尚未提交,可是結果已經被 A 看到了。所以,V二、V3 也都是 2 |
讀提交 | V1 是 1,V2 的值是 2。事務 B 的更新在提交後才能被 A 看到。因此, V3 的值也是 2。 |
可重複讀 | 則 V一、V2 是 1,V3 是 2。 V2 仍是 1 的緣由,須要遵循:事務在執行期間看到的數據先後必須是一致的。 |
串行化 | 事務 B 執行「將 1 改爲 2」時,會被鎖住。直到事務 A 提交後,事務 B 才能夠繼續執行。因此從 A 的角度看, V一、V2 值是 1,V3 的值是 2。 |
執行的效率會和執行的級別有關,隔離的越高,效率越低,須要在兩者間尋找平衡。
讀未提交:
讀提交:
可重複讀:
串行化:
在實現上,數據庫裏面會建立一個視圖,訪問的時候以視圖的邏輯結果爲準。
注意這個視圖不是用於查詢定義的虛擬表,而是在 InnoDB 中實現 MVCC 用到的一致性讀視圖(consistent read view),用於支持 RC 和 RR 隔離級別的實現。
可重複讀的具體實現:
在 MySQL 中,實際上每條記錄在更新時都會同時記錄一條回滾操做。記錄上的最新值,經過回滾均可以獲得前一個狀態的值。好比一個值 從 1 按照順序,被修改爲 二、三、4 ,就會在回滾日誌中有以下的記錄。
當前最新是 4,在查詢這條記錄時,不一樣時刻啓動的事務會有不一樣的 read-view. 在視圖 A B C 中,記錄值爲 1, 2, 4. 同一條記錄能夠存在多個版本,這就是數據庫多版本併發控制(MVCC)。對於 read-view A 來講,要獲得 1,就必須將當前值依次執行圖中全部的回滾操做獲得。假如,有另一個事務將 4 改爲 5,但對於視圖 A B C 來講,事務是不衝突的。
既然每一條記錄都會更新是都會產生一條回滾操做記錄,時間一長,確定會佔用大量的存儲空間。那麼系統會在何時刪除這些回滾日誌呢,就是在當前系統裏不存在比該回滾日誌更早的 read-view 時。
但若是系統裏存在着很老的事務視圖。因爲這些事務可能會訪問數據庫裏的任何數據,因此在事務提交以前,全部可能用到的回滾記錄都必須保留,這就可能出現佔用大量存儲空間的狀況。
在 MySQL 5.5 以前,回滾日誌和數據字典一塊兒放在 ibdata 文件裏,即便長事務被提交,回滾段被清理,文件也不會變小。
而且長事務還佔用鎖資源,也可能拖垮整個庫。
顯式啓動事務:
# 使用 START TRANSACTION 或者 BEGIN 開啓事務: START TRANSACTION [transaction_characteristic [, transaction_characteristic] ...] transaction_characteristic: { WITH CONSISTENT SNAPSHOT | READ WRITE | READ ONLY } BEGIN [WORK] # 使用 COMMIT 來提交事務 COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE] # 使用 ROLLBACK 來回滾事務 ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
隱式啓動事務:
# 設置當前事務的是否自動提交 SET autocommit = {0 | 1}
autocommit 的討論:
commit work and chain
語法。多一次交互的問題,若是採用 autocommit=0 的這種方式,不須要每次輸入 begin ,減小了語句的交互次數。
查詢長事務:
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
避免長事務的方案:
在開始部分,介紹了 MySQL 中事務的概念,並回顧了事務的 ACID 的特性。接着探討了事務隔離的可能出現的髒讀,不可重複讀以及幻讀的問題,並給出了相應的解決方案-隔離級別。並分析了常見事務隔離的應用場景以及事務隔離的實現方式。
並在最後引出了回滾段的概念,以及爲何要避免使用長事務。並給出了開啓事務的方法。