數據庫事務指的是一組數據操做,事務內的操做要麼就是所有成功,要麼就是所有失敗,什麼都不作,其實不是沒作,是可能作了一部分可是隻要有一步失敗,就要回滾全部操做,有點一不作二不休的意思。html
在 MySQL 中,事務支持是在引擎層實現的。MySQL 是一個支持多引擎的系統,但並非全部的引擎都支持事務。好比 MySQL 原生的 MyISAM 引擎就不支持事務,這也是 MyISAM 被 InnoDB 取代的重要緣由之一。前端
SQL 事務的四大特性中原子性、一致性、持久性都比較好理解。但事務的隔離級別確實比較難的,今天主要聊聊 MySQL 事務的隔離性。java
SQL 標準的事務隔離從低到高級別依次是:讀未提交(read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和串行化(serializable )。級別越高,效率越低。面試
SQL 事務隔離級別的設計就是爲了能最大限度的解決併發問題:算法
SQL 不一樣的事務隔離級別能解決的併發問題也不同,以下表所示:只有串行化的隔離級別解決了所有這 3 個問題,其餘的 3 個隔離級別都有缺陷。sql
事務隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
讀未提交 | 可能 | 可能 | 可能 |
讀已提交 | 不可能 | 可能 | 可能 |
可重複讀 | 不可能 | 不可能 | 可能 |
串行化 | 不可能 | 不可能 | 不可能 |
PS:不可重複讀的和幻讀很容易混淆,不可重複讀側重於修改,幻讀側重於新增或刪除。解決不可重複讀的問題只需鎖住知足條件的行,解決幻讀須要鎖表數據庫
這麼說可能有點難以理解,舉個栗子。仍是以前的表結構以及表數據編程
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`age` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 66 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
複製代碼
假設如今,我要同時啓動兩個食物,一個事務 A 查詢 id = 2 的學生的 age,一個事務 B 更新 id = 2 的學生的 age。流程以下,在四種隔離級別下的 X一、X二、X3 的值分別是怎樣的呢?設計模式
那爲何會出現這樣的結果呢?事務隔離級別究竟是怎麼實現的呢?數組
事務隔離級別是怎麼是實現的呢?我在極客時間丁奇老師的課上找到了答案:
實際上,數據庫裏面會建立一個視圖,訪問的時候以視圖的邏輯結果爲準。在 「可重複讀」 隔離級別下,這個視圖是在事務啓動時建立的,整個事務存在期間都用這個視圖。在 「讀提交」 隔離級別下,這個視圖是在每一個 SQL 語句開始執行的時候建立的。這裏須要注意的是,「讀未提交」 隔離級別下直接返回記錄上的最新值,沒有視圖概念;而 「串行化」 隔離級別下直接用加鎖的方式來避免並行訪問。
不一樣的數據庫默認設置的事務隔離級別也大不同,Oracle 數據庫的默認隔離級別是讀提交,而 MySQL 是可重複讀。因此,當你的系統須要把數據庫從 Oracle 遷移到 MySQL 時,請把級別設置成與搬遷以前的(讀提交)一致,避免出現不可預測的問題。
# 查看事務隔離級別
5.7.20 以前
SELECT @@transaction_isolation
show variables like 'transaction_isolation';
# 5.7.20 以及以後
SELECT @@tx_isolation
show variables like 'tx_isolation'
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+
複製代碼
修改隔離級別語句格式是:set [做用域] transaction isolation level [事務隔離級別]
其中做用域可選:SESSION(會話)、GLOBAL(全局);隔離級別就是上面提到的 4 種,不區分大小寫。
例如:設置全局隔離級別爲讀提交
set global transaction isolation level read committed;
複製代碼
MySQL 的事務啓動有如下幾種方式:
# 更新學生名字
START TRANSACTION;
update student set name = '張三' where id = 2;
commit;
複製代碼
set autocommit = 0,這個命令會將線程的自動提交關掉。意味着若是你只執行一個 select 語句,這個事務就啓動了,並且並不會自動提交。這個事務持續存在直到你主動執行 commit 或 rollback 語句,或者斷開鏈接。
set autocommit = 1,表示 MySQL 自動開啓和提交事務。 好比執行一個 update 語句,語句只完成後就自動提交了。不須要顯示的使用 begin、commit 來開啓和提交事務。因此當咱們執行多個語句的時候,就須要手動的用 begin、commit 來開啓和提交事務。
start transaction with consistent snapshot;上面提到的 begin/start transaction 命令並非一個事務的起點,在執行到它們以後的第一個操做 InnoDB 表的語句,事務才真正啓動。若是你想要立刻啓動一個事務,可使用 start transaction with consistent snapshot 命令。 第一種啓動方式,一致性視圖是在執行第一個快照讀語句時建立的; 第二種啓動方式,一致性視圖是在執行 start transaction with consistent snapshot 時建立的。
理解了隔離級別,那事務的隔離是怎麼實現的呢?要想理解事務隔離,先得了解 MVCC 多版本的併發控制這個概念。而 MVCC 又依賴於 undo log 和 read view 實現。
百度上的解釋是這樣的:
MVCC,全稱 Multi-Version Concurrency Control,即多版本併發控制。MVCC 是一種併發控制的方法,通常在數據庫管理系統中,實現對數據庫的併發訪問,在編程語言中實現事務內存。
MVCC 使得數據庫讀不會對數據加鎖,普通的 SELECT 請求不會加鎖,提升了數據庫的併發處理能力;數據庫寫纔會加鎖。 藉助 MVCC,數據庫能夠實現 READ COMMITTED,REPEATABLE READ 等隔離級別,用戶能夠查看當前數據的前一個或者前幾個歷史版本,保證了 ACID 中的 I 特性(隔離性)。
MVCC 只在 REPEATABLE READ 和 READ COMMITIED 兩個隔離級別下工做。其餘兩個隔離級別都和 MVCC 不兼容 ,由於 READ UNCOMMITIED 老是讀取最新的數據行,而不是符合當前事務版本的數據行。而 SERIALIZABLE 則會對全部讀取的行都加鎖。
InnDB 中每一個事務都有一個惟一的事務 ID,記爲 transaction_id。它在事務開始時向 InnDB 申請,按照時間前後嚴格遞增。
而每行數據其實都有多個版本,這就依賴 undo log 來實現了。每次事務更新數據就會生成一個新的數據版本,並把 transaction_id 記爲 row trx_id。同時舊的數據版本會保留在 undo log 中,並且新的版本會記錄舊版本的回滾指針,經過它直接拿到上一個版本。
因此,InnDB 中的 MVCC 實際上是經過在每行記錄後面保存兩個隱藏的列來實現的。一列是事務 ID:trx_id;另外一列是回滾指針:roll_pt。
回滾日誌保存了事務發生以前的數據的一個版本,能夠用於回滾,同時能夠提供多版本併發控制下的讀(MVCC),也即非鎖定讀。
根據操做的不一樣,undo log 分爲兩種: insert undo log 和 update undo log。
insert 操做產生的 undo log,由於 insert 操做記錄沒有歷史版本只對當前事務自己可見,對於其餘事務此記錄不可見,因此 insert undo log 能夠在事務提交後直接刪除而不須要進行 purge 操做。
purge 的主要任務是將數據庫中已經 mark del 的數據刪除,另外也會批量回收 undo pages
因此,插入數據時。它的初始狀態是這樣的:
UPDATE 和 DELETE 操做產生的 Undo log 都屬於同一類型:update_undo。(update 能夠視爲 insert 新數據到原位置,delete 舊數據,undo log 暫時保留舊數據)。
事務提交時放到 history list 上,沒有事務要用到這些回滾日誌,即系統中沒有比這個回滾日誌更早的版本時,purge 線程將進行最後的刪除操做。
一個事務修改當前數據:
另外一個事務修改數據:
這樣的同一條記錄在數據庫中存在多個版本,就是上面提到的多版本併發控制 MVCC。
另外,藉助 undo log 經過回滾能夠回到上一個版本狀態。好比要回到 V1 只須要順序執行兩次回滾便可。
read view 是 InnDB 在實現 MVCC 時用到的一致性讀視圖,用於支持 RC(讀提交)以及 RR(可重複讀)隔離級別的實現。
read view 不是真實存在的,只是一個概念,undo log 纔是它的體現。它主要是經過版本和 undolog 計算出來的。做用是決定事務能看到哪些數據。
每一個事務或者語句有本身的一致性視圖。普通查詢語句是一致性讀,一致性讀會根據 row trx_id 和一致性視圖肯定數據版本的可見性。
read view 中主要包含當前系統中還有哪些活躍的讀寫事務,在實現上 InnDB 爲每一個事務構造了一個數組,用來保存這個事務啓動瞬間,當前正活躍(還未提交)的事務。
前面說了事務 ID 隨時間嚴格遞增的,把系統中已提交的事務 ID 的最大值記爲數組的低水位,已建立過的事務 ID + 1記爲高水位。
這個視圖數組和高水位就組成了當前事務的一致性視圖(read view)
這個數組畫個圖,長這樣:
規則以下:
第三點我在看教程的時候也有點疑惑,好在有熱心網友解答:
落在綠色區域意味着是事務 ID 在低水位和高水位這個範圍裏面,而真正是否可見,看綠色區域是否有這個值。若是綠色區域沒有這個事務 ID,則可見,若是有,則不可見。在這個範圍裏面並不意味着這個範圍就有這個值,好比 [1,2,3,5],4 在這個數組 1-5 的範圍裏,卻沒在這個數組裏面。
這樣說可能有點難以理解,我假設一個場景:三個事務對同一條數據進行查詢更新等操做,爲此畫了張圖以方便理解:
原始數據仍是下圖這樣的,對 id = 2 的張三進行信息的更新:
針對上圖,我想提個問題。**分別在 RC(讀提交)以及 RR(可重複讀)隔離級別下,T4 和 T5 時間點的查詢 age 值分別是多少呢?T4 更新的值又是多少呢?**思考片刻,相信你們都有本身的答案。答案在文末,但願你們能帶着本身的疑問繼續讀下去。
RR 級別下,查詢只認可在事務啓動前就已經提交完成的數據,一旦啓動事務就會建視圖。因此使用 start transaction with consistent snapshot 命令,立刻就會建視圖。
如今假設:
在這種隔離級別下,他們建立視圖的時刻以下:
根據上圖得,事務 A 的視圖數組是[2,3];事務 B 的視圖數組是 [2,3,4];事務 C 的視圖數組是[2,3,4,5]。分析一波:
這樣執行下來,雖然期間這一行數據被修改過,可是事務 A 不論在何時查詢,看到這行數據的結果都是一致的,因此咱們稱之爲一致性讀。
其實視圖是否可見主要看建立視圖和提交的時機,總結下規律:
事務 B 的 update 語句,若是按照上圖的一致性讀,好像結果不大對?
以下圖周明,B 的視圖數組是先生成的,以後事務 C 才提交。那就應該看不見 C 修改的 age = 23 呀?最後 B 怎麼得出 24 了?
沒錯,若是 B 在更新以前執行查詢語句,那返回的結果確定是 age = 22。問題是更新就不能在歷史版本更新了呀,不然 C 的更新不就丟失了?
因此,更新有個規則:更新數據都是先讀後寫(讀是更新語句執行,不是咱們手動執行),讀的就是當前版本的值,叫當前讀;而咱們普通的查詢語句就叫快照讀。
所以,在更新時,當前讀讀到的是 age = 23,更新以後就成 24 啦。
除了更新語句,查詢語句若是加鎖也是當前讀。若是把事務 A 的查詢語句 select age from t where id = 2 改一下,加上鎖(lock in mode 或者 for update),也均可以獲得當前版本 4 返回的 age = 24
下面就是加了鎖的 select 語句:
select age from t where id = 2 lock in mode;
select age from t where id = 2 for update;
複製代碼
假設事務 C 不立刻提交,可是 age = 23 版本已生成。事務 B 的更新將會怎麼走呢?
事務 C 還沒提交,寫鎖還沒釋放,可是事務 B 的更新必需要當前讀且必須加鎖。因此事務 B 就阻塞了,必須等到事務 C 提交,釋放鎖才能繼續當前的讀。
在讀提交隔離級別下,查詢只認可在語句啓動前就已經提交完成的數據;每個語句執行以前都會從新算出一個新的視圖。
注意:在上圖的表格中用於啓動事務的是 start transaction with consistent snapshot 命令,它會建立一個持續整個事務的視圖。因此,在 RC 級別下,這命令其實不起做用。等效於普通的 start transaction(在執行 sql 語句以前纔算是啓動了事務)。因此,事務 B 的更新實際上是在事務 C 以後的,它還沒真正啓動事務,而 C 已提交。
如今假設:
在這種隔離級別下,他們建立視圖的時刻以下:
根據上圖得,事務 A 的視圖數組是[2,3,4],但它的高水位是 6或者更大(已建立事務 ID + 1);事務 B 的視圖數組是 [2,4];事務 C 的視圖數組是 [2,5]。分析一波:
本文詳細聊了事務的方方面面,好比:四大特性、隔離級別、解決的併發問題、如何設置、查看隔離級別、如何啓動事務等。除此之外,還深刻了解了 RR 和 RC 兩個級別的隔離是怎麼實現的?包括詳解 MVCC、undo log 和 read view 是怎麼配合實現 MVCC 的。最後還聊了快照讀、當前讀等等。能夠說,事務相關的知識點都在這了。看完這一篇還不懂的話,你來捶我呀!
好啦,以上就是狗哥關於數據庫事務的總結。感謝各技術社區大佬們的付出,尤爲是極客時間,真的牛逼。若是說我看得更遠,那是由於我站在大家的肩膀上。但願這篇文章對你有幫助,咱們下篇文章見~
若是看到這裏,喜歡這篇文章的話,請幫點個好看。微信搜索JavaFish,關注後回覆電子書送你 1000+ 本編程電子書 ,包括 C、C++、Java、Python、GO、Linux、Git、數據庫、設計模式、前端、人工智能、面試相關、數據結構與算法以及計算機基礎,詳情看下圖。回覆1024送你一套完整的 java 視頻教程。