mysql中的隔離性原理詳解
數(shù)據(jù)庫并發(fā)的三種場景
- 讀-讀 :不存在任何問題,也不需要并發(fā)控制
- 讀-寫 :有線程安全問題,可能會造成事務(wù)隔離性問題,可能遇到臟讀,幻讀,不可重復(fù)讀
- 寫-寫 :有線程安全問題,可能會存在更新丟失問題
在這三種場景中 讀-讀幾乎沒有任何問題 所以我們不需要并發(fā)控制
寫-寫并發(fā)只需要加鎖控制即可
所以說我們今天重點討論下讀-寫并發(fā)
MVCC
基本介紹
多版本并發(fā)控制( MVCC )是一種用來解決 讀-寫沖突 的無鎖并發(fā)控制
為事務(wù)分配單向增長的事務(wù)ID,為每個修改保存一個版本,版本與事務(wù)ID關(guān)聯(lián),讀操作只讀該事務(wù)開始前的數(shù)據(jù)庫的快照。 所以 MVCC 可以為數(shù)據(jù)庫解決以下問題
- 在并發(fā)讀寫數(shù)據(jù)庫時,可以做到在讀操作時不用阻塞寫操作,寫操作也不用阻塞讀操作,提高了數(shù)據(jù)庫并發(fā)讀寫的性能
- 同時還可以解決臟讀,幻讀,不可重復(fù)讀等事務(wù)隔離問題,但不能解決更新丟失問題
在我們理解MVCC之前 我們需要知道三個前提知識
- 3個記錄隱藏字段
- undo 日志
- Read View
我們下面就分別先介紹下這三個隱藏字段
三個前提知識介紹
三個隱藏字段
- DB_TRX_ID :6 byte,最近修改( 修改/插入 )事務(wù)ID,記錄創(chuàng)建這條記錄/最后一次修改該記錄的事務(wù)ID
- DB_ROLL_PTR : 7 byte,回滾指針,指向這條記錄的上一個版本(簡單理解成,指向歷史版本就行,這些數(shù)據(jù)一般在 undo log 中)
- DB_ROW_ID : 6 byte,隱含的自增ID(隱藏主鍵),如果數(shù)據(jù)表沒有主鍵, InnoDB 會自動以DB_ROW_ID 產(chǎn)生一個聚簇索引
- 實際還有一個刪除flag隱藏字段, 既記錄被更新或刪除并不代表真的刪除,而是刪除flag變了
假設(shè)我們現(xiàn)在創(chuàng)建并且插入了一條數(shù)據(jù) 代碼和顯示如下
mysql> create table if not exists student( name varchar(11) not null, age int not null ); mysql> insert into student (name, age) values ('張三', 28); Query OK, 1 row affected (0.05 sec) mysql> select * from student; +--------+-----+ | name | age | +--------+-----+ | 張三 | 28 | +--------+-----+ 1 row in set (0.00 sec)
實際上在Linux隱藏字段的效果就是
對于上圖做出一定解釋
- 假設(shè)插入的事務(wù)ID是9 那么TRX_ID字段實際上就是9
- 因為這是我們插入的第一個數(shù)據(jù) 所以說隱式主鍵就是1
- 因為這是第一個數(shù)據(jù) 沒有更前面的數(shù)據(jù)了 所以說回滾指針指向的就是空
- 其實還有其他的隱藏字段 比如說flag等 上面沒有標(biāo)識出
undo log日志
mySQL 將來是以服務(wù)進程的方式,在內(nèi)存中運行。我們之前所講的所有機制:索引,事務(wù),隔離性,日志等,都是在內(nèi)存中完成的,即在 MySQL 內(nèi)部的相關(guān)緩沖區(qū)中,保存相關(guān)數(shù)據(jù),完成各種判斷操作。然后在合適的時候,將相關(guān)數(shù)據(jù)刷新到磁盤當(dāng)中的。
所以,我們這里理解undo log,簡單理解成,就是 MySQL 中的一段內(nèi)存緩沖區(qū),用來保存日志數(shù)據(jù)的就行。
read view 快照
關(guān)于快照讀的知識下面模擬MVCC場景的時候會講
Read View就是事務(wù)進行 快照讀 操作的時候生產(chǎn)的 讀視圖 (Read View),在該事務(wù)執(zhí)行的快照讀的那一刻,會生成數(shù)據(jù)庫系統(tǒng)當(dāng)前的一個快照,記錄并維護系統(tǒng)當(dāng)前活躍事務(wù)的ID(當(dāng)每個事務(wù)開啟時,都會被分配一個ID, 這個ID是遞增的,所以最新的事務(wù),ID值越大)
Read View 在 MySQL 源碼中,就是一個類,本質(zhì)是用來進行可見性判斷的。 即當(dāng)我們某個事務(wù)執(zhí)行快照讀的時候,對該記錄創(chuàng)建一個 Read View 讀視圖,把它比作條件,用來判斷當(dāng)前事務(wù)能夠看到哪個版本的數(shù)據(jù),既可能是當(dāng)前最新的數(shù)據(jù),也有可能是該行記錄的 undo log 里面的某個版本的數(shù)據(jù)。
下面是 ReadView 結(jié)構(gòu),但為了減少同學(xué)們負(fù)擔(dān),我們簡化一下
class ReadView { // 省略... private: /** 高水位:大于等于這個ID的事務(wù)均不可見*/ trx_id_t m_low_limit_id; /** 低水位:小于這個ID的事務(wù)均可見 */ trx_id_t m_up_limit_id; /** 創(chuàng)建該 Read View 的事務(wù)ID*/ trx_id_t m_creator_trx_id; /** 創(chuàng)建視圖時的活躍事務(wù)id列表*/ ids_t m_ids; /** 配合purge,標(biāo)識該視圖不需要小于m_low_limit_no的UNDO LOG, * 如果其他視圖也不需要,則可以刪除小于m_low_limit_no的UNDO LOG*/ trx_id_t m_low_limit_no; /** 標(biāo)記視圖是否被關(guān)閉*/ bool m_closed; // 省略... };
m_ids; //一張列表,用來維護Read View生成時刻,系統(tǒng)正活躍的事務(wù)ID up_limit_id; //記錄m_ids列表中事務(wù)ID最小的ID low_limit_id; //ReadView生成時刻系統(tǒng)尚未分配的下一個事務(wù)ID,也就是目前已出現(xiàn)過的事務(wù)ID的最大值+1 creator_trx_id //創(chuàng)建該ReadView的事務(wù)ID
我們在實際讀取數(shù)據(jù)版本鏈的時候,是能讀取到每一個版本對應(yīng)的事務(wù)ID的,即:當(dāng)前記錄的DB_TRX_ID 。
那么,我們現(xiàn)在手里面有的東西就有,當(dāng)前快照讀的 ReadView 和 版本鏈中的某一個記錄的DB_TRX_ID 。
那么 現(xiàn)在我們的問題就是 當(dāng)前快照讀,應(yīng)不應(yīng)該讀到當(dāng)前版本記錄。一張圖,解決所有問題!
如果查到不應(yīng)該看到當(dāng)前版本,接下來就是遍歷下一個版本,直到符合條件,即可以看到。上面的readview 是當(dāng)你進行select的時候,會自動形成。
看到這里有的同學(xué)可能會產(chǎn)生這樣一個疑問 如何遍歷下個版本呢?
我們之前說過 undo log其實就是一個緩沖區(qū) 并且里面有著回滾指針連接著的各種數(shù)據(jù) (實際上就是單鏈表連接的各種數(shù)據(jù))
再次總結(jié)下
- 我們第一次開啟事務(wù)的時候會生成一個read view的結(jié)構(gòu)體
- 該結(jié)構(gòu)體中會記錄活躍的最小事務(wù)id和比最大事務(wù)id還要大一的事務(wù)id
- 當(dāng)我們第一次select讀的時候會形成一個快照
- 如果說當(dāng)前版本的TRX_ID小于最小的id 那么我們就可以見到
- 如果當(dāng)前版本的TRX_ID大于等于最大ID我們就不能見到
- 如果說在最小和最大區(qū)間里面 并且該TRX_ID不是活躍ID(已提交) 則我們可以看到
- 如果說在最小和最大區(qū)間里面 并且TRX_ID還是活躍ID(未提交) 則我們不能看到
轉(zhuǎn)化成現(xiàn)實中的例子
現(xiàn)在的我們能夠看到我們出生之前所有人寫的作品 但是我們不能看到還未出生的人寫的作品
如果說寫書的人跟我們同一個時代 我們就要判斷這本書有沒有發(fā)表 (是否提交) 如果提交了我們就能看見 如果沒有提交 我們就不能看見
模擬MVCC場景
MVCC場景中有增刪改查 下面我們分別進行討論
增
我們插入的時候只需要形成一條新的undo log版本鏈 將回滾指針指向前面的數(shù)據(jù) 如果需要回滾直接通過回滾指針找到需要覆蓋的數(shù)據(jù)進行覆蓋即可
刪
我們前面說過了 mysql中還有一個隱藏的falg字段 因此 如果需要刪除的話 只需要將flag標(biāo)志位設(shè)置即可
改
這是最麻煩的一個環(huán)節(jié) 我們使用一個例子來說明MVCC中的改
現(xiàn)在一個表中有如下的記錄
現(xiàn)在有一個事務(wù)ID為10的事務(wù) 要修改表中的name張三為李四
- 因為要修改 所以我們肯定要先給記錄上鎖
- 修改之前 我們要將改之前的數(shù)據(jù)拷貝一份要undo log當(dāng)中 假設(shè)地址為0x11223344
- 之后我們修改原始數(shù)據(jù)中name為李四 并且將回滾指向0x11223344這個地址
- 事務(wù)10commit提交 釋放鎖
過程圖如下
如果還有事務(wù)要修改新的數(shù)據(jù)就參考上面的步驟即可
于是乎我們就形成了一條基于鏈表記錄的歷史版本鏈 undo log里面的一個個歷史版本就稱為快照
現(xiàn)在我們明白了
- 所謂的回滾其實就是拿歷史版本鏈中的某條數(shù)據(jù)覆蓋當(dāng)前數(shù)據(jù)
查
首先我們要理解兩個概念 當(dāng)前讀和快照讀
- 當(dāng)前讀:讀取最新的記錄,就是當(dāng)前讀。增刪改,都叫做當(dāng)前讀,select也有可能當(dāng)前讀,比如select lock in share mode(共享鎖), select for update
- 快照讀:讀取歷史版本。快照讀不會被加鎖。
多個事務(wù)同時增刪改的時候是當(dāng)前讀 需要加鎖 在串行化的隔離級別下 select也是當(dāng)前讀 需要加鎖
如果select是快照讀 那么和增刪改的當(dāng)前讀不沖突 所以說并行效率高 事務(wù)的隔離級別決定了select是當(dāng)期讀還是快照讀 具體的判斷方法可以參考前面read view部分的知識
RR和RC的區(qū)別
RR級別測試
演示一 兩邊開啟事務(wù) 右邊先進行快照讀 左邊插入數(shù)據(jù)之后commit 右邊再進行快照讀和當(dāng)前讀
我們可以發(fā)現(xiàn)的是 當(dāng)右邊使用快照讀的時候不管左邊有沒有commit 讀取到的數(shù)據(jù)是一樣的
而使用當(dāng)前讀的時候 我們可以發(fā)現(xiàn)讀取的數(shù)據(jù)就是最新的數(shù)據(jù)了 光靠這個一個試驗我們看不出來什么 接下來我們看演示二
演示二: 左右兩邊同時開啟一個事務(wù) 左邊先插入數(shù)據(jù)之后提交 右邊在左邊提交之后進行快照讀
我們發(fā)現(xiàn) 這個時候右邊的快照讀 讀取了最新的數(shù)據(jù)
對比這兩次試驗加上之前的read view部分學(xué)習(xí)我們不難做出以下的推斷
在RR級別下 第一次select快照讀的時候會生成一個read view快照 之后的讀取就按照這個快照進行
而實際上在RC級別中 每一次的select快照都都會生成一個最新的read view快照
所以說RR和RC最本質(zhì)的區(qū)別就是 RR只會生成依次read view快照 而RC快照讀幾次就會生成幾次快照
四種隔離級別的不同處理方式
讀–未提交
直接當(dāng)前讀 不加鎖
串行化
當(dāng)前讀 加鎖
讀 提交
在RC級別中 每次的select讀取都是快照讀 每次都會生成一個最新的read view快照
可重復(fù)讀
在RR級別中 每次select讀取都是快照讀 并且都會遵循第一次select讀取時生成的read view快照
總結(jié)
到此這篇關(guān)于mysql隔離性的原理的文章就介紹到這了,更多相關(guān)mysql隔離性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL中把varchar類型轉(zhuǎn)為date類型方法詳解
這篇文章主要介紹了MySQL中把varchar類型轉(zhuǎn)為date類型方法詳解的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-07-07本地windows安裝兩個mysql服務(wù)器,配置主從同步
大型網(wǎng)站為了緩解大量的并發(fā)訪問,除了在網(wǎng)站實現(xiàn)分布式負(fù)載均衡,還會搭建服務(wù)器mysql集群技術(shù),來分擔(dān)主數(shù)據(jù)庫的壓力。在本地電腦能實現(xiàn)這樣的技術(shù)嗎,本地windows安裝兩個mysql服務(wù)器,配置主從同步也是可以實現(xiàn)的,快來跟著教程測試一下吧。2022-12-12mysql5.5 master-slave(Replication)配置方法
mysql5.5 master-slave(Replication)配置方法,需要的朋友可以參考下。2011-08-08MySQL子查詢與HAVING/SELECT的結(jié)合使用
這篇文章主要介紹了MySQL子查詢在HAVING/SELECT字句中使用、及相關(guān)子查詢和WITH/EXISTS字句的使用,具有一定的參考價值,感興趣的可以了解一下2023-06-06

MySQL安裝提示配置信息已損壞請聯(lián)系技術(shù)人員