MySQL 實戰 | 08 懵逼,可重複讀好像失效了?
我們之前學習了隔離級別和鎖,在隔離級別裡有一個可重複讀,鎖裡有個行鎖。
可重複讀:事務期間,看不懂別的事務的更新;
行鎖:有事務 1 在更新某行資料時,若有其他事務 2 進來,會被鎖住
矛盾來了:事務 2 等待結束,獲取到行鎖時,看到的是哪個資料呢?
按可重複讀隔離級別來說,看到的應該是事務啟動時的最新資料,即事務 1 修改之前的資料;
但是這樣不就造成了事務 1 的修改丟失了嗎?
話不多說,我們先手動實驗一把。
實驗
我們建個表先:
mysql> CREATE TABLE `t` (
`id` int(11) NOT NULL,
`k` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);
然後做如下操作:
說明:
1、begin/start transaction 執行後其實並不會立即啟動事務,執行第一個操作 InnoDB 表的語句時才會真正啟動;
2、顯示啟動事務:start transaction with consistent snapshot
很容易看到實驗結果:
A 讀到的值是 1
B 讀到的值是 3
看上去 B 事務違反了可重複讀隔離級別的概念,為啥呢?
原因探索
之前在學習事務隔離級別時,我們接觸到了一個「檢視」的概念,這個檢視和我們平常接觸的 view 檢視並不一樣。
MySQL 中的兩個「檢視」的區別
一、常說的檢視:view
① 是用查詢語句定義的虛擬表;
② 在呼叫時執行查詢,並生成結果;
③ 建立方法:create view...
;
二、MVCC 中的一致性檢視(consistent read view)
① 用於支援隔離級別的實現:RC(Read Committed,讀提交)和 RR(Repeatable Read,可重複讀)
② 沒有物理結構,作用是事務執行期間用來定義我能看到什麼資料;
③ 其中,可重複讀:每個事務啟動是都會重建讀檢視,整個事務存在期間都用這個檢視;
快照是什麼
很多文章都會說可重複讀隔離級別下,事務啟動時會生成整個庫的快照。
那麼這個快照是什麼?
我們要先了解下資料的版本問題:
其實每個事務都有一個標識 id:trx_id,是在事務啟動時向儲存引擎的事務系統申請的,並且是按照申請順序嚴格遞增的。
每行資料都有版本的概念,這裡的版本其實就是修改歷史,而這個修改歷史是跟事務掛鉤的,比如:
如圖所示,一行資料被多次事務修改時,這行資料會儲存多個版本,如 V1、V2、V3 等。
每個版本會記錄了關聯的事務 id,這裡的版本並不是物理上存在的,需要根據版本號+undo log 來獲取。
其實,快照就是版本號的集合。
事務啟動時發生了什麼
可重複讀隔離級別下,事務的屬性是這樣的:可以看到所有已提交的更新,所有未提交的更新都不能看到。對於同一行資料,以最新一次的事務提交為資料基準。
另外,事務啟動後,很可能存在其他活躍事務(啟動且未提交),我們把這些活躍事務的 id 組成一個陣列,並且記陣列中 trx_id 最小的記為低水位,trx_id 最大的記為高水位。
因此,所有的事務 id 可以分成下圖這種:
trx_id 在綠色部分,已提交,可見
在紅色部分,未提交,不可見
黃色部分
trx_id 在陣列中,未提交,不可見
trx_id 不在陣列中,已提交,可見
舉個例子
1、假設有一組事務 id:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
2、其中已提交(可見):[1, 2, 3, 6, 7],未開始的(不可見):[10, 11, 12],當前 id:[8]
3、那麼活躍 id 陣列(不可見):[4, 5, 9],高水位:9,低水位:4;
4、高低水位之間既有已提交但不在陣列中的(可見):[6, 7],又有活躍的(不可見):[4, 5, 9]
實驗覆盤
按照上面的陣列和水位的概念,我們來捋一下文章開頭的實驗。
首先,假設事務 A 的 id 為 10,啟動後,A 的活躍陣列就是 [10];
接下來是事務 B,id 為 11,啟動後,B 的活躍陣列是 [10, 11];
最後是 C,id 為 12,啟動後,活躍陣列是 [10, 11, 12];
C 處理完畢後,直接提交事務;k 的值由 1 變為 2,
此時就儲存了兩個版本的資料:(事務id-9, 1),(事務id-12, 2)
接下來 B 來處理,它會將 k 的值更新為 3,此時就有三個版本的資料:(事務id-9, 1),(事務id-12, 2),(事務id-11, 3)
最後 A 事務,由於 A 啟動時,B、C 事務都未提交,所以它們的資料更新對於 A 來說都是看不到的,因此 A 獲取到的結果是 1。
更新的邏輯
不知道你注意到沒有,上面的覆盤中,有一個重要的點,我們沒有說。
按照可重複讀的邏輯,B 執行更新時,看到的 k 的值應該是 1,執行更新的話,就直接造成了 C 操作中的資料丟失。
但是事務 B 在更新時,為什麼讀取到了事務 C 更新的資料?
這裡有一條規則,就是更新資料都是先讀後寫的,而這個讀,只能讀當前的最新值,稱為當前讀(current read),
即,更新資料時總是讀取已經提交完成的最新版本。
另外,除了 update 語句外,select 語句如果加鎖,也是當前讀。
讀鎖(S 鎖,共享鎖):mysql> select k from t where id=1 lock in share mode;
寫鎖(X 鎖,排他鎖):mysql> select k from t where id=1 for update;
總結
可重複讀的能力是怎麼實現的?
把握以下幾點:
1、可重複讀的核心就是一致性讀(consistent read);
2、而事務更新資料的時候,只能用當前讀。
3、如果當前的記錄的行鎖被其他事務佔用的話,就需要進入鎖等待。
另外,本文的幾個重要概念:
1、一致性檢視,保證了當前事務從啟動到提交期間,讀取到的資料是一致的(包括當前事務的修改)。
2、當前讀,保證了當前事務修改資料時,不會丟失其他事務已經提交的修改。
3、兩階段鎖協議,保證了當前事務修改資料時,不會丟失其他事務未提交的修改。
4、RR 是透過事務啟動時建立一致性識圖來實現,RC 是語句執行時建立一致性識圖來實現
課後題目
我們用下面的語句初始化一個表:
mysql> CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, c) values(1,1),(2,2),(3,3),(4,4);
你是否可以嘗試製造出下面的「詭異」現象?
詭異之處在於,我們的目的是將“欄位 c 和 id 值相等的行”的 c 值清零,但是發現更新語句執行成功後,c 的值並沒有被清零!
答案
我們用如下操作流程就可以:
原因分析
主要原因就算當前讀!
事務 B 是在事務 A 之後啟動的,但是事務 B 的更新提交是在 事務 A 之前。
事務 A 第一次查詢時,由於可重複讀,讀取到的自然是 1 2 3 4。
事務 A 更新時,根據當前讀規則,此時 c 的值已經是 5,不再滿足更新條件 id=c
,因此更新不會真正執行。
所以,事務 A 再次查詢時,獲取到的仍然是 1 2 3 4,事務 A 提交後,再查詢時,獲取到的自然是最新的資料了
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559358/viewspace-2286858/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- MySQL 之隔離級別:可重複讀MySql
- Mysql可重複讀(1) —— 快照何時建立MySql
- Mysql RC/RR隔離原理和區別 不可重複讀和可重複讀MySql
- MySQL的可重複讀級別能解決幻讀嗎MySql
- MySQL 事務隔離實驗-認識:髒讀、不可重複讀、幻讀MySql
- 簡單聊聊mysql的髒讀、不可重複讀MySql
- MySQL 可重複讀,差點就我背上了一個 P0 事故!MySql
- 【Mysql】資料庫事務,髒讀、幻讀、不可重複讀MySql資料庫
- 為什麼mysql選可重複讀作為預設的隔離級別MySql
- MySQL許可權管理實戰MySql
- 髒讀,幻讀,不可重複讀
- 答讀者問:BeanFactoryPostProcessor 似乎失效了?Bean
- [SuperSocket2.0]SuperSocket 2.0從入門到懵逼
- 髒讀、幻讀和不可重複讀
- 髒讀!幻讀!不可重複讀!mysql併發事務引發的問題MySql
- MySQL運維實戰(7)建立複製MySql運維
- 讀資料保護:工作負載的可恢復性08去重技術(下)負載
- mysql 刪除重複項MySql
- mysql 清除重複資料MySql
- 髒讀、幻讀和不可重複讀?為啥?
- 什麼是髒讀,不可重複讀,幻讀
- 牛逼!MySQL 8.0 中的索引可以隱藏了…MySql索引
- mysql避免插入重複資料MySql
- MySQL 處理重複資料MySql
- MySQL運維實戰(7.1) 開啟GTID複製MySql運維
- 懵了!女朋友突然問我MVCC實現原理MVC
- NetCore專案實戰篇08---Docker掛載mysql並連線.netCoreWebNetCoreDockerMySqlWeb
- 最小路徑可重複點覆蓋
- 讀構建可擴充套件分散式系統:方法與實踐08微服務套件分散式微服務
- MySQL 查詢重複的資料MySql
- 一文詳解髒讀、不可重複讀、幻讀
- 一直讓 PHP 程式設計師懵逼的同步阻塞非同步非阻塞,終於搞明白了PHP程式設計師非同步
- 好像不是最全的陣列去重方法陣列
- 2020年了,別再重複學習原型了原型
- Vue 框架-08-基礎實戰 demoVue框架
- 用幾張圖實戰講解MySQL主從複製MySql
- 08、MySQL—字串型MySql字串
- mysql 為什麼很多網際網路公司選擇了讀可提交MySql