元数据解决分表不可 mysql_MySQL InnoDB技术内幕:内存管理、事务和锁
前面有多篇文章介紹過(guò)MySQL InnoDB的相關(guān)知識(shí),今天我們要更深入一些,看看它們的內(nèi)部原理和機(jī)制是如何實(shí)現(xiàn)的。
一、內(nèi)存管理
我們知道,MySQl是一個(gè)存儲(chǔ)系統(tǒng),數(shù)據(jù)最后都寫在磁盤上。我們以前也提到過(guò),磁盤的速度特別是大容量的磁盤受磁頭臂的影響,速度相對(duì)內(nèi)存慢很多。所以Innodb實(shí)現(xiàn)了自己的緩存機(jī)制。
首先我們先看下Innodb對(duì)內(nèi)存是如何使用和劃分的,然后我們?cè)倏纯此侨绾伪4鏌釘?shù)據(jù)的。
1、主要模塊和組成
(1) Buffer Pool
預(yù)分配的內(nèi)存池
(2) Page
Buffer Pool的最小單位
(3) Free list
空閑Page組成的鏈表
(4) Flush list
臟頁(yè)鏈表
(5) Page hash 表
維護(hù)內(nèi)存Page和文件Page的映射關(guān)系
(6) LRU
內(nèi)存淘汰算法
以上三種鏈表LRU list、Free list、Flush list 和內(nèi)存池、Page hash?以及磁盤文件之間的映射關(guān)系如下圖所示:
2、LRU算法
LRU,Least Recent Used,最近最少使用。每次將剛使用過(guò)的頁(yè)面插到LRU隊(duì)列的最前端,那么最少使用的排在尾端,當(dāng)緩存不夠時(shí),淘汰尾端的頁(yè)。
很多文件系統(tǒng)和開源庫(kù)的內(nèi)存淘汰算法都用到了LRU,以前有不少文章都提到過(guò)。
但是LRU的缺陷是,有時(shí)會(huì)無(wú)法淘汰真正的冷數(shù)據(jù),尾端的數(shù)據(jù)可能暫時(shí)沒(méi)使用而已,不代表不使用頻繁,不代表不是熱數(shù)據(jù)。所以很多系統(tǒng)對(duì)LRU進(jìn)行了優(yōu)化。
比如Redis加了LFU(least?frequently?used最不經(jīng)常使用)配合LRU一起使用。
那么InnoDB存儲(chǔ)引擎是如何改進(jìn)的呢?如下圖,它將LRU分成兩部分,中間的分割點(diǎn)叫做midpoint,新讀取的頁(yè)不再是加入到最頭部,而是midpoint后面的位置,即后半截的頭部。
那么midpoint的位置是如何計(jì)算的呢,在默認(rèn)配置下,離LRU整個(gè)頭部的5/8處。當(dāng)然這個(gè)比例是可以根據(jù)實(shí)際業(yè)務(wù)進(jìn)行設(shè)置的。但總之,可以真正將 冷熱數(shù)據(jù)分離 ,熱數(shù)據(jù)在前,冷數(shù)據(jù)在后。
那么這兩個(gè)區(qū)的數(shù)據(jù)如何移動(dòng)的呢,即冷熱數(shù)據(jù)如何切換的呢?
上面我們提到了,剛插入的頁(yè)放在old區(qū)的頭部,那么如果該頁(yè)確實(shí)訪問(wèn)頻繁,不能一直呆在該位置吧。
InnoDB引入了參數(shù)innodb_old_blocks_time,如果old區(qū)的數(shù)據(jù)在該時(shí)間范圍內(nèi)沒(méi)有被淘汰出去,就可以移到new區(qū),加入到new區(qū)的頭部。這也叫做 made young 。
而如果在old呆的時(shí)間不夠innodb_old_blocks_time,而且緩存不夠時(shí),就會(huì)面臨直接淘汰,這就叫做 made not young 。這種情況,可以發(fā)生在全表掃描的時(shí)候,保證了new區(qū)的數(shù)據(jù)才是真正的熱數(shù)據(jù)!
當(dāng)然數(shù)據(jù)也有可能從 new區(qū)移動(dòng)到old區(qū) ,只是相對(duì)比較簡(jiǎn)單了,直接移動(dòng)midpoint指向的位置即可。即new區(qū)的尾巴變成了old區(qū)的頭部。
二、事務(wù)
1、 MySQL事務(wù)基本概念
事務(wù)特性
A(Atomicity原子性):全部成功或全部失敗
I(Isolation隔離性):并行事務(wù)之間互不干擾
D(Durability持久性):事務(wù)提交后,永久生效
C(Consistency一致性):通過(guò)AID保證
并發(fā)問(wèn)題
臟讀(Drity Read):讀取到未提交的數(shù)據(jù)。 中間所有變化的值都可能讀到 。
不可重復(fù)讀(Non-repeatable read):兩次讀取結(jié)果不同。讀取已提交的(不一樣的值), 讀到的值變化數(shù)量比臟讀要少 。
幻讀(Phantom Read):select 操作得到的結(jié)果所表征的數(shù)據(jù)狀態(tài) 影響(無(wú)法支撐)后續(xù)的業(yè)務(wù)操作。
網(wǎng)上有人這樣區(qū)分,臟讀是讀取修改的數(shù)據(jù),幻讀是讀取新提交的數(shù)據(jù)。我認(rèn)為也可以,或許phantom表示 虛幻的新數(shù)據(jù) (所以無(wú)法支撐后續(xù)操作),而drity代表了修改的意思呢?
所以,不可重復(fù)讀重點(diǎn)在于update和delete,而幻讀的重點(diǎn)在于insert。
隔離級(jí)別
Read Uncommitted(讀取未提交內(nèi)容):最低隔離級(jí)別,會(huì)讀取到其他事務(wù)未提交的數(shù)據(jù);存在 臟讀 的問(wèn)題 。
Read Committed(讀取提交內(nèi)容):事務(wù)過(guò)程中可以讀取到其他事務(wù)已提交的數(shù)據(jù);存在 不可重復(fù)讀 的問(wèn)題 。
Repeatable Read(可重復(fù)讀):每次讀取相同結(jié)果集,不管其他事務(wù)是否提交;存在 幻讀 的問(wèn)題 。
Serializable(串行化):事務(wù)排隊(duì),隔離級(jí)別最高,性能最差。
2、MySQL事務(wù)實(shí)現(xiàn)原理
從上我們可以看出事務(wù)有ACID四大特性,而“I”隔離性是通過(guò)鎖來(lái)實(shí)現(xiàn)的,我們下一節(jié)講述。那么其他三個(gè)特性主要通過(guò)undo/redo日志的機(jī)制來(lái)實(shí)現(xiàn),這個(gè)知識(shí)點(diǎn)在前面有一篇文章中介紹和對(duì)比過(guò)。 現(xiàn)在我們站在事務(wù)實(shí)現(xiàn)的角度再來(lái)看看。
(1)undo log
回滾日志,顧名思義,是對(duì)事物rollback時(shí)使用。這是它核心的功能之一,但是它還有另一個(gè)非常重要的功能,MVCC。所以今天這里主要介紹它是如何在事務(wù)中發(fā)揮作用的。
MVCC
Multiversion concurrency control,多版本并發(fā)控制。當(dāng)用戶讀取一行時(shí),如果該記錄已經(jīng)被其他事務(wù)占用,當(dāng)前事務(wù)可以通過(guò)undo讀取之前的 行版本信息 ( 快照數(shù)據(jù) ),以此實(shí)現(xiàn) 非鎖定讀 。 所以實(shí)現(xiàn)了非阻塞的讀操作,寫操作也只鎖定必要的行。即 解決讀-寫沖突。
快照數(shù)據(jù)就是當(dāng)前行數(shù)據(jù)的歷史版本,每行記錄可能含有多個(gè)版本。那該讀取哪個(gè)版本呢?
首先,InnoDB的每行記錄或者說(shuō)每條數(shù)據(jù),除了記錄用戶定義的列之外,還有 兩個(gè)隱藏的列 :事務(wù)ID列 DB_TRX_ID 和回滾指針 DB_ROLL_PTR。 如果該表沒(méi)有定義主鍵,每行還會(huì)增加一個(gè)rowid列。 DB_TRX_ID是當(dāng)時(shí)執(zhí)行這條sql的事務(wù)id,DB_ROLL_PTR指向的就是undo log中修改前的行DB_ROW_ID。所以對(duì)同一條數(shù)據(jù)的修改,通過(guò)roll_pointer就形成了 undo log版本鏈 。
然后我們?cè)賮?lái)介紹下 Read View 快照讀。
一般情況下讀取數(shù)據(jù)時(shí)會(huì)生成一個(gè)Read View,對(duì)當(dāng)前該行的可能正在進(jìn)行的事務(wù)進(jìn)行一個(gè)快照。
Read View中主要包含4個(gè)比較重要的內(nèi)容:
m_ids:表示在生成Read View時(shí)當(dāng)前系統(tǒng)中活躍的讀寫事務(wù)的事務(wù)id列表,簡(jiǎn)稱 活躍列表 。
min_trx_id:表示在生成Read View時(shí)當(dāng)前系統(tǒng)中活躍的讀寫事務(wù)中 最小的事務(wù)id ,也就是m_ids中的最小值。
max_trx_id:表示生成Read View時(shí)系統(tǒng)中應(yīng)該分配給下一個(gè)事務(wù)的 id 值,,也就是m_ids中的 最大 值。
creator_trx_id:表示生成該Read View的事務(wù)的事務(wù)id。
有了這些信息,這樣在訪問(wèn)某條記錄時(shí),只需要依次判斷undo log版本鏈中節(jié)點(diǎn)的事務(wù)ID 是否可見(jiàn) ,如果可見(jiàn)即找到了所需要的行記錄。
最后,READ COMMITTED和REPEATABLE READ兩種隔離級(jí)別對(duì)于快照數(shù)據(jù)生成的時(shí)機(jī)不一樣。
對(duì)于RC,在每次查詢語(yǔ)句執(zhí)行的過(guò)程中,都關(guān)閉Read View, 再創(chuàng)建當(dāng)前的一份Read View。這樣就會(huì)產(chǎn)生不可重復(fù)讀現(xiàn)象。
對(duì)于RR,創(chuàng)建事務(wù)trx結(jié)構(gòu)的時(shí)候,就生成了當(dāng)前的global Read View,一直維持到事務(wù)結(jié)束。在事務(wù)結(jié)束這段時(shí)間內(nèi)每一次查詢都不會(huì)重新重建Read View,從而實(shí)現(xiàn)了可重復(fù)讀。
undo log分為兩種格式 ,處理不一樣。
insert undo log:用于回滾,提交即清理;不需要進(jìn)行purge操作。
update undo log:用于回滾,同時(shí)實(shí)現(xiàn)快照讀,不能隨便刪除,所以需要等待purge線程來(lái)判斷何時(shí)刪除。它記錄的是對(duì)delete和update的操作產(chǎn)生的undo log。
注:以上來(lái)自書上的說(shuō)法,網(wǎng)上有人把第一種說(shuō)成delete undo log,包括insert和delete操作,供參考。
還需要補(bǔ)充一點(diǎn)的是,update undo log怎樣去清理, 應(yīng)該是根據(jù)系統(tǒng)活躍的Read view中最小的活躍事務(wù)ID之前的即可清除。
(2)redo log
redo日志其實(shí)在《 MySQL的undo/redo日志和binlog日志,以及2PC 》文章中介紹比較多,也提到了XA事務(wù)的2PC。我們這里簡(jiǎn)單介紹下普通事務(wù)的流程。
寫入流程仍然可以分為兩步,類似二階段提交:
記錄頁(yè)的修改,狀態(tài)為prepare
事務(wù)提交,講事務(wù)記錄為commit狀態(tài)
三、鎖
1、InnoDB鎖種類
(1) 類型
共享鎖(S)
讀鎖,可以同時(shí)被多個(gè)事務(wù)獲取,阻止其他事務(wù)對(duì)記錄的修改。
排他鎖(X)
寫鎖,只能被一個(gè)事務(wù)獲取,允許獲得鎖的事務(wù)修改數(shù)據(jù)。
而讀其實(shí)又可以分為 當(dāng)前讀 (鎖定讀)和快照讀(非鎖定讀),而快照讀通過(guò)上一節(jié)描述的MVCC來(lái)實(shí)現(xiàn)。
當(dāng)前讀, 讀取的是最新版本,所以需要對(duì)讀取的記錄加鎖,阻塞其他事務(wù)改動(dòng)該記錄。當(dāng)前讀又分為兩種方式:
select...for update,對(duì)讀取的行加X(jué)鎖;
select...lock in share mode ,對(duì)讀取的行加S鎖。
(2)鎖粒度
行級(jí)鎖
Record Lock,單個(gè)記錄上的鎖。
鎖直接加在索引記錄上面,鎖住的是key,所以必須是 聚簇索引或者二級(jí)索引是唯一索引。
間隙鎖
Gap Lock,間隙鎖,鎖定一個(gè)范圍,單不包含記錄本身。
InnoDB存儲(chǔ)引擎的隔離級(jí)別默認(rèn)是Repeatable Read,所以引入了間隙鎖 解決 可重復(fù)讀模式下的 幻讀問(wèn)題 。
GAP鎖不是加在記錄上, 鎖住的位置是兩條記錄之間的GAP; 保證 兩次當(dāng)前讀 返回一致的記錄。
所以兩次當(dāng)前讀之前,其他的事務(wù) 不會(huì)插入新的 滿足條件的記錄。
我們來(lái)整理下著兩者的關(guān)系和區(qū)別。
Record Lock針對(duì)的是索引必須具備唯一性;而GAP鎖針對(duì)的是索引不具備唯一性但需要保證可重復(fù)讀,也就是說(shuō)如果發(fā)現(xiàn)數(shù)據(jù)有被其他事務(wù)修改的可能,那就把前后間隙都加上鎖。
比如說(shuō)如下圖,有個(gè)用戶表,uid為主鍵,那么就只需要103這條記錄加上行鎖即可。
但是如果我們變化下查詢條件(phone列上建立了二級(jí)索引),則除了對(duì)于這兩條記錄加鎖外,對(duì)前后的間隙也需要加鎖。當(dāng)然這種情況是針對(duì)RR的隔離級(jí)別,如果隔離級(jí)別是RC或者更低,安全性就沒(méi)有這么高,系統(tǒng)會(huì)自動(dòng)降級(jí)到行鎖。
Next-Key Lock,是Record Lock與Gap Lock的一個(gè)結(jié)合。理解了上述兩種鎖的原理,對(duì)于它而言就很容易了。
表級(jí)鎖
Table Lock,鎖定整張表。
主要用在運(yùn)維的時(shí)候,對(duì)表格進(jìn)行操作比如MDL或者元數(shù)據(jù)的操作 (meta data lock)等等。
當(dāng)然有些情況下會(huì)觸發(fā)鎖升級(jí): 全表掃描。全表掃描的觸發(fā)一般情況下是當(dāng)前被查詢的字段沒(méi)有建立任何索引。
而表級(jí)鎖事實(shí)上是對(duì)所有記錄和所有的間隙都加上鎖。
所以全表掃描的效率非常低,要盡量避免。
2、InnoDB加鎖過(guò)程
如下圖,當(dāng)我們更新多條數(shù)據(jù)時(shí),是一行一行的加鎖。
所以當(dāng)同時(shí)出現(xiàn)對(duì)多條記錄交叉查詢時(shí),很容易出現(xiàn)AB-BA死鎖,如下圖操作。
附錄
分庫(kù)分表的建議:
是否分表
建議單表不超過(guò)1KW
分表方式
取模:存儲(chǔ)均勻&訪問(wèn)均勻
按時(shí)間:冷熱庫(kù)
分庫(kù)
按業(yè)務(wù)垂直分
水平查分多個(gè)庫(kù)
參考:
《MySQL技術(shù)內(nèi)幕InnoDB存儲(chǔ)引擎》
內(nèi)部培訓(xùn)資料
總結(jié)
以上是生活随笔為你收集整理的元数据解决分表不可 mysql_MySQL InnoDB技术内幕:内存管理、事务和锁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 汽车转向油泵的方法?
- 下一篇: 兰州到西安高速有充电桩吗?