SQL標準中DB隔離級別有:mysql
read uncommitted:能夠讀到其它transaction 未提交數據 read committed:能夠讀到其它transaction 已提交數據 repeatable read:一個transaction中相同的查詢,每次獲取的結果是同樣的 serialize:全部操做串行
這幾種隔離級別爲的是解決併發中的以下問題:sql
髒讀 即一個transaction 能夠讀到另外一個的未提交數據。 不可重複讀 即一個transaction 中,兩次相同的查詢會讀到不同的結果。 幻讀 一個transaction中,以前不存在的記錄,忽然存在了。
read uncommitted 和 serialize 基本沒有數據庫在用。因此咱們只關注read committed 和 repeatable read, ORACLE 默認是 rc 級別,mysql默認是rr級別。咱們本文的實驗都是mysql rr級別下作的。
這兩種隔離級別對併發衝突的解決程度以下:數據庫
隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
RC | 不存在 | 存在 | 存在 |
RR | 不存在 | 不存在 | 存在 |
要注意的是,sql標準中定義的RR 是存在幻讀的,但實際上mysql 的RR級別不存在幻讀 併發
RC級別能夠看到其它transaction的提交,因此它是不可重複讀的,同時也會存在幻讀
RR級別不可看到其它transaction的更改,因此它是可重複讀的。但SQL標準定義的RR存在幻讀。(雖然mysql的實現中作到了RR無幻讀)。爲何SQL 標準定義的RR 會有幻讀呢? 首先要進一步清晰不可重複讀和幻讀的區別mvc
幻讀和不可重複讀不太好區分。不可重複讀針對update/delete等操做和已有的數據, 而幻讀針對的是insert類型操做。一次transaction中,以前讀取過的數據被其它transaction 更改提交了,而且在本transaction中可以看到。這是不可重複讀。一次transaction中,以前沒有的數據,再次操做時卻有了,是幻讀。雖然幻讀也是一種不可重複讀,但仍是要把它們區分開討論。這是由於,這兩種問題的解決方案很不同。ui
若是使用鎖機制來實現這兩種隔離級別,在可重複讀中,該sql第一次讀取到數據後,就將這些數據加鎖,其它事務沒法修改這些數據,就能夠實現可重複讀了。但這種方法卻沒法鎖住insert的數據,因此當事務A先前讀取了數據,或者修改了所有數據,事務B仍是能夠insert數據提交,這時事務A就會發現莫名其妙多了一條以前沒有的數據,這就是幻讀,不能經過行鎖來避免。code
因此幻讀和不可重複讀最大的區別是如何用鎖來解決他們產生的問題。orm
用鎖解決不可重複讀的問題很是簡單。只須要對transaction中讀取到的數據加行鎖。保證讀取過程當中該行不被修改便可。但這樣會形成部分數據的操做變成串行。主流的數據庫通常不經過鎖的方式來實現可重複讀,它們採起的方式叫 MVCC (multi-version concurrent control),咱們以mysql 的MVCC 來講明。索引
mysql 在每一行後面加了幾個字段,來標識每一行的狀態, 如 row_id 記錄行的聚簇索引 key , 建立/更新事務ID, 刪除事務ID,回滾ID , 如:事務
ROW_ID, F1, F2, ... ROW_ID, 建立事務ID, 刪除事務ID,回滾ID
INSERT 操做會把建立事務ID 置爲本身的transaction ID , 刪除事務ID 和回滾ID 爲空
UPDATE 操做會:
1. 建立該行的回滾記錄 2. 更新建立事務ID 爲本身的ID 3. 更新回滾ID 指向回滾記錄
DELETE 操做會
1. 建立該行回滾記錄 2. 更新刪除事務ID爲本身的transaction ID 3. 更新回滾ID
SELECT 操做會
查找刪除事務ID 爲空(沒刪除)或大於本身事務ID 的記錄(本身transaction 開始以後刪除的) and 建立事務ID 小於等於本身的transaction id (確保不是本身transaction後建立的)
經過這種方式,能夠發現mysql 其實實現了可重複讀。而且沒必要加鎖,由於各事務能夠維護和操做不一樣版本的數據。
也行你認爲該方式不只解決了可重複讀,還解決了幻讀。 確實,假設有transaction A和B ,而且A早於B 開始,那麼B中insert的記錄A 是讀不到的,從這個角度說是解決了幻讀。但實際上並不徹底對,mysql中的讀其實有兩種,當前讀和快照讀
RR模式MYSQL 中的SELECT 操做只讀取某一時間點的數據,即transaction開始時刻的數據,儘管後續有transaction 對數據作出了更改,當前transaction 也看不到。這有點相似於 MYSQL 在transaction開始的時刻打了一個快照。因此這種讀叫快照讀。它可重複但不是實時的。
MYSQL 中還有另一種讀叫當前讀(CURRENT READ). 這種讀只讀取表中當前最新的已提交的數據,能夠理解爲是一種READ COMMITTED。當前讀通常發生在
UPDATE/DELETE/INSERT 以及 SELECT ... FOR UPDATE 和 LOCK ... IN SHARE MODE中。能夠經過實驗驗證一下
transaction A
MariaDB [test]> start transaction; Query OK, 0 rows affected (0.00 sec) MariaDB [test]> select * from t1; +----+--------+ | id | number | +----+--------+ | 1 | 11 | | 2 | 22 | +----+--------+ 2 rows in set (0.00 sec)
transaction B 插入一條數據並commit
MariaDB [test]> select * from t1; +----+--------+ | id | number | +----+--------+ | 1 | 11 | | 2 | 22 | | 3 | 33 | +----+--------+
transaction A 先是 select 證實select 是可重複讀,快照讀
MariaDB [test]> select * from t1; +----+--------+ | id | number | +----+--------+ | 1 | 11 | | 2 | 22 | +----+--------+ 2 rows in set (0.01 sec)
而後 transaction A 再delete
MariaDB [test]> delete from t1 where id=3; Query OK, 1 row affected (0.00 sec)
能夠發現,有趣的是雖然SELECT 不到但delete操做顯示成功的刪除了該數據。這說明DELETE 能夠看到其它TRANSACTION 提交的數據,是RC。
transaction A 提交後再查詢,也能夠發現 數據確實被刪除了
這說明DML操做確實是RC, 爲何這些操做是當前讀咱們後面再看,但至少如今能夠知道,MVCC 的方式雖然能解決快照讀的不可重複與幻讀問題,但不能解決當前讀的。由於當前讀是Read committed。那麼 Mysql 如何解決當前讀的幻讀問題呢? 經過間隙鎖
用例子說明一下,假設咱們有數據表以下:
其中number上有索引
start transaction;
select * from t4; +--------+ | number | +--------+ | 5 | | 10 | | 15 | +--------+ select * from t4 where number=10 for update; +--------+ | number | +--------+ | 10 | +--------+
上述語句用當前讀,讀取number=10 , 這種狀況下要避免幻讀,即接下來:
- insert 操做不會插入到 number=10 t1
- update操做不能更新 number=10 這行
- delete操做不能刪除number = 10這行
mysql 所用的方式很簡單,經過row鎖鎖住 number=10的行,阻止update/delete。 經過間隙鎖鎖住10 可能出現的位置
由於 number 有索引,經過索引咱們知道 number = 10 可能出現的位置有兩處 5-10 和 10-15 , 因此mysql 會把這兩處鎖住, 從其它transaction 去 insert 數據到 5-10 和10 - 15 的位置會被卡住。這就是間隙鎖。咱們驗證一下
MariaDB [test]> start transaction; Query OK, 0 rows affected (0.00 sec) MariaDB [test]> insert into t4 values(18); Query OK, 1 row affected (0.00 sec) MariaDB [test]> insert into t4 values(6); ^CCtrl-C -- query killed. Continuing normally. ERROR 1317 (70100): Query execution was interrupted MariaDB [test]> insert into t4 values(11); ^CCtrl-C -- query killed. Continuing normally. ERROR 1317 (70100): Query execution was interrupted
除了 18 能夠成功,其它兩條被卡住
但要注意的是:mysql 經過間隙鎖來鎖住目標記錄可能出現的位置,若是檢索條件有索引,能夠經過索引鎖住目標位置,若是索引是unique 則不用鎖間隙,由於不會出現間隙,若是沒有索引會鎖住全表
間隙鎖加行鎖的方式來防止當前讀的幻讀,在mysql中叫next key鎖
SELECT FOR UPDATE/SHARE 是當前讀比較好理解。這兩種讀的目的就是鎖住記錄,不讓他人更改,因此鎖住快照沒有意義
那麼DML 爲何是當前讀呢?考慮如下場景
start transaction; select * from t4; +--------+ | number | +--------+ | 5 | | 10 | | 15 | +--------+ select * from t4 where number=10 for update; +--------+ | number | +--------+ | 10 | +--------+
這時有另外transaction 進行DML 操做,若是insert / update / delete 不是當前讀, 那麼 SELECT FOR UPDATE的鎖仍然毫無心義..