建表SQL
CREATE TABLE `user` ( `id` int(11) NOT NULL COMMENT 'id', `balance` int(255) DEFAULT NULL COMMENT '余額', `version` int(255) DEFAULT NULL COMMENT '版本號', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_croatian_ci;
當前的MYSQL的事務隔離級別為,REPEATABLE-READ
select @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+
嘗試觸發幻讀
SESSION1 | SESSION2 |
---|---|
start transaction; |
start transaction; |
select * from user; |
|
insert into user values(1, 0, 0); |
|
commit; |
|
select * from user; |
|
commit; |
|
select * from user; |
- session1開啟事務,並且執行一次檢索,記錄為空
- session2開啟事務,並且插入一條新記錄,提交
- session1執行檢索,未讀取到session2插入的新記錄,記錄還是為空
- session1提交,再次檢索可以成功讀取到session2插入的新記錄
說明 MYSQL 的 REPEATABLE-READ , 其實是可以解決幻讀的問題
但是,不一定能徹底的解決幻讀的問題
SESSION1 | SESSION2 |
---|---|
start transaction; |
start transaction; |
select * from user where version = 1; |
|
insert into user values(2, 0, 1); |
|
commit; |
|
select * from user where version = 1; |
|
update user set balance = 1 where version = 1; |
|
select * from user where version = 1; |
|
commit; |
- session1開啟事務,並且根據條件
version = 1
執行一次檢索,記錄為空 - session2開啟事務,並且插入一條符合session1檢索條件的新記錄,提交
- session1再次嘗試根據條件
version = 1
進行檢索,未讀取到session2插入的新記錄,記錄還是為空 - session1嘗試修改session2新插入的記錄,修改成功
- session1執行修改成功后,再次根據條件
version = 1
進行檢索,發現讀取到了session2新插入的記錄
第一種情況可以解決幻讀,而第二種情況就不能解決幻讀,為什么呢?
在MVCC並發控制中,讀操作可以分成兩類:快照讀 (snapshot read)與當前讀 (current read)。 快照讀,讀取的是記錄的可見版本 (有可能是歷史版本),不用加鎖。 當前讀,讀取的是記錄的最新版本,並且,當前讀返回的記錄,都會加上鎖,保證其他事務不會再並發修改這條記錄。 在一個支持MVCC並發控制的系統中,哪些讀操作是快照讀?哪些操作又是當前讀呢?以MySQL InnoDB為例: 快照讀:簡單的select操作,屬於快照讀,不加鎖。(當然,也有例外,下面會分析) select * from table where ?; 當前讀:特殊的讀操作,插入/更新/刪除操作,屬於當前讀,需要加鎖。 select * from table where ? lock in share mode; select * from table where ? for update; insert into table values (…); update table set ? where ?; delete from table where ?; 所有以上的語句,都屬於當前讀,讀取記錄的最新版本。並且,讀取之后,還需要保證其他並發事務不能修改當前記錄,對讀取記錄加鎖。其中,除了第一條語句,對讀取記錄加S鎖 (共享鎖)外,
其他的操作,都加的是X鎖 (排它鎖)。
MySQL/InnoDB定義的4種隔離級別:
-
Read Uncommited
可以讀取未提交記錄。此隔離級別,不會使用,忽略。
-
Read Committed (RC)
快照讀忽略,本文不考慮。
針對當前讀,RC隔離級別保證對讀取到的記錄加鎖 (記錄鎖),存在幻讀現象。
-
Repeatable Read (RR)
快照讀忽略,本文不考慮。
針對當前讀,RR隔離級別保證對讀取到的記錄加鎖 (記錄鎖),同時保證對讀取的范圍加鎖,新的滿足查詢條件的記錄不能夠插入 (間隙鎖),不存在幻讀現象。
-
Serializable
從MVCC並發控制退化為基於鎖的並發控制。不區別快照讀與當前讀,所有的讀操作均為當前讀,讀加讀鎖 (S鎖),寫加寫鎖 (X鎖)。
Serializable隔離級別下,讀寫沖突,因此並發度急劇下降,在MySQL/InnoDB下不建議使用。