Schema與數(shù)據(jù)類型優(yōu)化
選擇優(yōu)化的數(shù)據(jù)類型
有幾個簡單的原則:
- 更小的通常更好
一般情況下使用可以正確存儲數(shù)據(jù)的最小數(shù)據(jù)類型罩句。 - 簡單的更好
例如整型比字符操作代價更低谍憔。應(yīng)當(dāng)使用Mysql的日期類型而不是字符串匪蝙,應(yīng)當(dāng)用整型存儲IP地址 - 盡量避免NULL
查詢中如果包含NULL的列,對于Mysql來說更難優(yōu)化习贫,這樣使得索引逛球,索引統(tǒng)計,值都比較復(fù)雜苫昌。NULL的列會使用更多的存儲空間颤绕,在Mysql里也需要特殊處理。當(dāng)可為NULL的列被索引時祟身,每個索引記錄需要一個額外的字節(jié)
整數(shù)類型
無符號的數(shù)字上限可以提高一倍
為整數(shù)類型指定寬度奥务,如INT(11),不會限制值的合法范圍袜硫,只是規(guī)定了Mysql的一些交互工具(命令行或客戶端)用來顯示的字符個數(shù)氯葬。對于存儲和計算來講,int(1) 和 int(20)是相同的婉陷。
實數(shù)類型
浮點類型在存儲同樣范圍的值時,通常比Decimal使用更少的空間憨攒,F(xiàn)loat使用4個字節(jié),Double使用8個字節(jié)相比Float有更高的精度和更大的范圍蛛壳。這里能選擇的是存儲類型,Mysql內(nèi)部使用Double作為內(nèi)部浮點計算的類型。
字符串類型
CHAR和VARCHAR
VARCHAR節(jié)省了存儲空間,如果行占用存儲空間增長这溅,并且在頁內(nèi)沒有更多的空間存儲组民,MyISAM拆成不同的片段存儲,InnoDB則需要分裂頁來使行可以放進頁內(nèi)悲靴。
下列情況使用Varchar是合適的:
- 字符串最大長度比平均長度大很多臭胜;
- 列的更新很少,所以碎片不是問題对竣;
- 使用了UTF-8字符集庇楞,每個字符都使用不同的字節(jié)數(shù)進行存儲。
InnoDB把過長的VARCHAR存儲為BLOB
CHAR是定長的否纬,會刪除末尾的空格吕晌。CHAR(1)需要一個字節(jié),VARCHAR(1)需要2個字節(jié)临燃,因為還需要多一個字節(jié)存儲長度睛驳。
類似的還有BINNARY和VARBINARY,填充使用的\0(0字節(jié))
BLOB和TEXT
都是為了存儲很大的數(shù)據(jù)設(shè)計的字符串?dāng)?shù)據(jù)類型膜廊,分別采用二進制和字符方式存儲乏沸。不同在于BLOB存儲的是二進制數(shù)據(jù),沒有排序規(guī)則或者字符集爪瓜。
排序也只是對每個列的max_sort_length字節(jié)而不是整個字符串排序蹬跃。
查詢?nèi)绻婕癇LOB,服務(wù)器不能在內(nèi)存臨時表中存儲BLOB铆铆,必須要使用磁盤臨時表蝶缀,無論它多小丹喻。
日期和時間類型
DATETIME可以存儲1001到9999年,精度為秒翁都,與時區(qū)無關(guān)碍论,使用8個字節(jié)的存儲空間。TIMESTAMP保存了1970年1月1日以來的秒數(shù)柄慰。只使用4個字節(jié)的存儲空間鳍悠。從1970到2038年。
位數(shù)據(jù)類型
這些類型坐搔,不管底層存儲格式和處理方式如何藏研,從技術(shù)上來說都是字符串類型。
BIT
5.0之前BIT是TINYINT的同義詞薯蝎。之后則完全不同遥倦。MyISAM會打包所有的BIT列,InnoDB和Memory使用足夠存儲最小整數(shù)類型來存放BIT占锯,所以不能節(jié)省存儲空間袒哥。Mysql把BIT當(dāng)作字符串類型而不是數(shù)字,會造成一些混亂消略。例如 a bit(8)堡称,值為b'00111001'二進制等于57(ascii顯示值等于9),a=9艺演,a+0=57却紧。應(yīng)該謹慎使用,如果想存儲true/false胎撤,可以使用CHAR(0)
選擇標識符(identifier)
整數(shù)類型是最好的選擇晓殊,很快并且可以使用AUTO_INCREMENT。避免使用字符串作為標識列伤提,很耗空間巫俺,通常比數(shù)字類型慢,MyISAM默認對字符串使用壓縮索引肿男,會導(dǎo)致查詢慢很多介汹。
- 隨機值如MD5,SHA1舶沛,UUID會導(dǎo)致INSERT和一些SELECT語句變慢嘹承,因為可能導(dǎo)致隨機寫入索引不同位置,導(dǎo)致頁分裂如庭,磁盤隨機訪問叹卷,對于聚簇存儲引擎產(chǎn)生聚簇索引碎片。
- SELECT語句變慢因為邏輯上相鄰的行會分布在磁盤和內(nèi)存的不同地方。
- 隨機值導(dǎo)致緩存對所有類型的查詢語句效果都很差豪娜。
Scheme設(shè)計中的陷阱
- 太多的列
Mysql的存儲引擎API工作時需要在服務(wù)器層和存儲引擎層之間通過行緩沖格式拷貝數(shù)據(jù)餐胀,然后在服務(wù)器層將緩沖內(nèi)容解碼成各個列哟楷。從行緩沖中將編碼過的列轉(zhuǎn)換成行數(shù)據(jù)結(jié)構(gòu)的操作代價是非常高的瘤载。非常寬的表可能會使得CPU占用非常高。 - 太多的關(guān)聯(lián)
“實體-屬性-值”(EAV)設(shè)計模式在Mysql下不能靠譜的工作卖擅,限制了每個關(guān)聯(lián)操作最多只能有61張表鸣奔。單個查詢最好在12個表內(nèi)做關(guān)聯(lián)。 - 全能的枚舉
枚舉列表增加數(shù)據(jù)需要使用到ALTER TABLE惩阶,若不是加在最后可能會有影響 - 變相的枚舉
范式和反范式
在范式化的數(shù)據(jù)庫中挎狸,每個事實數(shù)據(jù)會出現(xiàn)并且只出現(xiàn)一次,相反断楷,在反范式化的數(shù)據(jù)庫中锨匆,信息是冗余的。
第一范式
確保數(shù)據(jù)表中每列(字段)的原子性冬筒。
如果數(shù)據(jù)表中每個字段都是不可再分的最小數(shù)據(jù)單元恐锣,則滿足第一范式。
例如:user用戶表舞痰,包含字段id,username,password第二范式
在第一范式的基礎(chǔ)上更進一步土榴,目標是確保表中的每列都和主鍵相關(guān)。
如果一個關(guān)系滿足第一范式响牛,并且除了主鍵之外的其他列玷禽,都依賴于該主鍵,則滿足第二范式呀打。
例如:一個用戶只有一種角色矢赁,而一個角色對應(yīng)多個用戶。則可以按如下方式建立數(shù)據(jù)表關(guān)系贬丛,使其滿足第二范式撩银。
user用戶表,字段id,username,password,role_id
role角色表瘫寝,字段id,name
用戶表通過角色id(role_id)來關(guān)聯(lián)角色表第三范式
在第二范式的基礎(chǔ)上更進一步蜒蕾,目標是確保表中的列都和主鍵直接相關(guān),而不是間接相關(guān)焕阿。
例如:一個用戶可以對應(yīng)多個角色咪啡,一個角色也可以對應(yīng)多個用戶。則可以按如下方式建立數(shù)據(jù)表關(guān)系暮屡,使其滿足第三范式撤摸。
user用戶表,字段id,username,password
role角色表,字段id,name
user_role用戶-角色中間表准夷,id,user_id,role_id
像這樣钥飞,通過第三張表(中間表)來建立用戶表和角色表之間的關(guān)系,同時又符合范式化的原則衫嵌,就可以稱為第三范式读宙。反范式化
反范式化指的是通過增加冗余或重復(fù)的數(shù)據(jù)來提高數(shù)據(jù)庫的讀性能。
例如:在上例中的user_role用戶-角色中間表增加字段role_name楔绞。
反范式化可以減少關(guān)聯(lián)查詢時结闸,join表的次數(shù)。
范式的優(yōu)點
- 范式化的更新操作更快
- 更新需要變更的數(shù)據(jù)更少
- 表比較小酒朵,可以更好放在內(nèi)存里
缺點是通常需要關(guān)聯(lián)桦锄,代價相對昂貴,也可能使得一些索引策略無效蔫耽。
反范式的優(yōu)點
避免關(guān)聯(lián)
查詢相對高效(當(dāng)索引合理)
創(chuàng)建高性能索引
索引可以包含一個或多個列结耀,如果索引包含多個列,那列的順序也十分重要匙铡,因為Mysql只能最高效的使用索引的最左前綴列图甜。
B-Tree的索引列是順序組織存儲的,很適合查找范圍數(shù)據(jù)慰枕。適用于全鍵值具则、鍵值范圍或鍵前綴查找。
紅黑樹是一種含有紅黑結(jié)點并能自平衡的二叉查找樹具帮。它必須滿足下面性質(zhì):
性質(zhì)1:每個節(jié)點要么是黑色博肋,要么是紅色。
性質(zhì)2:根節(jié)點是黑色蜂厅。
性質(zhì)3:每個葉子節(jié)點(NIL)是黑色匪凡。
性質(zhì)4:每個紅色結(jié)點的兩個子結(jié)點一定都是黑色。
性質(zhì)5:任意一結(jié)點到每個葉子結(jié)點的路徑都包含數(shù)量相同的黑結(jié)點掘猿。
從性質(zhì)5又可以推出:
性質(zhì)5.1:如果一個結(jié)點存在黑子結(jié)點病游,那么該結(jié)點肯定有兩個子結(jié)點
哈希索引(hash index)只有精確匹配索引所有列的查詢才有效。只包含哈希值和行指針稠通,不存儲字段值衬衬,所以不能避免讀取行。
并不是按照索引值順序存儲改橘,所以無法用于排序滋尉。
也不支持部分索引列匹配查找。只支持等值查詢飞主,不支持范圍查詢狮惜。
高性能的索引策略
獨立的列才能使用到索引高诺,列不能使用操作符或者表達式
多列索引,當(dāng)使用到多個單列索引時碾篡,會進行多個索引的聯(lián)合操作(索引合并)
選擇合適的索引列順序
正確的順序依賴于使用該索引的查詢虱而,并且同時需要考慮如何更好地滿足排序和分組的需要。
在一個多列B-Tree索引中开泽,索引列的順序意味著索引首先按照最左列進行排序牡拇,其次是第二列。
聚簇索引
并非一種單獨的索引類型眼姐,而是一種數(shù)據(jù)存儲方式诅迷。InnoDB在同一個結(jié)構(gòu)中保存了B-Tree索引和數(shù)據(jù)行。
InnoDB使用主鍵聚集數(shù)據(jù)众旗,如果沒有定義主鍵,會選擇一個唯一的非空索引代替趟畏,如果沒有這樣的索引贡歧,會隱式定義一個主鍵作為聚簇索引。InnoDB只聚集同一個頁面的記錄赋秀。
優(yōu)點:
- 把相關(guān)數(shù)據(jù)保存再一起利朵。
- 數(shù)據(jù)訪問更快
- 使用覆蓋索引掃描的查詢可以直接使用節(jié)點中的主鍵值。
缺點:
- 插入速度依賴于插入順序猎莲,如果不是按照主鍵加載數(shù)據(jù)绍弟,加載完成后最好使用OPTIMIZE TABLE重新組織表
- 更新聚簇索引的代價很高,因為會將被更新的行移動到新位置
- 插入新航或者主鍵更新需要移動行時著洼,可能面臨“頁分裂(Page Split)”問題
- 可能導(dǎo)致全表掃描變慢樟遣,尤其是行比較稀疏
- 二級索引(非聚簇索引)可能比想象的要更大,因為葉子節(jié)點包含了引用行的主鍵列身笤。
- 二級索引需要兩次索引查找豹悬,而不是一次
覆蓋索引
如果索引的葉子節(jié)點中已經(jīng)包含要查詢的數(shù)據(jù),那么還有什么必要再回表查詢呢液荸?所以一個索引包含(或者覆蓋)所有需要查詢的字段的值瞻佛,我們就稱之為覆蓋索引。
索引排序
只有索引的列順序和orderby的順序完全一致娇钱,并且列的正序伤柄,逆序都一樣時,才能使用索引對結(jié)果進行排序文搂。如果查詢需要關(guān)聯(lián)多張表适刀,則只有當(dāng)orderby的引用字段全部為第一個表時,才能使用索引進行排序细疚。
索引和數(shù)據(jù)的碎片化
B-Tree索引可能會碎片化蔗彤。
表的數(shù)據(jù)存儲也可能碎片化:
行碎片
這種碎片指的時數(shù)據(jù)行被存儲到多個地方的多個片段中川梅。即使只查詢一行記錄,也會導(dǎo)致性能下降然遏。
行間碎片
邏輯上順序的頁贫途,或者行再磁盤上不是順序存儲的。行間碎片對諸如全表掃描和聚簇索引掃描之類的操作有很大影響待侵。
剩余空間碎片
指數(shù)據(jù)頁中有大量的空余空間丢早,會導(dǎo)致服務(wù)器讀取大量不需要的數(shù)據(jù)造成浪費。
查詢性能優(yōu)化
查詢的聲明周期大致按照順序:
從客戶端秧倾,到服務(wù)器怨酝,然后在服務(wù)器上進行解析,生成執(zhí)行計劃那先,執(zhí)行农猬,并返回結(jié)果給客戶端。執(zhí)行時最重要的階段售淡,包含了大量為檢索數(shù)據(jù)到存儲引擎的調(diào)用以及調(diào)用后的數(shù)據(jù)處理斤葱,包括排序,分組等揖闸。
慢查詢基礎(chǔ):優(yōu)化數(shù)據(jù)訪問
是否請求了不需要的數(shù)據(jù)
- 查詢不需要的記錄
- 查詢不需要的列 (多表關(guān)聯(lián) * )
- 總是取出全部列(select * )
- 重復(fù)查詢相同的數(shù)據(jù)
是否在掃描額外的記錄
衡量查詢開銷的三個指標如下:
- 響應(yīng)時間
- 掃描的行數(shù)
- 返回的行數(shù)
響應(yīng)時間是 服務(wù)時間 和 排隊時間 之和揍堕。
掃描的行數(shù)和返回的行數(shù)理想情況下應(yīng)該是相同的,一般在1:1到10:1之間
掃描的行數(shù)和訪問類型:在EXPAIN語句中的type列反應(yīng)了訪問類型汤纸。訪問類型有很多中衩茸,包括全表掃描,索引掃描贮泞,范圍掃描楞慈,唯一索引查詢,常數(shù)引用等隙畜。這些速度是從慢到快抖部,掃描行數(shù)也是從多到少。
重構(gòu)查詢的方式
一個復(fù)雜查詢還是多個簡單查詢
Mysql支持多個簡單查詢议惰,一個通用服務(wù)器上可以支持每秒10萬的查詢慎颗,一個千兆網(wǎng)卡滿足每秒2000次的查詢。Mysql內(nèi)部每秒能掃描內(nèi)存中上百萬行數(shù)據(jù)言询,相比之下響應(yīng)數(shù)據(jù)給客戶端就慢得多了
切分查詢
將一個大查詢分而治之俯萎,例如一個刪除大量數(shù)據(jù)的語句,拆分為多個小的刪除运杭。
分解關(guān)聯(lián)查詢
有很多好處:
- 讓緩存的效率更高夫啊。無論是應(yīng)用程序的緩存和Mysql的緩存,都會在單表的情況下更容易命中辆憔。
- 查詢分解后減少了鎖的競爭
- 應(yīng)用層關(guān)聯(lián)撇眯,更容易對數(shù)據(jù)庫進行拆分报嵌,做到高性能和可擴展
- 減少冗余記錄的查詢
- 在應(yīng)用中實現(xiàn)的哈希關(guān)聯(lián),而不是使用Mysql的嵌套查詢熊榛。
執(zhí)行查詢的基礎(chǔ)
執(zhí)行查詢的過程:
- 客戶端發(fā)送一條查詢給服務(wù)器
- 服務(wù)器先檢查緩存锚国,如果命中了緩存,則立刻返回存儲在緩存中的結(jié)果玄坦。否則進入下一個階段血筑。
- 服務(wù)器進行SQL解析,預(yù)處理煎楣,再由優(yōu)化器生成對應(yīng)的執(zhí)行計劃豺总。
- Mysql根據(jù)優(yōu)化器生成的執(zhí)行計劃,調(diào)用存儲引擎的API執(zhí)行查詢择懂。
- 將結(jié)果返回給客戶端
Mysql客戶端/服務(wù)器通信協(xié)議
通信協(xié)議是“半雙工”的喻喳,意味著任何一個時刻,要么是服務(wù)端向客戶端發(fā)送數(shù)據(jù)休蟹,要么是客戶端向服務(wù)端發(fā)送數(shù)據(jù)沸枯。這種協(xié)議讓MySQL通信簡單快速。但是也意味著沒法進行流量控制赂弓,一旦一端開始發(fā)送消息,另一端要完整接收完整個消息才能響應(yīng)它哪轿∮客戶端用一個單獨的數(shù)據(jù)包將查詢傳給服務(wù)器,所以查詢語句特別長的時候窃诉,參數(shù)max_allowed_packet特別重要杨耙。
查詢狀態(tài)
最簡單使用SHOW FULL PROCESSLIST查看當(dāng)前狀態(tài),狀態(tài)值有如下幾種:
- Sleep:線程正在等待客戶端發(fā)送新的請求飘痛。
- Query:線程正在執(zhí)行查詢或者將查詢結(jié)果返回客戶端珊膜。
- Locked:服務(wù)器層線程等待表鎖。在存儲引擎基本實現(xiàn)的鎖宣脉,例如InnoDB的行所车柠,不會體現(xiàn)在線程狀態(tài)中。
- Analyzing and statistics:線程收集存儲引擎的統(tǒng)計信息塑猖,并生成查詢的執(zhí)行計劃竹祷。
- Copying to tmp table [on disk]:線程執(zhí)行查詢,并將其結(jié)果集復(fù)制到一個臨時表中羊苟,這種狀態(tài)一般要么是做GROUP BY操作塑陵,或者文件排序操作,或者UNION操作蜡励。如果后面有“on disk”標記表示MySQL將內(nèi)存臨時表放到磁盤上令花。
- Sorting result:線程在對結(jié)果集排序阻桅。
- Sending data:線程可能在多個狀態(tài)之間傳送數(shù)據(jù),或者在生成結(jié)果集兼都,或者在向客戶端返回數(shù)據(jù)嫂沉。
查詢緩存
檢查緩存是通過一個對大小寫敏感的哈希查找實現(xiàn)的。查詢和緩存中的查詢即使只有一個字節(jié)不同頁不會匹配俯抖,如果命中在返回結(jié)果集之前MySQL會檢查一次用戶權(quán)限输瓜,這是無需解析SQL的,因為查詢緩存中有保存當(dāng)前查詢需要的表信息芬萍。
查詢優(yōu)化處理
語法解析器和預(yù)處理
MySQL通過關(guān)鍵字將SQL語句解析尤揣,生成語法解析樹,使用MySQL語法規(guī)則驗證和解析查詢柬祠。例如是否使用了錯誤的關(guān)鍵字北戏,關(guān)鍵字順序是否正確,引號前后是否正確匹配漫蛔。
預(yù)處理根據(jù)MySQL規(guī)則進一步檢查解析樹是否合法嗜愈。例如數(shù)據(jù)表、列是否存在莽龟,名字和別名是否有歧義蠕嫁。
下一步預(yù)處理器會驗證權(quán)限。
查詢優(yōu)化器
語法樹已經(jīng)合法毯盈,優(yōu)化器將其轉(zhuǎn)為了執(zhí)行計劃剃毒。優(yōu)化器作用就是找到最好的執(zhí)行計劃。
可以通過查詢當(dāng)前回話的Last_query_cost的值來得知MySQL計算當(dāng)前查詢成本搂赋。
根據(jù)一系列統(tǒng)計信息計算得來:每個表或者索引的頁面?zhèn)€數(shù)赘阀,索引的基數(shù)(索引中不同值的數(shù)量),索引和數(shù)據(jù)行的長度脑奠,索引分布的情況基公。
優(yōu)化器在評估成本的時候不考慮任何緩存,假設(shè)讀取任何數(shù)據(jù)都需要一次磁盤IO
MySQL的查詢優(yōu)化器是一個復(fù)雜部件宋欺,使用了很多優(yōu)化的執(zhí)行策略擂煞。優(yōu)化策略簡單分為兩種:靜態(tài)優(yōu)化和動態(tài)優(yōu)化宫仗。
靜態(tài)優(yōu)化直接對解析樹進行優(yōu)化,靜態(tài)優(yōu)化在第一次萬能充后就一直有效,使用不同參數(shù)執(zhí)行查詢頁不會發(fā)生變化但骨,可以認為是一種“編譯時優(yōu)化”蜘犁。
動態(tài)優(yōu)化和查詢的上下文有關(guān)姚炕,例如WHERE條件中的取值征唬、索引中條目對應(yīng)的數(shù)據(jù)行數(shù)等》褪剑可以認為時“運行時優(yōu)化”陡厘。
MySQL能夠處理的優(yōu)化類型:
- 重新定義關(guān)聯(lián)表的順序:數(shù)據(jù)表的關(guān)聯(lián)并不總是按照查詢中指定的順序執(zhí)行
- 將外連接轉(zhuǎn)為內(nèi)連接:MySQL識別并重寫查詢抽米,讓其可以調(diào)整關(guān)聯(lián)順序。
- 使用等價變化規(guī)則:通過等價變換來簡化并規(guī)范表達式糙置。合并減少一些比較云茸,一定一些恒等或者恒不等的判斷。
- 優(yōu)化Count() Max() Min():min和max可以直接查詢b-tree的最左或者最右端谤饭。
- 預(yù)估并轉(zhuǎn)化位常數(shù)表達式:
- 覆蓋索引掃描
- 子查詢優(yōu)化标捺;某些情況下可以將子查詢轉(zhuǎn)換為效率更高的形式
- 提前終止查詢:在發(fā)現(xiàn)已經(jīng)滿足查詢需求的時候,MySQL總是能夠立刻終止查詢揉抵。
- 等值傳播:兩個列的值通過等值關(guān)聯(lián)亡容,MySQL能夠傳遞where條件。
- 列表in()的比較:MySQL將in()列表中的數(shù)據(jù)先進行排序冤今,然后通過二分查找的方式來確定列表中的值是否滿足條件闺兢。這是一個O(log n)的操作。等價轉(zhuǎn)換為Or的復(fù)雜度時O(n)戏罢。
MySQL執(zhí)行關(guān)聯(lián)查詢
MySQL先從一個表中循環(huán)取出單條數(shù)據(jù)屋谭,在嵌套循環(huán)到下一個表中尋找匹配的行,依次直到找到所有表中匹配的行龟糕,然后根據(jù)各個表匹配的行返回查詢中需要的各個列桐磁。MySQL會嘗試在最后一個關(guān)聯(lián)表中找到所有匹配的行,如果不行就返回上一層次關(guān)聯(lián)表讲岁。
MySQL多表關(guān)聯(lián)的指令樹時一顆左側(cè)深度優(yōu)先的樹所意。
關(guān)聯(lián)查詢優(yōu)化器
MySQL的最優(yōu)執(zhí)行計劃中的關(guān)聯(lián)表的順序,通過預(yù)估需要讀取的數(shù)據(jù)頁來選擇催首,讀取的數(shù)據(jù)頁越少越好。
關(guān)聯(lián)順序的調(diào)整泄鹏,可能會讓查詢進行更少的嵌套循環(huán)和回溯操作郎任。
可以使用STRAIGHT_JOIN關(guān)鍵字重寫查詢,讓優(yōu)化器按照查詢順序執(zhí)行备籽。
排序優(yōu)化
排序時成本很高的操作舶治,從性能角度考慮,應(yīng)該盡量避免排序车猬,或者避免對大量數(shù)據(jù)進行排序霉猛。
當(dāng)不能用索引生成排序結(jié)果時,MySQL需要字節(jié)進行排序珠闰,如果數(shù)據(jù)量小使用內(nèi)存惜浅,數(shù)據(jù)量大使用磁盤。不過統(tǒng)一都稱為文件排序(filesort)伏嗜。
MySQL有兩種排序算法:
- 兩次傳輸排序(舊版本):讀取指針和需要排序的字段坛悉,排序之后伐厌,再根據(jù)排序結(jié)果讀取所需要的數(shù)據(jù)行。第二次讀取數(shù)據(jù)的時候可能產(chǎn)生大量隨機IOS裸影,成本很高挣轨,不過在排序時加載的數(shù)據(jù)較少,所以在內(nèi)存中就可以讀取更多的行數(shù)進行排序轩猩。
- 單次傳輸排序(新版本):查詢所有需要列卷扮,根據(jù)給定列進行排序直接返回結(jié)果。在MySQL4.1之后引入均践。
查詢執(zhí)行引擎
查詢執(zhí)行階段就根據(jù)執(zhí)行計劃晤锹,調(diào)用存儲引擎的實現(xiàn)接口來完成。
查詢結(jié)果返回時浊猾,即使不需要返回結(jié)果集給客戶端抖甘,MySQL返回查詢信息,例如影響到的行數(shù)葫慎。
查詢優(yōu)化的局限性
關(guān)聯(lián)子查詢(in+子查詢)
使用join衔彻,或者使用函數(shù)GROUP_CONCAT()在in中構(gòu)造一個由分好分隔的列表,有時候比關(guān)聯(lián)更快偷办,in加子查詢性能糟糕艰额,一般建議使用exists等效改寫。
優(yōu)化特定類型的查詢
優(yōu)化count查詢
MyISAM的count函數(shù)非辰费模快柄沮,只有在沒有條件的前提下。
近似值:某些不需要精確值的情況下废岂,可以使用EXPLAIN出來的優(yōu)化器估算行數(shù)祖搓。
優(yōu)化關(guān)聯(lián)查詢
- 確保on或者using子句中的列上有索引。
- 確保任何的group by和order by中的表達式只設(shè)計一個表中的列湖苞,這樣MySQL才有可能使用索引來優(yōu)化過程
優(yōu)化子查詢
在5.6之前盡量轉(zhuǎn)換使用join拯欧,5.6之后沒有太多差別
優(yōu)化group by和distinct
groupby 使用主鍵列效率更高。
優(yōu)化limit
“延遲關(guān)聯(lián)”财骨,首先使用索引覆蓋來選取范圍內(nèi)的主鍵镐作,接下來根據(jù)這些主鍵獲取對應(yīng)數(shù)據(jù)。
分區(qū)表
分區(qū)表限制:
- 一個表最多只能有1024個分區(qū)
- 5.1中分區(qū)表達式必須是整數(shù)隆箩,或者是返回整數(shù)的表達式该贾。5.5中某些場景可以直接使用列進行分區(qū)。
- 如果分區(qū)字段中有主鍵或者唯一索引列捌臊,那么所有的主鍵列和唯一索引列都必須包含進來杨蛋。
- 分區(qū)表中無法使用外鍵約束。
在數(shù)據(jù)量超大的時候B-Tree就無法起作用了,除非是索引覆蓋查詢六荒,否則數(shù)據(jù)庫服務(wù)器需要根據(jù)索引掃描的結(jié)果回表护姆,查詢所有符合條件的記錄。如果數(shù)據(jù)量巨大掏击,這將產(chǎn)生大量隨機IO卵皂,數(shù)據(jù)庫的響應(yīng)時間將大到不可接受的程度。
MySQL優(yōu)化服務(wù)器配置
MySQL配置的工作原理
MySQL從 命令行參數(shù)和配置文件中獲取配置信息砚亭。配置文件一般是在 /etc/my.cnf 或 /etc/mysql/my.cnf灯变。
確認配置文件路徑,可以使用下列命令
$ which mysql
/bin/mysql
$/bin/mysql --verbose --help|grep -A 1 'Default options'
Default options are read from the following files in the given order:
/etc/my.cnf /etc/mysql/my.cnf /usr/etc/my.cnf ~/.my.cnf
配置文件分為多個部分捅膘,每個部分的開頭是用方括號括起來的分段名稱添祸。客戶端會讀取client部分寻仗,服務(wù)器通常讀取mysqld部分刃泌。
配置項都使用小鞋,單次之間用下劃線或者橫線隔開署尤。
常用變量及其效果
- key_buffer_size
一次性為鍵緩沖區(qū)(key buffer)分配所有的指定空間耙替。操作系統(tǒng)會在使用時才真正分配。 - table_cache_size
這個變量會等到下次有線程打開表才有效果曹体,會變更緩存中表的數(shù)量俗扇。 - thread_cache_size
MySQL只有再關(guān)閉連接時才在緩存中增加線程,只在創(chuàng)建新連接時才從緩存中刪除線程箕别。 - query_cache_size
修改這個變量會立刻刪除所有緩存的查詢铜幽,重新分配這片緩存到指定大小,并且重新初始化內(nèi)存串稀。 - read_buffer_size
MySQL只會在查詢需要使用時才會為該緩存分配內(nèi)存除抛,并且一次性分配該參數(shù)指定大小的全部內(nèi)存。 - read_rnd_buffer_size
MySQL只會在查詢需要使用時才會為該緩存分配內(nèi)存母截,并且只會分配該參數(shù)需要大小的內(nèi)存镶殷。 - sort_buffer_size
MySQL只會在查詢排序需要使用時才會為該緩存分配內(nèi)存,并且一次性分配該參數(shù)指定大小的全部內(nèi)存微酬,不管排序是否需要這想·么大的內(nèi)存。
InnoDB事務(wù)日志
InnoDB使用日志來減少提交事務(wù)時的開銷颤陶。因為日志中已經(jīng)記錄了事務(wù)颗管,無需在每個事務(wù)提交時把緩沖池的臟塊刷新到磁盤中。
InnoDB用日志把隨機IO變成順序IO滓走,一旦日志寫入磁盤垦江,事務(wù)就持久化了,即使變更還沒有寫到數(shù)據(jù)文件搅方。
InnoDB最后是要把變更寫入數(shù)據(jù)文件比吭,日志有固定大小绽族。InnoDB的日志是環(huán)形方式寫的:當(dāng)寫到日志的尾部,會重新跳轉(zhuǎn)到開頭繼續(xù)寫衩藤,但不會覆蓋到還沒應(yīng)用到數(shù)據(jù)文件的日志記錄吧慢,因為這樣會清掉已經(jīng)提交事務(wù)的唯一持久化記錄。
InnoDB使用一個后臺線程只能地刷新這些變更到數(shù)據(jù)文件赏表。這個線程可以批量組合寫入检诗,是的數(shù)據(jù)寫入更順序,以提高效率瓢剿。事務(wù)日志把數(shù)據(jù)文件的隨機IO轉(zhuǎn)換為幾乎順序的日志文件和數(shù)據(jù)文件IO逢慌,把刷新操作轉(zhuǎn)移到后臺使得查詢可以更快完成,并且緩和查詢高峰時IO的壓力间狂。
InnoDB表空間
InnoDB把數(shù)據(jù)保存在表空間內(nèi)攻泼,本質(zhì)上是一個由一或多個磁盤文件組成的虛擬文件系統(tǒng)。InnoDB用表空間實現(xiàn)很多功能鉴象,不只是存儲表和索引忙菠。它還保存了回滾日志(舊版本號),插入緩沖(Insert Buffer)炼列、雙寫緩沖(Doublewrite Buffer)只搁,以及其他內(nèi)部數(shù)據(jù)結(jié)構(gòu)。
InnoDB使用雙寫緩沖來避免頁沒寫完整鎖導(dǎo)致的數(shù)據(jù)損壞俭尖。這是一個特殊的保留區(qū)域氢惋,再一些連續(xù)的塊中足夠保存100個頁。本質(zhì)上是一個最近寫回的頁面的備份拷貝稽犁。當(dāng)InnoDB從緩沖池刷新頁面到磁盤時焰望,首先把他們寫到雙寫緩沖,然后再把他們寫到其所屬的數(shù)據(jù)區(qū)域中已亥,可以保證每個頁面的寫入都是原子并且持久化的熊赖。頁面在末尾都有校驗值(Checksum)來確認是否損壞。
InnoDB的多線程
- Master Thread
非常核心的后臺線程虑椎,主要負責(zé)將緩沖池中的數(shù)據(jù)異步刷新到磁盤震鹉,保證數(shù)據(jù)的一致性,包括臟頁的刷新捆姜、合并插入緩沖(INSERT BUFFER)传趾、UNDO頁的回收等。 - IO Thread
InnoDB中大量使用了AIO(Async IO)來處理IO請求泥技,可以極大提高數(shù)據(jù)庫性能浆兰,IO Thread主要是負責(zé)這些IO請求的回調(diào)(call back)處理。InnoDB1.0之前工有4個IO Thread,分別是write簸呈、read榕订、insert buffer、log IO thread蜕便。 - Purge Thread
事務(wù)提交后劫恒,其使用的undolog可能不再需要,因此需要PurgeThread來回收已經(jīng)使用并分配的undo頁玩裙。
InnoDB的內(nèi)存
-
緩沖池
InnoDB基于磁盤存儲兼贸,記錄按照頁的方式進行管理。在數(shù)據(jù)庫中進行讀取頁的操作吃溅,首先將磁盤讀到的頁存放在緩沖池中溶诞,下次讀取先判斷頁是否在緩沖池則直接讀取,否則讀取磁盤上的頁决侈。對頁的修改首先修改緩沖池螺垢,然后再以一定的頻率刷新到磁盤(通過checkpoint機制)。緩沖池配置通過innodb_buffer_pool_size來設(shè)置赖歌。
緩沖池中緩存的數(shù)據(jù)頁類型有:索引頁枉圃、數(shù)據(jù)頁、undo頁庐冯、插入緩沖(insert buffer)孽亲、自適應(yīng)哈希索引(adaptive hash index)、InnoDB存儲的鎖信息(lock info)展父、數(shù)據(jù)字典信息(data dictionary)等
LRU List返劲、Free List和Flush List
InnoDB的LRU添加了midpoint位置,新讀取的頁不是放到首部栖茉,而是放到midpoint位置篮绿。默認是放在LRU列表長度的5/8處。有些操作可能會全表掃描加載大量的頁吕漂,如果直接加載到首部則可能刷出有效頁亲配。數(shù)據(jù)庫開始時,LRU是空的惶凝,頁都在FreeList中吼虎,查找時從Free列表中查找是否有可用空閑頁,若有則從Free列表中刪除放入LRU苍鲜。當(dāng)頁從LRU的old部分假如到new時鲸睛,稱之為page made young,因為innodb_old_blocks_time設(shè)置導(dǎo)致頁沒有從old部分移動到new部分稱為page not made young坡贺。重做日志緩沖(redo log buffer)
三種情況會講redo log buffer中的內(nèi)容刷新到日志文件
- Master Thread每秒刷新一次
- 每個事務(wù)提交時會刷新
- redo log buffer剩余空間小于1/2時
- 額外的內(nèi)存池
在對數(shù)據(jù)庫結(jié)構(gòu)本身的內(nèi)存進行分配的時候,需要從額外的內(nèi)存池進行申請。
Checkpoint技術(shù)
InnoDB存儲引擎內(nèi)部有兩種:
- Sharp Checkpoint
數(shù)據(jù)庫關(guān)閉時將所有臟頁刷回磁盤遍坟,默認工作方式拳亿,參數(shù)innodb_fast_shuthown=1 - Fuzzy Checkpoint
刷新一部分臟頁。(Master Thread Checkpoint愿伴,F(xiàn)LUSH_LRU_LIST Checkpoint肺魁,Async/Sync Flush Checkpoint,Dirty Page too much Checkpoint)
InnoDB關(guān)鍵特性
插入緩沖
- Insert Buffer
對于非聚集索引的插入或者更新操作隔节,不是每一次直接插入到索引頁鹅经,而是先判斷插入的非聚集索引是否在緩沖池中,若在則插入怎诫,若不在則放入到一個Insert Buffer中瘾晃。以一定的頻率進行Insert Buffer和非聚集索引子節(jié)點的合并操作。需要滿足兩個條件:1.索引是輔助索引幻妓。2.索引不是唯一的蹦误。 - Change Buffer
InnoDB 1.0.x開始可以對DML操作進行緩沖 (Insert,Delete肉津,Update)分別是:Insert Buffer强胰,Delete Buffer,Purge Buffer妹沙。
Insert Buffer是一顆B+樹偶洋,全局唯一,負責(zé)對所有表的輔助索引進行Insert Buffer距糖。
Merge Insert Buffer是合并到真正的輔助索引中的操作玄窝,在下面幾種情況時發(fā)生:
- 輔助索引頁被讀取到緩沖池中
- Insert Buffer Bitmap 頁追蹤到該輔助索引頁已經(jīng)沒有空間可用
- Master Thread 觸發(fā)
自適應(yīng)Hash索引(Adaptive Hash Index)
InnoDB 會監(jiān)控各種索引列的查詢,如果判斷建立哈希索引可以提高訪問速度肾筐,則會自動建立哆料。AHI是通過緩沖池的B+樹構(gòu)建而來,不需要對整張表結(jié)構(gòu)建立哈希索引吗铐。有如下要求:
- 以相同模式訪問了100次
- 頁通過該模式訪問了N次:N=頁中記錄*1/16
異步IO
異步IO(Asychronous IO东亦,AIO)
文件
- 參數(shù)文件:
初始化參數(shù)文件 - 日志文件:
例如錯誤日志文件(error log),二進制日志文件(binlog)唬渗,慢查詢?nèi)罩疚募╯low query log)典阵,查詢?nèi)罩疚募╨og) - socket文件:
UNIX域套接字方式進行連接是需要的文件。 - pid文件:
MySQL實例的進程ID文件 - MySQL表結(jié)構(gòu)文件:
用來存放MySQL表結(jié)構(gòu)定義的文件 - 存儲引擎文件:
二進制日志(binlog)
記錄了對MySQL數(shù)據(jù)庫執(zhí)行更改的所有操作镊逝,不包括SELECT和SHOW壮啊。
mysql> mysqlmaster status;
File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
---|---|---|---|---|
binlog.001663 | 5924141 |
mysql> show binlog events in 'binlog.001663' limit 5;
binlog文件名(Log_name) | 日志開始位置(Pos) | 事件類型(Event_type) | 服務(wù)器編號(Server_id) | 日志結(jié)束位置(End_log_pos) | 信息 |
---|---|---|---|---|---|
binlog.001663 | 5878887 | Anonymous_Gtid | 1 | 5878966 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
binlog.001663 | 5878966 | Query | 1 | 5879057 | BEGIN |
binlog.001663 | 5879057 | Table_map | 1 | 5879148 | table_id: 8291 (vv_oaxxljob.XXL_JOB_QRTZ_SCHEDULER_STATE) |
binlog.001663 | 5879148 | Update_rows | 1 | 5879340 | table_id: 8291 flags: STMT_END_F |
binlog.001663 | 5879340 | Xid | 1 | 5879371 | COMMIT /* xid=4800934 */ |
binlog.001663 | 5879371 | Anonymous_Gtid | 1 | 5879450 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
binlog.001663 | 5879450 | Query | 1 | 5879541 | BEGIN |
binlog.001663 | 5879541 | Table_map | 1 | 5879632 | table_id: 8291 (vv_oaxxljob.XXL_JOB_QRTZ_SCHEDULER_STATE) |
binlog.001663 | 5879632 | Update_rows | 1 | 5879824 | table_id: 8291 flags: STMT_END_F |
binlog.001663 | 5879824 | Xid | 1 | 5879855 | COMMIT /* xid=4800956 */ |
binlog.001663 | 5879855 | Anonymous_Gtid | 1 | 5879934 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS' |
binlog.001663 | 5879934 | Query | 1 | 5880025 | BEGIN |
binlog.001663 | 5880025 | Table_map | 1 | 5880116 | table_id: 8291 (vv_oaxxljob.XXL_JOB_QRTZ_SCHEDULER_STATE) |
binlog.001663 | 5880116 | Update_rows | 1 | 5880308 | table_id: 8291 flags: STMT_END_F |
binlog.001663 | 5880308 | Xid | 1 | 5880339 | COMMIT /* xid=4800988 */ |
MySQL5.1引入了binlog_format參數(shù),參數(shù)有STATEMENT撑蒜、ROW歹啼、MIXED三種玄渗。
- STATEMENT
和之前的MySQL版本一樣,二進制日志文件記錄的是日志的邏輯SQL語句狸眼。 - ROW
記錄的是表的行更改情況藤树。如果設(shè)置為ROW,可以將InnoDB事務(wù)隔離設(shè)置為READ COMMITTED獲取更好的并發(fā)性拓萌。 - MIXED
默認使用STATEMENT岁钓,某些情況下使用MIXED。
- 表的存儲引擎為NDB微王,對表的DML操作以ROW格式記錄屡限。
- 使用了UUID() USER() CURRENT_USER() FOUND_ROWS() ROW_COUNT()
- 使用了INSERT DELAY語句
- 使用了用戶自定義函數(shù)
- 使用了臨時表
要查看binlog日志文件的內(nèi)容,必須使用MySQL提供的工具mysqlbinlog炕倘。
表結(jié)構(gòu)定義文件
MySQL定義了frm為后綴名的文件钧大,記錄了表結(jié)構(gòu)(視圖)定義。
InnoDB存儲引擎文件
表空間文件(tablespace file)
默認有一個初始大小為10MB激才,名為ibdata1的文件
重做日志文件(redo log file)
默認情況下會有 ib_logfile0和ib_logfile1作為 redo log file 拓型。每個InnoDB至少有一個重做日志文件組(group),文件組下有兩個重做日志文件瘸恼,用戶可以設(shè)置多個鏡像日志組(mirrored log groups)
表
索引組織表(index organized table)
MySQL默認創(chuàng)建一個6字節(jié)大小的指針(_rowid)
InnoDB邏輯存儲結(jié)構(gòu)
所有的數(shù)據(jù)都被邏輯地存放在一個空間內(nèi)劣挫,稱之為表空間(tablespace),表空間又由段(segment)东帅,區(qū)(extent)压固、頁(page)組成,頁在某些文檔中也成為塊(block)
表空間
如果啟用了 innodb_file_per_table的參數(shù)靠闭,每張表的數(shù)據(jù)可以單獨放到一個表空間內(nèi) 帐我,其中存放的是數(shù)據(jù)、索引愧膀、和插入緩沖Bitmap頁拦键,其他類的數(shù)據(jù)如回滾(undo)信息、插入緩沖索引頁檩淋、系統(tǒng)事務(wù)信息芬为、二次寫緩沖還是放在原本的共享表空間。
段
表空間是由各個段組成的蟀悦,包括數(shù)據(jù)段媚朦、索引段、回滾段等日戈。數(shù)據(jù)段就是B+樹的葉子節(jié)點(Leaf node segment)询张,索引段即B+樹的非索引節(jié)點(Non-leaf node segment)。
區(qū)
區(qū)是連續(xù)頁組成的空間浙炼,任何情況下每個區(qū)的大小都為1MB份氧,為了保證區(qū)中頁的連續(xù)性唯袄,InnoDB一次從磁盤申請4-5個區(qū),默認情況頁大小為16KB蜗帜,一個區(qū)中一共有64個連續(xù)的頁越妈。InnoDB1.0.x引入壓縮頁,每個頁的大小可以通過key_block_size設(shè)置為2k钮糖、4k、8k酌住。1.2.x版本新增了參數(shù)innodb_page_size店归,通過該參數(shù)可以將默認頁的大小設(shè)置為4k、8k酪我。
頁
InnoDB中常見的頁類型有:
- 數(shù)據(jù)頁(B-tree Node)
- Undo頁(Undo Log Page)
- 系統(tǒng)頁(System Page)
- 事務(wù)數(shù)據(jù)頁(Transaction system Page)
- 插入緩沖頁位圖(Insert Buffer Bitmap)
- 插入緩沖空閑列表頁(Insert Buffer Free List)
- 未壓縮的二進制大對象頁(Uncompressed BLOB Page)
行
MySQL的存儲是面向列的(row-oriented),數(shù)據(jù)是按行存儲的消痛。頁存放的記錄有硬性定義最多存放16KB/2 - 200行,即7992行都哭。
InnoDB數(shù)據(jù)頁結(jié)構(gòu)
數(shù)據(jù)頁由下面7個部分組成:
- File Header(文件頭)固定
- Page Header(頁頭)固定
- Infimun 和 Supremun Record 固定
頁中兩個虛擬的行記錄秩伞,Infimun是指比頁中任何主鍵更小的值,Supremun指比任何值都大的值欺矫,這兩個值在頁創(chuàng)建的時候創(chuàng)建纱新,在任何時候情況下都不會刪除。 - User Record(用戶記錄穆趴,即行記錄)
存儲實際記錄脸爱,B+樹索引組織。 - Free Space(空閑空間)
空閑空間未妹,鏈表數(shù)據(jù)結(jié)構(gòu)簿废。一條記錄被刪除后會放到空閑空間。 - Page Directory(頁目錄)
存放了記錄的相對位置络它,這些記錄指針稱之為槽(slots)或者目錄槽(dictionary slots)族檬,稀疏目錄,可能包含多條記錄化戳。
B+樹索引不能找到實際的記錄单料,而是找到記錄的頁。 -
File Trailer(文件結(jié)尾信息)
檢測頁是否完整寫入了磁盤迂烁,checksum值看尼。
行溢出數(shù)據(jù)
InnoDB會將一條記錄中的某些列存儲在真正的數(shù)據(jù)列之外,BLOB盟步,LOB字段可能不一定會將字段放在溢出頁面藏斩,VARCHAR也有可能會放進溢出頁面。
Oracle VarCHAR2最多存放4000字節(jié)却盘,MSSQL最多8000字節(jié)狰域,MySQL最多65535(存在其他開銷媳拴,最長65532)。當(dāng)發(fā)生行溢出時兆览,數(shù)據(jù)存放在頁類型Uncompress BLOB頁面屈溉。數(shù)據(jù)頁只保存數(shù)據(jù)的前768字節(jié)。
鎖
lock與latch
latch一般稱為閂鎖抬探,輕量級子巾,要求鎖定的時間非常短。在InnoDB中小压,分為mutex(互斥量)與rwlock(讀寫鎖)线梗。用來保證并發(fā)線程操作臨界資源的正確性,并且通常沒有死鎖檢測的機制怠益。
lock的對象是事務(wù)仪搔,用來鎖定的是數(shù)據(jù)庫中的對象,如表蜻牢、頁烤咧、行。在commit或者rollback之后釋放抢呆,有死鎖檢測機制煮嫌。
鎖的類型
- 共享鎖(S Lock):允許事務(wù)讀一行數(shù)據(jù)
- 排他鎖(X Lock):允許事務(wù)更新或刪除一行數(shù)據(jù)
上述兩種都是悲觀鎖,樂觀鎖就是CAS(Compare and Swap)
一致性非鎖定讀(consistent nonlocking read)
是指InnoDB通過MVCC(Multi Version Concurrency Control)讀取數(shù)據(jù)庫當(dāng)前行的方式镀娶。如果讀取的行正在進行update或者delete操作立膛,則讀取一個快照。在Read Committed和Repeatedable Read中使用梯码。前者讀取最新的快照宝泵,后者使用事務(wù)開始時的快照。
一致性鎖定讀(locking read)
也可以顯式的對讀取加鎖轩娶,有兩種操作:
- select ... for update(加一個X鎖)
- select ... lock in share mode(加一個S鎖)
行鎖的3種算法
- Record Lock:單個行記錄的鎖
- Gap Lock:鎖定一個范圍儿奶,不包括記錄本身
- Next-Key Lock:Gap+Record,鎖定范圍以及記錄本身鳄抒。用來解決幻影相關(guān)問題(Phantom)
針對的是索引的區(qū)間闯捎,但是當(dāng)查詢條件指定唯一索引值(只針對主鍵索引/聚集索引)時,會降級為Record Lock许溅,若是二級索引則不會瓤鼻。而且InnoDB還會對二級索引的下一個鍵值加上Gap Lock。
例如贤重,二級索引b列有1茬祷,3,6并蝗,9祭犯。當(dāng)使用X鎖鎖定3時(where b<=3 for update)秸妥,會NKL鎖定了范圍(1-3),同時會使用GL鎖定下一個鍵值(3-6)沃粗。
利用這個機制可以用一個事務(wù)粥惧,首先select id from t where col=xxx lock in share mode,接下來insert t (col) values (xxx)最盅,能夠保證一定插入不存在的值突雪。
死鎖
兩個事務(wù)執(zhí)行時,因爭奪鎖資源互相等待的場景涡贱。
解決死鎖最簡單的就是超時挂签,通過innodb_lock_wait_timeout控制超時時間。
當(dāng)前普遍使用的是wait-for graph(主動檢測的方式)盼产,這要求數(shù)據(jù)庫保存兩種信息:
- 鎖的信息鏈表
- 事務(wù)的等待列表
通過上述信息,可以在事務(wù)請求鎖并發(fā)生等待時都進行判斷勺馆,在上述兩個信息構(gòu)造的圖中是否存在回路戏售,如果存在就表示存在死鎖。
采用深度優(yōu)先算法實現(xiàn)草穆,InnoDB1.2之前采用遞歸方式灌灾,之后采用非遞歸提高了性能。
事務(wù)的實現(xiàn)(ACID)
事務(wù)的隔離性由鎖來實現(xiàn)悲柱,redo log(重做日志)保證事務(wù)的原子性和持久性锋喜,undo log()保證事務(wù)的一致性。
redo恢復(fù)提交事務(wù)修改的頁操作豌鸡,是物理日志嘿般,記錄的是頁的物理修改操作。
undo回滾某個行記錄到特定版本涯冠,是邏輯日志炉奴,記錄的是行的修改記錄。
redo
存在 redo log buffer和redo log file蛇更,buffer寫入file時需要調(diào)用fsync操作瞻赶,此操作取決于磁盤性能,決定了事務(wù)提交的性能也就是數(shù)據(jù)庫的性能派任。
UNIX的寫操作
一般情況下砸逊,對硬盤(或者其他持久存儲設(shè)備)文件的write操作,更新的只是內(nèi)存中的頁緩存(page cache)掌逛,而臟頁面不會立即更新到硬盤中师逸,而是由操作系統(tǒng)統(tǒng)一調(diào)度,如由專門的flusher內(nèi)核線程在滿足一定條件時(如一定時間間隔颤诀、內(nèi)存中的臟頁達到一定比例)內(nèi)將臟頁面同步到硬盤上(放入設(shè)備的IO請求隊列)字旭。
因為write調(diào)用不會等到硬盤IO完成之后才返回对湃,因此如果OS在write調(diào)用之后、硬盤同步之前崩潰遗淳,則數(shù)據(jù)可能丟失拍柒。雖然這樣的時間窗口很小,但是對于需要保證事務(wù)的持久化(durability)和一致性(consistency)的數(shù)據(jù)庫程序來說屈暗,write()所提供的“松散的異步語義”是不夠的拆讯,通常需要OS提供的同步IO(synchronized-IO)原語來保證
fsync的功能是確保文件fd所有已修改的內(nèi)容已經(jīng)正確同步到硬盤上,該調(diào)用會阻塞等待直到設(shè)備報告IO完成养叛。除了同步文件的修改內(nèi)容(臟頁)种呐,fsync還會同步文件的描述信息(metadata,包括size弃甥、訪問時間st_atime & st_mtime等等)爽室,因為文件的數(shù)據(jù)和metadata通常存在硬盤的不同地方,因此fsync至少需要兩次IO寫操作
undo
delete和update操作產(chǎn)生的刪除語句并不是馬上執(zhí)行淆攻,而是將delete_flag標記為1阔墩,最后有purge操作來統(tǒng)一完成。用undo log來執(zhí)行瓶珊,執(zhí)行之后的空間不會回收啸箫,只會用于重用。