Innodb隔离级别的实现原理
Mysql簡介
版本號3.23(2001)Mysql的誕生,引入MyISAM和InnoDB。
?
版本號4.0(2003)支持更多語法,如UNION和多表DELETE語法,引入查詢緩存。
?
版本號5.0(2006)出現企業級Mysql特性:視圖,觸發器,存儲過程和存儲函數。之后Sun收購Mysql后,5.1版本,引入分區和基于行的復制備份,以及可插拔的存儲引擎API。
?
版本號5.5(2010)Oracle收購Sun以后,將InnoDB設為默認存儲引擎,增加了其擴展性和性能提升。
?
版本號5.6(2013)為InnoDB加入全文檢索。
?
Mysql 服務器架構
Mysql的整體架構如下圖所示,分為三層:
第一層為大部分應用都擁有的client端或者接口端,主要負責調用Mysql服務器的服務。
第二層為Mysql服務器層,其中包含了所有在調用存儲引擎之前所做的預備工作
第三層為存儲引擎層,該層和服務器層是完全分離,并且由上面可以知道已經實現了存儲引擎可插拔的模式
?
Mysql模塊架構圖
?
?
?
?
?
1、Client& Server 交互協議模塊
任何C/S 結構的軟件系統,都肯定會有自己獨有的信息交互協議,MySQL 也不例外。MySQL的Client & Server 交互協議模塊部分,實現了客戶端與MySQL 交互過程中的所有協議。當然這些協議都是建立在現有的OS 和網絡協議之上的,如TCP/IP 以及Unix Socket。
?
2、初始化模塊
顧名思議,初始化模塊就是在MySQL Server 啟動的時候,對整個系統做各種各樣的初始化操作,比如各種buffer,cache 結構的初始化和內存空間的申請,各種系統變量的初始化設定,各種存儲引擎的初始化設置,等等。
?
3、網絡交互模塊
底層網絡交互模塊抽象出底層網絡交互所使用的接口api,實現底層網絡數據的接收與發送,以方便其他各個模塊調用,以及對這一部分的維護。所有源碼都在vio 文件夾下面。
??
4、連接管理、連接線程模塊
連接管理模塊負責監聽對MySQL Server 的各種請求,接收連接請求,轉發所有連接請求到線程管理模塊。每一個連接上MySQL Server 的客戶端請求都會被分配(或創建)一個連接線程為其單獨服務。而連接線程的主要工作就是負責MySQL Server 與客戶端的通信,接受客戶端的命令請求,傳遞Server 端的結果信息等。線程管理模塊則負責管理維護這些連接線程。包括線程的創建,線程的cache 等。
?
5、用戶模塊
用戶模塊所實現的功能,主要包括用戶的登錄連接權限控制和用戶的授權管理。他就像MySQL 的大門守衛一樣,決定是否給來訪者“開門”。
?
6、Query 解析和轉發模塊
在MySQL 中我們習慣將所有Client端發送給Server 端的命令都稱為query,在MySQL Server 里面,連接線程接收到客戶端的一個Query 后,會直接將該query 傳遞給專門負責將各種Query 進行分類然后轉發給各個對應的處理模塊,這個模塊就是query 解析和轉發模塊。其主要工作就是將query 語句進行語義和語法的分析,然后按照不同的操作類型進行分類,然后做出針對性的轉發。
?
7、QueryCache 模塊
Query Cache 模塊在MySQL 中是一個非常重要的模塊,他的主要功能是將客戶端提交給MySQL 的Select 類query 請求的返回結果集cache 到內存中,與該query 的一個hash 值做一個對應。該Query 所取數據的基表發生任何數據的變化之后,MySQL 會自動使該query 的Cache 失效。在讀寫比例非常高的應用系統中,Query Cache 對性能的提高是非常顯著的。當然它對內存的消耗也是非常大的。
?
8、日志記錄模塊
日志記錄模塊主要負責整個系統級別的邏輯層的日志的記錄,包括error log,binary log,slow query log 等。
?
9、Query 優化器模塊
Query 優化器,顧名思義,就是優化客戶端請求的query,根據客戶端請求的query 語句,和數據庫中的一些統計信息,在一系列算法的基礎上進行分析,得出一個最優的策略,告訴后面的程序如何取得這個query 語句的結果。
?
10、表變更管理模塊
表變更管理模塊主要是負責完成一些DML 和DDL 的query,如:update,delte,insert,create table,alter table 等語句的處理。
?
11、表維護模塊
表的狀態檢查,錯誤修復,以及優化和分析等工作都是表維護模塊需要做的事情。
?
12、復制模塊
復制模塊又可分為Master 模塊和Slave 模塊兩部分, Master 模塊主要負責在Replication 環境中讀取Master 端的binary 日志,以及與Slave 端的I/O 線程交互等工作。
Slave 模塊比Master 模塊所要做的事情稍多一些,在系統中主要體現在兩個線程上面。一個是負責從Master請求和接受binary 日志,并寫入本地relay log 中的I/O 線程。另外一個是負責從relay log 中讀取相關日志事件,然后解析成可以在Slave 端正確執行并得到和Master端完全相同的結果的命令并再交給Slave 執行的SQL 線程。
?
13、系統狀態管理模塊
系統狀態管理模塊負責在客戶端請求系統狀態的時候,將各種狀態數據返回給用戶,像DBA 常用的各種showstatus 命令,showvariables 命令等,所得到的結果都是由這個模塊返回的。
?
14、訪問控制模塊
造訪客人進門了就可以想干嘛就干嘛么?為了安全考慮,肯定不能如此隨意。這時候就需要訪問控制模塊實時監控客人的每一個動作,給不同的客人以不同的權限。訪問控制模塊實現的功能就是根據用戶模塊中各用戶的授權信息,以及數據庫自身特有的各種約束,來控制用戶對數據的訪問。用戶模塊和訪問控制模塊兩者結合起來,組成了MySQL 整個數據庫系統的權限安全管理的功能。
?
15、表管理器
這個模塊從名字上看來很容易和上面的表變更和表維護模塊相混淆,但是其功能與變更及維護模塊卻完全不同。大家知道,每一個MySQL 的表都有一個表的定義文件,也就是*.frm文件。表管理器的工作主要就是維護這些文件,以及一個cache,該cache 中的主要內容是各個表的結構信息。此外它還維護table 級別的鎖管理。
?
16、存儲引擎接口模塊
存儲引擎接口模塊可以說是MySQL 數據庫中最有特色的一點了。目前各種數據庫產品中,基本上只有MySQL 可以實現其底層數據存儲引擎的插件式管理。這個模塊實際上只是一個抽象類,但正是因為它成功地將各種數據處理高度抽象化,才成就了今天MySQL 可插拔存儲引擎的特色。
?
17、核心API
核心API 模塊主要是為了提供一些需要非常高效的底層操作功能的優化實現,包括各種底層數據結構的實現,特殊算法的實現,字符串處理,數字處理等,小文件I/O,格式化輸出,以及最重要的內存管理部分。核心API 模塊的所有源代碼都集中在mysys和strings文件夾下面,有興趣的讀者可以研究研究。
?
?
存儲引擎
可以稱之為一種處理數據的能力,也可以稱之為是一種表的類型。
MyISAM: 擁有較高的插入,查詢速度,但不支持?事務
InnoDB?:5.5版本后Mysql的默認數據庫,事務型數據庫的首選引擎,支持ACID事務,支持行級鎖定
?
高并發下的mysql會發生什么問題?(以下文章僅僅討論InnoDB存儲引擎)
當一個數據庫存在高并發的情況下,如果只進行普通的查操作,那么我相信通過mysql本身的緩存機制和索引機制能夠達到很好的性能效果,但是如果還有其他讀寫操作呢?會發生什么事情?
一個經典的例子是email,如果一個用戶在閱讀一封郵件的時候,同時另外一個用戶刪除了這一封郵件,最后會發生什么?結果是不確定的,有可能會報錯退出,有可能會讀取到不一樣的數據。
以此為基礎,引入一個概念,?多版本并發控制(Multi-version concurrency control,MVCC),這個概念不僅僅是對于Mysql,包括Oracle、PostgreSQL等其他數據庫系統都實現了MVCC,只是各自的機制不同,它可以保證不阻塞地讀到一致的數據,實現是通過保存數據在某個時間點的快照來實現的。再引入幾個基礎概念,?鎖、事務和隔離級別。
日志
MySQL Innodb中存在多種日志,除了錯誤日志、查詢日志外,還有很多和數據持久性、一致性有關的日志。
bin.log是mysql服務層產生的日志,常用來進行數據恢復、數據庫復制,常見的mysql主從架構,就是采用slave同步master的binlog實現的, 另外通過解析binlog能夠實現mysql到其他數據源(如ElasticSearch)的數據復制。
redo.log記錄了數據操作在物理層面的修改,mysql中使用了大量緩存,緩存存在于內存中,修改操作時會直接修改內存,而不是立刻修改磁盤,當內存和磁盤的數據不一致時,稱內存中的數據為臟頁(dirty page)。為了保證數據的安全性,事務進行中時會不斷的產生redo log,在事務提交時進行一次flush操作,保存到磁盤中, redo log是按照順序寫入的,磁盤的順序讀寫的速度遠大于隨機讀寫。當數據庫或主機失效重啟時,會根據redo log進行數據的恢復,如果redo log中有事務提交,則進行事務提交修改數據。這樣實現了事務的原子性、一致性和持久性。
undo.Log除了記錄redo log外,當進行數據修改時還會記錄undo log,undo log用于數據的撤回操作,它記錄了修改的反向操作,比如,插入對應刪除,修改對應修改為原來的數據,通過undo log可以實現事務回滾,并且可以根據undo log回溯到某個特定的版本的數據,實現MVCC。
redo log 和binlog的一致性,為了防止寫完binlog但是redo log的事務還沒提交導致的不一致,innodb 使用了兩階段提交
鎖
一種提高共享資源并發性的操作就是讓鎖更有選擇性,盡量只鎖定需要修改的數據而不是所有數據,但是加鎖也是需要消耗各種資源。鎖的各種操作,包括獲得鎖,檢查鎖是否解除,釋放鎖等,都會增加系統開銷,所以想要獲得最高性能的并發鎖策略,是在鎖的開銷和數據的安全性之間尋找一個最優點。
鎖在架構上分為兩層,一種為服務器層面的鎖,一種為存儲引擎層面的鎖。從功能上分為讀鎖和寫鎖(共享鎖和排它鎖)。
?
服務器層面的鎖是在執行Alter Table的某些操作的時候,mysql服務器會忽略存儲引擎,直接會使用表鎖,執行對應的語句。
?
存儲引擎層面的鎖是為了最大程度的支持并發處理,在InnoDB,鎖分行鎖、Metadata Lock(事務級表鎖),行鎖的算法共有三種:Record Lock,Gap Lock,Next-Key Lock
?
Record Lock:單個行記錄的上鎖
Gap Lock:間歇鎖,不包含記錄本身的區間鎖
Next-Key Lock:包含記錄本身的區間鎖
只有在RR隔離級別下才會有gap lock,next-key lock,其中
當where條件為?普通索引時為gap lock或者Next-key Lock
當where條件為?主鍵索引的時候,Next-key Lock 和Gap Lock的鎖策略降級為行鎖
當where條件?不是索引的時候,innodb會給所有數據上鎖,然后返回Mysql server層,然后在Server層過濾掉不符合條件的數據,通過調用?unlock_row方法解鎖
以下為測試間歇鎖的語句:
create table t(id int,name int,key idx_id(name),primary key(id))engine =innodb;
insert into t values(1,1),(3,3),(5,5),(8,8),(11,11);
session 1:select * from t where name=8 for update;
session 2:insert into t(id,name) values(12,6);
session 2:insert into t(id,name) values(6,6);
?
如何在事務中進行行鎖操作?
SELECT??語句中加??for???update??或者??lock???in???share??mode
或者update、delete
其中還有一種檢驗死鎖的算法叫做?wait-for graph?,沒當請求沒有立即反應的時候就會執行。
?
Metadate Lock主要解決了2個問題,一個是事務隔離問題,比如在可重復隔離級別下,會話A在2次查詢期間,會話B對表結構做了修改,兩次查詢結果就會不一致,無法滿足可重復讀的要求;另外一個是數據復制的問題,比如會話A執行了多條更新語句期間,另外一個會話B做了表結構變更并且先提交,就會導致slave在重做時,先重做alter,再重做update時就會出現復制錯誤的現象。測試的時候可以在使用show processlist查看alter 表的session是否存在Waiting for table metadata lock狀態。
?
事務
我理解的事務為用戶和Mysql服務器完整的交流,從Start Transaction;開始后的所有sql語句直到commit;結束的一次數據交流,?一般mysql中會設置自動提交事務的特點,設置方法為?show variables like 'AUTOCOMMIT';
?
事務應該具有4個屬性:?原子性、一致性、隔離性、持久性。這四個屬性通常稱為ACID特性。
?
原子性(atomicity)。一個事務是一個不可分割的工作單位,事務中包括的諸操作要么都做,要么都不做。
一致性(consistency)。事務必須是使數據庫從一個一致性狀態變到另一個一致性狀態。一致性與原子性是密切相關的。
隔離性(isolation)。一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的數據對并發的其他事務是隔離的,并發執行的各個事務之間不能互相干擾。
持久性(durability)。持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。
?
其中原子性、持久性通過數據庫的?redo.log(重做日志)來實現,?undo.log用來保證事務的一致性、隔離性。
重做日志:每當有操作執行前,將數據真正更改時,先前相關操作寫入重做日志。這樣當斷電,或者一些意外,導致后續任務無法完成時,系統恢復后,可以繼續完成這些更改
撤消日志:當一些更改在執行一半時,發生意外,而無法完成,則可以根據撤消日志恢復到更改之前的壯態
比如某一時刻數據庫宕機了,有兩個事務,一個事務已經提交,另一個事務正在處理
數據庫重啟的時候就要根據日志進行前滾及回退,把已提交事務的更改寫到數據文件,未提交事務的更改恢復到事務開始前的狀態。
?
關于事務,你可能不知道的地方:
保存點(savepoint),當開始一個事務的時候,里面會隱式的包含一個保存點,也可以在事務過程中使用保存點,當RollBack的時候會回到上一個保存點的位置,每個保存點擁有一個ID。(?保存點只會遞增,例如:從3回滾到2,再記錄下一個保存點的時候ID為4)
事務還可以進行?鏈事務(觸發器連接)、嵌套事務、分布式事務
?
隔離級別
未提交讀(?Read Uncommitted):允許臟讀,也就是可能讀取到其他會話中未提交事務修改的數據,兩個事務互相透明。
提交讀(?Read Committed):只能讀取到已經提交的數據,但是可能造成同一個事務中,由于另一個事務提前提交了一個更改事務,導致select到的數據不一致,所以為不可重讀。
可重復讀(?Repeatable Read):在同一個事務內的查詢都是事務開始時刻一致的,InnoDB默認級別。在SQL標準中,該隔離級別消除了不可重復讀,但是還存在幻讀。
串行讀(?Serializable):完全串行化的讀,每次讀都需要獲得表級共享鎖,讀寫相互都會阻塞
其他的都好理解,看一下什么是?幻讀:
| session one ??? ? ? ? ? ? ???? ? ? ? ? ? ?? ?| ?session two?? ? ? ? ? ? ???? ? ? ? ? ? ??? ? ? ? ? ? ??|
| begin?? ? ? ? ? ? ???? ? ? ? ? ? ?? ? ? ? ? ? ? ? | ?begin ? ?? ? ? ? ? ? ???? ? ? ? ? ? ???? ? ? ? ? ? ? ? ? ? |
| select table ?where ?name ='join' ? | ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ?? ? ?? ? ?|
| Empty set (0.00 sec)? ? ? ? ? ? ? ? ? ? ?| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ?Insert into table (name) values ('join')? ? |
| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ?Query OK, 1 row affected ? ? ? ? ? ? ? ? ? ?? |
?
| select table ?where ?name ='join' ? | ? ??? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??? ? ?? ? ?? ? ?? ???|
|?Empty set?(0.00 sec)? ? ? ? ? ? ? ? ? ? ?| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
| >update table set age=18 where ?| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|
| >name='join' ; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|
|?Query OK, 1 row affected? ??? ? ? ? ?| commit ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
| commit ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|
圖中標紅的地方,可以看到明明查不到數據,但是卻update成功了,就和幻象一樣,幻讀之名由此而來。
設置隔離級別
1.查看?當前會話隔離級別
select @@tx_isolation;
2.查看?系統當前隔離級別
select @@global.tx_isolation;
3.設置?當前會話隔離級別
SET session TRANSACTION ISOLATION LEVEL repeatable read;
4.設置?系統當前隔離級別
SET global TRANSACTION ISOLATION LEVEL repeatable read;
?
?
InnoDB下的MVCC強版本控制:
你可將MVCC看成行級別鎖的一種妥協,它在許多情況下避免了使用鎖,同時可以提供更小的開銷。根據實現的不同,它可以
允許非阻塞式讀,在寫操作進行時只鎖定必要的記錄。
在每一行數據中額外保存兩個隱藏的列:當前行創建時的版本號和刪除時的版本號(可能為空)。這里的版本號并不是實際的時間值,?而是
系統版本號。每開始個新的事務,?系統版本號都會自動遞增。事務開始時刻的系統版本號會作為事務的版本號,用來和查詢每行記
錄的版本號進行比較。
每個事務又有自己的版本號,這樣事務內執行CRUD操作時,就通過版本號的比較來達到數據版本控制的目的。
InnoDB每行數據只在表中保留一份,在更新數據時上行鎖,同時將舊版數據寫入?undo log;表和 undo log 中行數據都記錄著事務ID,在檢索時,只讀取來自當前已提交的
事務的行數據.
?
MVCC具體的操作如下:
?
SELECT:InnoDB會根據以下兩個條件檢查每行記錄:
1)InnoDB只查找版本早于當前事務版本的數據行(也就是,行的系統版本號小于或等于事務的系統版本號),這樣可以確保事務讀取的行,只么是在事務開始前已經存在的,要么是事務自身插入或者修改過的。
2)行的刪除版本要么未定義,要么大于當前事務版本號。這可以確保事務讀取到的行,在事務開始之前未被刪除。
INSERT:InnoDB為新插入的每一行保存當前系統版本號作為行版本號。
DELETE:InnoDB為刪除的每一行保存當前系統版本號作為行刪除標識。
UPDATE:InnoDB為插入一行新記錄,保存當前系統版本號作為行版本號,同時保存當系統的版本號為原來的行作為刪除標識。
?
保存這兩個額外系統版本號,使大多數操作都可以不用加鎖。這樣設計使得計數據操作很簡單,性能很好,并且也能保證只會讀取到符合標準的行。不足之處是每行記錄都需要額外的存儲空間,需要做更多的行檢查工作,以及一些額外的維護工作。
MVCC只在REPEATABLE READ和READ COMMITED兩個隔離級別下工作,其它兩個隔離級別和MVCC不兼容。
可是為什么RR級別和RC級別看到的數據不一樣呢?我們來看看innodb中MVCC的具體原理是怎么處理的
隱藏列
在分析MVCC原理之前,先看下InnoDB中數據行的結構:
在InnoDB中,每一行都有2個隱藏列?DATA_TRX_ID和?DATA_ROLL_PTR(如果沒有定義主鍵,則還有個隱藏主鍵列):
DATA_TRX_ID表示最近修改該行數據的事務ID
DATA_ROLL_PTR則表示指向該行回滾段的指針,該行上所有舊的版本,在undo中都通過鏈表的形式組織,而該值,正式指向undo中該行的歷史記錄鏈表
整個MVCC的關鍵就是通過DATA_TRX_ID和DATA_ROLL_PTR這兩個隱藏列來實現的。
事務鏈表
MySQL中的事務在開始到提交這段過程中,都會被保存到一個叫?trx_sys的事務鏈表中,這是一個基本的鏈表結構:
事務鏈表中保存的都是還未提交的事務,事務一旦被提交,則會被從事務鏈表中摘除。
Read View
有了前面隱藏列和事務鏈表的基礎,接下去就可以構造MySQL實現MVCC的關鍵——ReadView。
ReadView說白了就是一個數據結構,在SQL開始的時候被創建。這個數據結構中包含了3個主要的成員:?ReadView{low_trx_id, up_trx_id, trx_ids},在并發情況下,一個事務在啟動時,trx_sys鏈表中存在部分還未提交的事務,那么哪些改變對當前事務是可見的,哪些又是不可見的,這個需要通過ReadView來進行判定,首先來看下ReadView中的3個成員各自代表的意思:
low_trx_id表示該事務啟動時,當前事務鏈表中最大的事務id編號,也就是最近創建的除自身以外最大事務編號;
up_trx_id表示該事務啟動時,當前事務鏈表中最小的事務id編號,也就是當前系統中創建最早但還未提交的事務;
trx_ids表示所有事務鏈表中事務的id集合。
上述3個成員組成了ReadView中的主要部分,簡單圖示如下:
根據上圖所示,所有數據行上DATA_TRX_ID小于up_trx_id的記錄,說明修改該行的事務在當前事務開啟之前都已經提交完成,所以對當前事務來說,都是可見的。而對于DATA_TRX_ID大于low_trx_id的記錄,說明修改該行記錄的事務在當前事務之后,所以對于當前事務來說是不可見的。
注意,?ReadView是與SQL綁定的,而并不是事務,所以即使在同一個事務中,每次SQL啟動時構造的ReadView的up_trx_id和low_trx_id也都是不一樣的,至于DATA_TRX_ID大于low_trx_id本身出現也只有當多個SQL并發的時候,在一個SQL構造完ReadView之后,另外一個SQL修改了數據后又進行了提交,對于這種情況,數據其實是不可見的。
最后,至于位于(up_trx_id, low_trx_id)中間的事務是否可見,這個需要根據?不同的事務隔離級別來確定。對于RC的事務隔離級別來說,對于事務執行過程中,已經提交的事務的數據,對當前事務是可見的,也就是說上述圖中,當前事務運行過程中,trx1~4中任意一個事務提交,對當前事務來說都是可見的;而對于RR隔離級別來說,事務啟動時,已經開始的事務鏈表中的事務的所有修改都是不可見的,所以在RR級別下,low_trx_id基本保持與up_trx_id相同的值即可。這里解釋完也可以了解為什么會出現幻讀。
名詞解釋
DML(Data Manipulation Language)數據操縱語言:
適用范圍:對數據庫中的數據進行一些簡單操作,如insert,delete,update,select等.
DDL(Data Definition Language)數據定義語言:
適用范圍:對數據庫中的某些對象(例如,database,table)進行管理,如Create,Alter和Drop.
來源:https://blog.csdn.net/liqinglin06/article/details/77750246
總結
以上是生活随笔為你收集整理的Innodb隔离级别的实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java错误: 找不到或无法加载主类 H
- 下一篇: excel表格求和出来是0(excel自