Mysql加鎖過程詳解(2)-關於mysql 幻讀理解

這裏給出 mysql 幻讀的比較形象的場景:mysql

users: id 主鍵sql

1、T1:select * from users where id = 1;

2、T2:insert into `users`(`id`, `name`) values (1, 'big cat');

3、T1:insert into `users`(`id`, `name`) values (1, 'big cat');

T1 :主事務,檢測表中是否有 id 爲 1 的記錄,沒有則插入,這是咱們指望的正常業務邏輯。spa

T2 :干擾事務,目的在於擾亂 T1 的正常的事務執行。3d

 

在 RR 隔離級別下,一、2 是會正常執行的,3 則會報錯主鍵衝突,對於 T1 的業務來講是執行失敗的,這裏 T1 就是發生了幻讀,由於T1讀取的數據狀態並不能支持他的下一步的業務,見鬼了同樣。code

在 Serializable 隔離級別下,1 執行時是會隱式的添加 gap 共享鎖的,從而 2 會被阻塞,3 會正常執行,對於 T1 來講業務是正確的,成功的扼殺了擾亂業務的T2,對於T1來講他讀取的狀態是能夠拿來支持業務的。blog

 

因此 mysql 的幻讀並不是什麼讀取兩次返回結果集不一樣,而是事務在插入事先檢測不存在的記錄時,驚奇的發現這些數據已經存在了,以前的檢測讀獲取到的數據如同鬼影通常。事務

這裏要靈活的理解讀取的意思,第一次select是讀取,第二次的 insert 其實也屬於隱式的讀取,只不過是在 mysql 的機制中讀取的,插入數據也是要先讀取一下有沒有主鍵衝突才能決定是否執行插入。it

不可重複讀側重表達 讀-讀,幻讀則是說 讀-寫,用寫來證明讀的是鬼影。io

下面給出幻讀的例子:table

設置隔離級別爲 Read Repeatable,開啓兩個事務 t1和t2

原始表以下:

在t1中,首先查看id=C的數據,爲空:

mysql> select * from amount where id = 'C';
Empty set (0.00 sec)

而後在t2中插入id='C'的數據,此時咱們發現插入成功了

mysql> insert into  amount values('C',1000);
Query OK, 1 row affected (0.01 sec)

 而後,咱們在t1中插入id='C'的數據,能夠看到一直處於等待狀態,直到t2的事務被提交或者超時。

當t2的事務被提交後,t1中會報主鍵重複的錯誤:

mysql> insert into  amount values('C',1000);
ERROR 1062 (23000): Duplicate entry 'C' for key 'PRIMARY'

 

 這就是幻讀現象,咱們在t1中查詢id='C'的數據時顯示數據不存在,可是因爲t2中插入了id=C的數據,致使了t1再想插入時出現了主鍵重複的錯誤,t2成功擾亂了t1的事務。

 

咱們看看再Serializable的級別下是如何的

在t1中,首先查看id=C的數據,爲空:

mysql> select * from amount where id = 'C';
Empty set (0.00 sec)

 

t2插入數據,發現跟上面不一樣的是,t2阻塞了。

這時在t1中插入數據,成功插入:

mysql>  insert into  amount values('C',1000);
Query OK, 1 row affected (0.02 sec)

 

當t1提交之後,此次輪到t2出現了主鍵重複的錯誤。

從結果能夠知道,t1的事務並無受到t2事務的擾亂,即在Serializable的隔離級別下沒有出現幻讀。

 

在上面兩個實驗中咱們發現,repeatable read是沒法避免幻讀的,可是,在某種狀況下,它卻能解決幻讀問題。

下面看例子1,查詢的記錄不存在的狀況

t1 t2
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
start transaction; start transaction;
mysql> select * from amount where id = 'E' for update;
Empty set (0.00 sec)
 
 
mysql> select * from amount;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   600 |
| C  |  1000 |
| D  |  1000 |
+----+-------+
4 rows in set (0.00 sec)
   

鎖住了。

mysql> insert into  amount values('E',1000);
Query OK, 1 row affected (0.01 sec)
 
 commit  
 
mysql> insert into  amount values('E',1000);
ERROR 1062 (23000): Duplicate entry 'E' for key 'PRIMARY'
   commit;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

 

 

 

使用select .. for update鎖住,而後再insert,能夠避免幻讀。

其實,即便在t2中,插入id不爲"E"的記錄,也是會阻塞的(鎖住),依然要等待t1提交後才能輪到t2工做。 

 

例2 ,當查詢的結果已經存在

t1 t2
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
start transaction; start transaction;
mysql> select * from amount;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   600 |
+----+-------+
mysql> select * from amount;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   600 |
+----+-------+
mysql> select * from amount where id = 'A' for update;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
+----+-------+
 
 
mysql> select * from amount;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   600 |
+----+-------+
 
mysql> insert into  amount values('C',1000);
Query OK, 1 row affected (0.01 sec)
commit; commit;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

以上例子說明,for update時候,id爲主鍵,RR策略時候,鎖住了的條件符合的行,可是若是條件找不到任何列,鎖住的是整個表,所以當t1查詢到的記錄爲空時,在t2想插入該主鍵記錄時是阻塞的;當t1查詢到的記錄非空時,除了該主鍵記錄以外,能夠在其餘事務插入任何不存在的主鍵記錄而不阻塞。

 

例3,範圍查詢的狀況

t1 t2
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
start transaction; start transaction;
 
mysql> select * from amount where id < 'M' for update;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   600 |
| C  |  1000 |
| D  |  1000 |
+----+-------+
 
 
mysql> insert into  amount values('X',1000);
Query OK, 1 row affected (0.01 sec)
 
mysql> select * from amount ;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   600 |
| C  |  1000 |
| D  |  1000 |
| M  |  1000 |
| N  |  1000 |
+----+-------+

 

 
   

鎖住了

 
mysql> select * from amount ;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   600 |
| C  |  1000 |
| D  |  1000 |
| M  |  1000 |
| N  |  1000 |
+----+-------+

 

 
   commit;
 
mysql> select * from amount ;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   600 |
| C  |  1000 |
| D  |  1000 |
| M  |  1000 |
| N  |  1000 |
+----+-------+

可重複讀

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

能夠看到,用 id<'M' 加的鎖,只鎖住了 id< 'M' 的範圍,能夠成功添加id爲X的記錄,添加id爲'G'的記錄時就會等待鎖的釋放。

 

例4

t1 t2
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
start transaction; start transaction;
mysql> select * from amount;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   600 |
| C  |  1000 |
+----+-------+

 

 
 
mysql> insert into  amount values('D',1000);
Query OK, 1 row affected (0.01 sec)

mysql> commit;

 

mysql> select * from amount;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   600 |
| C  |  1000 |
+----+-------+

 可重複讀

 
mysql> select * from amount lock  in share mode;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   600 |
| C  |  1000 |
| D  |  1000 |
+----+-------+

 加鎖,讀取的是最新值,當前讀

 
mysql> select * from amount for update;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   600 |
| C  |  1000 |
| D  |  1000 |
+----+-------+

  加鎖,讀取的是最新值,當前讀

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

若是使用普通的讀,會獲得一致性的結果,若是使用了加鎖的讀,就會讀到「最新的」「提交」讀的結果。

自己,可重複讀和提交讀是矛盾的。在同一個事務裏,若是保證了可重複讀,就會看不到其餘事務的提交,違背了提交讀;若是保證了提交讀,就會致使先後兩次讀到的結果不一致,違背了可重複讀。

能夠這麼講,InnoDB提供了這樣的機制,在默認的可重複讀的隔離級別裏,能夠使用加鎖讀去查詢最新的數據。

結論:

MySQL InnoDB的可重複讀並不保證避免幻讀,須要應用使用加鎖讀來保證。而這個加鎖度使用到的機制就是next-key locks。

mysql 的重複讀解決了幻讀的現象,可是須要 加上 select for update/lock in share mode 變成當前讀避免幻讀,普通讀select存在幻讀

相關文章
相關標籤/搜索