天天看點

高性能mysql釋出時間_高性能MySQL分析

Schema與資料類型優化

選擇優化的資料類型

有幾個簡單的原則:

更小的通常更好

一般情況下使用可以正确存儲資料的最小資料類型。

簡單的更好

例如整型比字元操作代價更低。應當使用Mysql的日期類型而不是字元串,應當用整型存儲IP位址

盡量避免NULL

查詢中如果包含NULL的列,對于Mysql來說更難優化,這樣使得索引,索引統計,值都比較複雜。NULL的列會使用更多的存儲空間,在Mysql裡也需要特殊處理。當可為NULL的列被索引時,每個索引記錄需要一個額外的位元組

整數類型

無符号的數字上限可以提高一倍

為整數類型指定寬度,如INT(11),不會限制值的合法範圍,隻是規定了Mysql的一些互動工具(指令行或用戶端)用來顯示的字元個數。對于存儲和計算來講,int(1) 和 int(20)是相同的。

實數類型

浮點類型在存儲同樣範圍的值時,通常比Decimal使用更少的空間,Float使用4個位元組,Double使用8個位元組相比Float有更高的精度和更大的範圍。這裡能選擇的是存儲類型,Mysql内部使用Double作為内部浮點計算的類型。

字元串類型

CHAR和VARCHAR

VARCHAR節省了存儲空間,如果行占用存儲空間增長,并且在頁内沒有更多的空間存儲,MyISAM拆成不同的片段存儲,InnoDB則需要分裂頁來使行可以放進頁内。

下列情況使用Varchar是合适的:

字元串最大長度比平均長度大很多;

列的更新很少,是以碎片不是問題;

使用了UTF-8字元集,每個字元都使用不同的位元組數進行存儲。

InnoDB把過長的VARCHAR存儲為BLOB

CHAR是定長的,會删除末尾的空格。CHAR(1)需要一個位元組,VARCHAR(1)需要2個位元組,因為還需要多一個位元組存儲長度。

類似的還有BINNARY和VARBINARY,填充使用的\0(0位元組)

BLOB和TEXT

都是為了存儲很大的資料設計的字元串資料類型,分别采用二進制和字元方式存儲。不同在于BLOB存儲的是二進制資料,沒有排序規則或者字元集。

排序也隻是對每個列的max_sort_length位元組而不是整個字元串排序。

查詢如果涉及BLOB,伺服器不能在記憶體臨時表中存儲BLOB,必須要使用磁盤臨時表,無論它多小。

日期和時間類型

DATETIME可以存儲1001到9999年,精度為秒,與時區無關,使用8個位元組的存儲空間。TIMESTAMP儲存了1970年1月1日以來的秒數。隻使用4個位元組的存儲空間。從1970到2038年。

位資料類型

這些類型,不管底層存儲格式和處理方式如何,從技術上來說都是字元串類型。

BIT

5.0之前BIT是TINYINT的同義詞。之後則完全不同。MyISAM會打包所有的BIT列,InnoDB和Memory使用足夠存儲最小整數類型來存放BIT,是以不能節省存儲空間。Mysql把BIT當作字元串類型而不是數字,會造成一些混亂。例如 a bit(8),值為b'00111001'二進制等于57(ascii顯示值等于9),a=9,a+0=57。應該謹慎使用,如果想存儲true/false,可以使用CHAR(0)

選擇辨別符(identifier)

整數類型是最好的選擇,很快并且可以使用AUTO_INCREMENT。避免使用字元串作為辨別列,很耗空間,通常比數字類型慢,MyISAM預設對字元串使用壓縮索引,會導緻查詢慢很多。

随機值如MD5,SHA1,UUID會導緻INSERT和一些SELECT語句變慢,因為可能導緻随機寫入索引不同位置,導緻頁分裂,磁盤随機通路,對于聚簇存儲引擎産生聚簇索引碎片。

SELECT語句變慢因為邏輯上相鄰的行會分布在磁盤和記憶體的不同地方。

随機值導緻緩存對所有類型的查詢語句效果都很差。

Scheme設計中的陷阱

太多的列

Mysql的存儲引擎API工作時需要在伺服器層和存儲引擎層之間通過行緩沖格式拷貝資料,然後在伺服器層将緩沖内容解碼成各個列。從行緩沖中将編碼過的列轉換成行資料結構的操作代價是非常高的。非常寬的表可能會使得CPU占用非常高。

太多的關聯

“實體-屬性-值”(EAV)設計模式在Mysql下不能靠譜的工作,限制了每個關聯操作最多隻能有61張表。單個查詢最好在12個表内做關聯。

全能的枚舉

枚舉清單增加資料需要使用到ALTER TABLE,若不是加在最後可能會有影響

變相的枚舉

範式和反範式

在範式化的資料庫中,每個事實資料會出現并且隻出現一次,相反,在反範式化的資料庫中,資訊是備援的。

第一範式

確定資料表中每列(字段)的原子性。

如果資料表中每個字段都是不可再分的最小資料單元,則滿足第一範式。

例如:user使用者表,包含字段id,username,password

第二範式

在第一範式的基礎上更進一步,目标是確定表中的每列都和主鍵相關。

如果一個關系滿足第一範式,并且除了主鍵之外的其他列,都依賴于該主鍵,則滿足第二範式。

例如:一個使用者隻有一種角色,而一個角色對應多個使用者。則可以按如下方式建立資料表關系,使其滿足第二範式。

user使用者表,字段id,username,password,role_id

role角色表,字段id,name

使用者表通過角色id(role_id)來關聯角色表

第三範式

在第二範式的基礎上更進一步,目标是確定表中的列都和主鍵直接相關,而不是間接相關。

例如:一個使用者可以對應多個角色,一個角色也可以對應多個使用者。則可以按如下方式建立資料表關系,使其滿足第三範式。

user使用者表,字段id,username,password

role角色表,字段id,name

user_role使用者-角色中間表,id,user_id,role_id

像這樣,通過第三張表(中間表)來建立使用者表和角色表之間的關系,同時又符合範式化的原則,就可以稱為第三範式。

反範式化

反範式化指的是通過增加備援或重複的資料來提高資料庫的讀性能。

例如:在上例中的user_role使用者-角色中間表增加字段role_name。

反範式化可以減少關聯查詢時,join表的次數。

範式的優點

範式化的更新操作更快

更新需要變更的資料更少

表比較小,可以更好放在記憶體裡

缺點是通常需要關聯,代價相對昂貴,也可能使得一些索引政策無效。

反範式的優點

避免關聯

查詢相對高效(當索引合理)

建立高性能索引

索引可以包含一個或多個列,如果索引包含多個列,那列的順序也十分重要,因為Mysql隻能最高效的使用索引的最左字首列。

B-Tree的索引列是順序組織存儲的,很适合查找範圍資料。适用于全鍵值、鍵值範圍或鍵字首查找。

紅黑樹是一種含有紅黑結點并能自平衡的二叉查找樹。它必須滿足下面性質:

性質1:每個節點要麼是黑色,要麼是紅色。

性質2:根節點是黑色。

性質3:每個葉子節點(NIL)是黑色。

性質4:每個紅色結點的兩個子結點一定都是黑色。

性質5:任意一結點到每個葉子結點的路徑都包含數量相同的黑結點。

從性質5又可以推出:

性質5.1:如果一個結點存在黑子結點,那麼該結點肯定有兩個子結點

哈希索引(hash index)隻有精确比對索引所有列的查詢才有效。隻包含哈希值和行指針,不存儲字段值,是以不能避免讀取行。

并不是按照索引值順序存儲,是以無法用于排序。

也不支援部分索引列比對查找。隻支援等值查詢,不支援範圍查詢。

高性能的索引政策

獨立的列才能使用到索引,列不能使用操作符或者表達式

多列索引,當使用到多個單列索引時,會進行多個索引的聯合操作(索引合并)

選擇合适的索引列順序

正确的順序依賴于使用該索引的查詢,并且同時需要考慮如何更好地滿足排序和分組的需要。

在一個多列B-Tree索引中,索引列的順序意味着索引首先按照最左列進行排序,其次是第二列。

聚簇索引

并非一種單獨的索引類型,而是一種資料存儲方式。InnoDB在同一個結構中儲存了B-Tree索引和資料行。

InnoDB使用主鍵聚集資料,如果沒有定義主鍵,會選擇一個唯一的非空索引代替,如果沒有這樣的索引,會隐式定義一個主鍵作為聚簇索引。InnoDB隻聚集同一個頁面的記錄。

優點:

把相關資料儲存再一起。

資料通路更快

使用覆寫索引掃描的查詢可以直接使用節點中的主鍵值。

缺點:

插入速度依賴于插入順序,如果不是按照主鍵加載資料,加載完成後最好使用OPTIMIZE TABLE重新組織表

更新聚簇索引的代價很高,因為會将被更新的行移動到新位置

插入新航或者主鍵更新需要移動行時,可能面臨“頁分裂(Page Split)”問題

可能導緻全表掃描變慢,尤其是行比較稀疏

二級索引(非聚簇索引)可能比想象的要更大,因為葉子節點包含了引用行的主鍵列。

二級索引需要兩次索引查找,而不是一次

覆寫索引

如果索引的葉子節點中已經包含要查詢的資料,那麼還有什麼必要再回表查詢呢?是以一個索引包含(或者覆寫)所有需要查詢的字段的值,我們就稱之為覆寫索引。

索引排序

隻有索引的列順序和orderby的順序完全一緻,并且列的正序,逆序都一樣時,才能使用索引對結果進行排序。如果查詢需要關聯多張表,則隻有當orderby的引用字段全部為第一個表時,才能使用索引進行排序。

索引和資料的碎片化

B-Tree索引可能會碎片化。

表的資料存儲也可能碎片化:

行碎片

這種碎片指的時資料行被存儲到多個地方的多個片段中。即使隻查詢一行記錄,也會導緻性能下降。

行間碎片

邏輯上順序的頁,或者行再磁盤上不是順序存儲的。行間碎片對諸如全表掃描和聚簇索引掃描之類的操作有很大影響。

剩餘空間碎片

指資料頁中有大量的空餘空間,會導緻伺服器讀取大量不需要的資料造成浪費。

查詢性能優化

查詢的聲明周期大緻按照順序:

從用戶端,到伺服器,然後在伺服器上進行解析,生成執行計劃,執行,并傳回結果給用戶端。執行時最重要的階段,包含了大量為檢索資料到存儲引擎的調用以及調用後的資料處理,包括排序,分組等。

慢查詢基礎:優化資料通路

是否請求了不需要的資料

查詢不需要的記錄

查詢不需要的列 (多表關聯 * )

總是取出全部列(select * )

重複查詢相同的資料

是否在掃描額外的記錄

衡量查詢開銷的三個名額如下:

響應時間

掃描的行數

傳回的行數

響應時間是 服務時間 和 排隊時間 之和。

掃描的行數和傳回的行數理想情況下應該是相同的,一般在1:1到10:1之間

掃描的行數和通路類型:在EXPAIN語句中的type列反應了通路類型。通路類型有很多中,包括全表掃描,索引掃描,範圍掃描,唯一索引查詢,常數引用等。這些速度是從慢到快,掃描行數也是從多到少。

重構查詢的方式

一個複雜查詢還是多個簡單查詢

Mysql支援多個簡單查詢,一個通用伺服器上可以支援每秒10萬的查詢,一個千兆網卡滿足每秒2000次的查詢。Mysql内部每秒能掃描記憶體中上百萬行資料,相比之下響應資料給用戶端就慢得多了

切分查詢

将一個大查詢分而治之,例如一個删除大量資料的語句,拆分為多個小的删除。

分解關聯查詢

有很多好處:

讓緩存的效率更高。無論是應用程式的緩存和Mysql的緩存,都會在單表的情況下更容易命中。

查詢分解後減少了鎖的競争

應用層關聯,更容易對資料庫進行拆分,做到高性能和可擴充

減少備援記錄的查詢

在應用中實作的哈希關聯,而不是使用Mysql的嵌套查詢。

執行查詢的基礎

執行查詢的過程:

用戶端發送一條查詢給伺服器

伺服器先檢查緩存,如果命中了緩存,則立刻傳回存儲在緩存中的結果。否則進入下一個階段。

伺服器進行SQL解析,預處理,再由優化器生成對應的執行計劃。

Mysql根據優化器生成的執行計劃,調用存儲引擎的API執行查詢。

将結果傳回給用戶端

Mysql用戶端/伺服器通信協定

通信協定是“半雙工”的,意味着任何一個時刻,要麼是服務端向用戶端發送資料,要麼是用戶端向服務端發送資料。這種協定讓MySQL通信簡單快速。但是也意味着沒法進行流量控制,一旦一端開始發送消息,另一端要完整接收完整個消息才能響應它。用戶端用一個單獨的資料包将查詢傳給伺服器,是以查詢語句特别長的時候,參數max_allowed_packet特别重要。

查詢狀态

最簡單使用SHOW FULL PROCESSLIST檢視目前狀态,狀态值有如下幾種:

Sleep:線程正在等待用戶端發送新的請求。

Query:線程正在執行查詢或者将查詢結果傳回用戶端。

Locked:伺服器層線程等待表鎖。在存儲引擎基本實作的鎖,例如InnoDB的行所,不會展現線上程狀态中。

Analyzing and statistics:線程收集存儲引擎的統計資訊,并生成查詢的執行計劃。

Copying to tmp table [on disk]:線程執行查詢,并将其結果集複制到一個臨時表中,這種狀态一般要麼是做GROUP BY操作,或者檔案排序操作,或者UNION操作。如果後面有“on disk”标記表示MySQL将記憶體臨時表放到磁盤上。

Sorting result:線程在對結果集排序。

Sending data:線程可能在多個狀态之間傳送資料,或者在生成結果集,或者在向用戶端傳回資料。

查詢緩存

檢查緩存是通過一個對大小寫敏感的哈希查找實作的。查詢和緩存中的查詢即使隻有一個位元組不同頁不會比對,如果命中在傳回結果集之前MySQL會檢查一次使用者權限,這是無需解析SQL的,因為查詢緩存中有儲存目前查詢需要的表資訊。

查詢優化處理

文法解析器和預處理

MySQL通過關鍵字将SQL語句解析,生成文法解析樹,使用MySQL文法規則驗證和解析查詢。例如是否使用了錯誤的關鍵字,關鍵字順序是否正确,引号前後是否正确比對。

預處理根據MySQL規則進一步檢查解析樹是否合法。例如資料表、列是否存在,名字和别名是否有歧義。

下一步預處理器會驗證權限。

查詢優化器

文法樹已經合法,優化器将其轉為了執行計劃。優化器作用就是找到最好的執行計劃。

可以通過查詢目前回話的Last_query_cost的值來得知MySQL計算目前查詢成本。

根據一系列統計資訊計算得來:每個表或者索引的頁面個數,索引的基數(索引中不同值的數量),索引和資料行的長度,索引分布的情況。

優化器在評估成本的時候不考慮任何緩存,假設讀取任何資料都需要一次磁盤IO

MySQL的查詢優化器是一個複雜部件,使用了很多優化的執行政策。優化政策簡單分為兩種:靜态優化和動态優化。

靜态優化直接對解析樹進行優化,靜态優化在第一次萬能充後就一直有效,使用不同參數執行查詢頁不會發生變化,可以認為是一種“編譯時優化”。

動态優化和查詢的上下文有關,例如WHERE條件中的取值、索引中條目對應的資料行數等。可以認為時“運作時優化”。

MySQL能夠處理的優化類型:

重新定義關聯表的順序:資料表的關聯并不總是按照查詢中指定的順序執行

将外連接配接轉為内連接配接:MySQL識别并重寫查詢,讓其可以調整關聯順序。

使用等價變化規則:通過等價變換來簡化并規範表達式。合并減少一些比較,一定一些恒等或者恒不等的判斷。

優化Count() Max() Min():min和max可以直接查詢b-tree的最左或者最右端。

預估并轉化位常數表達式:

覆寫索引掃描

子查詢優化;某些情況下可以将子查詢轉換為效率更高的形式

提前終止查詢:在發現已經滿足查詢需求的時候,MySQL總是能夠立刻終止查詢。

等值傳播:兩個列的值通過等值關聯,MySQL能夠傳遞where條件。

清單in()的比較:MySQL将in()清單中的資料先進行排序,然後通過二分查找的方式來确定清單中的值是否滿足條件。這是一個O(log n)的操作。等價轉換為Or的複雜度時O(n)。

MySQL執行關聯查詢

MySQL先從一個表中循環取出單條資料,在嵌套循環到下一個表中尋找比對的行,依次直到找到所有表中比對的行,然後根據各個表比對的行傳回查詢中需要的各個列。MySQL會嘗試在最後一個關聯表中找到所有比對的行,如果不行就傳回上一層次關聯表。

MySQL多表關聯的指令樹時一顆左側深度優先的樹。

關聯查詢優化器

MySQL的最優執行計劃中的關聯表的順序,通過預估需要讀取的資料頁來選擇,讀取的資料頁越少越好。

關聯順序的調整,可能會讓查詢進行更少的嵌套循環和回溯操作。

可以使用STRAIGHT_JOIN關鍵字重寫查詢,讓優化器按照查詢順序執行。

排序優化

排序時成本很高的操作,從性能角度考慮,應該盡量避免排序,或者避免對大量資料進行排序。

當不能用索引生成排序結果時,MySQL需要位元組進行排序,如果資料量小使用記憶體,資料量大使用磁盤。不過統一都稱為檔案排序(filesort)。

MySQL有兩種排序算法:

兩次傳輸排序(舊版本):讀取指針和需要排序的字段,排序之後,再根據排序結果讀取所需要的資料行。第二次讀取資料的時候可能産生大量随機IOS,成本很高,不過在排序時加載的資料較少,是以在記憶體中就可以讀取更多的行數進行排序。

單次傳輸排序(新版本):查詢所有需要列,根據給定列進行排序直接傳回結果。在MySQL4.1之後引入。

查詢執行引擎

查詢執行階段就根據執行計劃,調用存儲引擎的實作接口來完成。

查詢結果傳回時,即使不需要傳回結果集給用戶端,MySQL傳回查詢資訊,例如影響到的行數。

查詢優化的局限性

關聯子查詢(in+子查詢)

使用join,或者使用函數GROUP_CONCAT()在in中構造一個由分好分隔的清單,有時候比關聯更快,in加子查詢性能糟糕,一般建議使用exists等效改寫。

優化特定類型的查詢

優化count查詢

MyISAM的count函數非常快,隻有在沒有條件的前提下。

近似值:某些不需要精确值的情況下,可以使用EXPLAIN出來的優化器估算行數。

優化關聯查詢

確定on或者using子句中的列上有索引。

確定任何的group by和order by中的表達式隻設計一個表中的列,這樣MySQL才有可能使用索引來優化過程

優化子查詢

在5.6之前盡量轉換使用join,5.6之後沒有太多差别

優化group by和distinct

groupby 使用主鍵列效率更高。

優化limit

“延遲關聯”,首先使用索引覆寫來選取範圍内的主鍵,接下來根據這些主鍵擷取對應資料。

分區表

分區表限制:

一個表最多隻能有1024個分區

5.1中分區表達式必須是整數,或者是傳回整數的表達式。5.5中某些場景可以直接使用列進行分區。

如果分區字段中有主鍵或者唯一索引列,那麼所有的主鍵列和唯一索引列都必須包含進來。

分區表中無法使用外鍵限制。

在資料量超大的時候B-Tree就無法起作用了,除非是索引覆寫查詢,否則資料庫伺服器需要根據索引掃描的結果回表,查詢所有符合條件的記錄。如果資料量巨大,這将産生大量随機IO,資料庫的響應時間将大到不可接受的程度。

MySQL優化伺服器配置

MySQL配置的工作原理

MySQL從 指令行參數和配置檔案中擷取配置資訊。配置檔案一般是在 /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部分,伺服器通常讀取mysqld部分。

配置項都使用小鞋,單次之間用下劃線或者橫線隔開。

常用變量及其效果

key_buffer_size

一次性為鍵緩沖區(key buffer)配置設定所有的指定空間。作業系統會在使用時才真正配置設定。

table_cache_size

這個變量會等到下次有線程打開表才有效果,會變更緩存中表的數量。

thread_cache_size

MySQL隻有再關閉連接配接時才在緩存中增加線程,隻在建立新連接配接時才從緩存中删除線程。

query_cache_size

修改這個變量會立刻删除所有緩存的查詢,重新配置設定這片緩存到指定大小,并且重新初始化記憶體。

read_buffer_size

MySQL隻會在查詢需要使用時才會為該緩存配置設定記憶體,并且一次性配置設定該參數指定大小的全部記憶體。

read_rnd_buffer_size

MySQL隻會在查詢需要使用時才會為該緩存配置設定記憶體,并且隻會配置設定該參數需要大小的記憶體。

sort_buffer_size

MySQL隻會在查詢排序需要使用時才會為該緩存配置設定記憶體,并且一次性配置設定該參數指定大小的全部記憶體,不管排序是否需要這想·麼大的記憶體。

InnoDB事務日志

InnoDB使用日志來減少送出事務時的開銷。因為日志中已經記錄了事務,無需在每個事務送出時把緩沖池的髒塊重新整理到磁盤中。

InnoDB用日志把随機IO變成順序IO,一旦日志寫入磁盤,事務就持久化了,即使變更還沒有寫到資料檔案。

InnoDB最後是要把變更寫入資料檔案,日志有固定大小。InnoDB的日志是環形方式寫的:當寫到日志的尾部,會重新跳轉到開頭繼續寫,但不會覆寫到還沒應用到資料檔案的日志記錄,因為這樣會清掉已經送出事務的唯一持久化記錄。

InnoDB使用一個背景線程隻能地重新整理這些變更到資料檔案。這個線程可以批量組合寫入,是的資料寫入更順序,以提高效率。事務日志把資料檔案的随機IO轉換為幾乎順序的日志檔案和資料檔案IO,把重新整理操作轉移到背景使得查詢可以更快完成,并且緩和查詢高峰時IO的壓力。

InnoDB表空間

InnoDB把資料儲存在表空間内,本質上是一個由一或多個磁盤檔案組成的虛拟檔案系統。InnoDB用表空間實作很多功能,不隻是存儲表和索引。它還儲存了復原日志(舊版本号),插入緩沖(Insert Buffer)、雙寫緩沖(Doublewrite Buffer),以及其他内部資料結構。

InnoDB使用雙寫緩沖來避免頁沒寫完整鎖導緻的資料損壞。這是一個特殊的保留區域,再一些連續的塊中足夠儲存100個頁。本質上是一個最近寫回的頁面的備份拷貝。當InnoDB從緩沖池重新整理頁面到磁盤時,首先把他們寫到雙寫緩沖,然後再把他們寫到其所屬的資料區域中,可以保證每個頁面的寫入都是原子并且持久化的。頁面在末尾都有校驗值(Checksum)來确認是否損壞。

InnoDB的多線程

Master Thread

非常核心的背景線程,主要負責将緩沖池中的資料異步重新整理到磁盤,保證資料的一緻性,包括髒頁的重新整理、合并插入緩沖(INSERT BUFFER)、UNDO頁的回收等。

IO Thread

InnoDB中大量使用了AIO(Async IO)來處理IO請求,可以極大提高資料庫性能,IO Thread主要是負責這些IO請求的回調(call back)處理。InnoDB1.0之前工有4個IO Thread,分别是write、read、insert buffer、log IO thread。

Purge Thread

事務送出後,其使用的undolog可能不再需要,是以需要PurgeThread來回收已經使用并配置設定的undo頁。

InnoDB的記憶體

緩沖池

InnoDB基于磁盤存儲,記錄按照頁的方式進行管理。在資料庫中進行讀取頁的操作,首先将磁盤讀到的頁存放在緩沖池中,下次讀取先判斷頁是否在緩沖池則直接讀取,否則讀取磁盤上的頁。對頁的修改首先修改緩沖池,然後再以一定的頻率重新整理到磁盤(通過checkpoint機制)。緩沖池配置通過innodb_buffer_pool_size來設定。

緩沖池中緩存的資料頁類型有:索引頁、資料頁、undo頁、插入緩沖(insert buffer)、自适應哈希索引(adaptive hash index)、InnoDB存儲的鎖資訊(lock info)、資料字典資訊(data dictionary)等

高性能mysql釋出時間_高性能MySQL分析

LRU List、Free List和Flush List

InnoDB的LRU添加了midpoint位置,新讀取的頁不是放到首部,而是放到midpoint位置。預設是放在LRU清單長度的5/8處。有些操作可能會全表掃描加載大量的頁,如果直接加載到首部則可能刷出有效頁。資料庫開始時,LRU是空的,頁都在FreeList中,查找時從Free清單中查找是否有可用空閑頁,若有則從Free清單中删除放入LRU。當頁從LRU的old部分假如到new時,稱之為page made young,因為innodb_old_blocks_time設定導緻頁沒有從old部分移動到new部分稱為page not made young。

重做日志緩沖(redo log buffer)

三種情況會講redo log buffer中的内容重新整理到日志檔案

Master Thread每秒重新整理一次

每個事務送出時會重新整理

redo log buffer剩餘空間小于1/2時

額外的記憶體池

在對資料庫結構本身的記憶體進行配置設定的時候,需要從額外的記憶體池進行申請。

Checkpoint技術

InnoDB存儲引擎内部有兩種:

Sharp Checkpoint

資料庫關閉時将所有髒頁刷回磁盤,預設工作方式,參數innodb_fast_shuthown=1

Fuzzy Checkpoint

重新整理一部分髒頁。(Master Thread Checkpoint,FLUSH_LRU_LIST Checkpoint,Async/Sync Flush Checkpoint,Dirty Page too much Checkpoint)

InnoDB關鍵特性

插入緩沖

Insert Buffer

對于非聚集索引的插入或者更新操作,不是每一次直接插入到索引頁,而是先判斷插入的非聚集索引是否在緩沖池中,若在則插入,若不在則放入到一個Insert Buffer中。以一定的頻率進行Insert Buffer和非聚集索引子節點的合并操作。需要滿足兩個條件:1.索引是輔助索引。2.索引不是唯一的。

Change Buffer

InnoDB 1.0.x開始可以對DML操作進行緩沖 (Insert,Delete,Update)分别是:Insert Buffer,Delete Buffer,Purge Buffer。

Insert Buffer是一顆B+樹,全局唯一,負責對所有表的輔助索引進行Insert Buffer。

Merge Insert Buffer是合并到真正的輔助索引中的操作,在下面幾種情況時發生:

輔助索引頁被讀取到緩沖池中

Insert Buffer Bitmap 頁追蹤到該輔助索引頁已經沒有空間可用

Master Thread 觸發

自适應Hash索引(Adaptive Hash Index)

InnoDB 會監控各種索引列的查詢,如果判斷建立哈希索引可以提高通路速度,則會自動建立。AHI是通過緩沖池的B+樹建構而來,不需要對整張表結建構立哈希索引。有如下要求:

以相同模式通路了100次

頁通過該模式通路了N次:N=頁中記錄*1/16

異步IO

異步IO(Asychronous IO,AIO)

檔案

參數檔案:

初始化參數檔案

日志檔案:

例如錯誤日志檔案(error log),二進制日志檔案(binlog),慢查詢日志檔案(slow query log),查詢日志檔案(log)

socket檔案:

UNIX域套接字方式進行連接配接是需要的檔案。

pid檔案:

MySQL執行個體的程序ID檔案

MySQL表結構檔案:

用來存放MySQL表結構定義的檔案

存儲引擎檔案:

二進制日志(binlog)

記錄了對MySQL資料庫執行更改的所有操作,不包括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)

伺服器編号(Server_id)

日志結束位置(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

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

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

MySQL5.1引入了binlog_format參數,參數有STATEMENT、ROW、MIXED三種。

STATEMENT

和之前的MySQL版本一樣,二進制日志檔案記錄的是日志的邏輯SQL語句。

ROW

記錄的是表的行更改情況。如果設定為ROW,可以将InnoDB事務隔離設定為READ COMMITTED擷取更好的并發性。

MIXED

預設使用STATEMENT,某些情況下使用MIXED。

表的存儲引擎為NDB,對表的DML操作以ROW格式記錄。

使用了UUID() USER() CURRENT_USER() FOUND_ROWS() ROW_COUNT()

使用了INSERT DELAY語句

使用了使用者自定義函數

使用了臨時表

要檢視binlog日志檔案的内容,必須使用MySQL提供的工具mysqlbinlog。

表結構定義檔案

MySQL定義了frm為字尾名的檔案,記錄了表結構(視圖)定義。

InnoDB存儲引擎檔案

表空間檔案(tablespace file)

預設有一個初始大小為10MB,名為ibdata1的檔案

重做日志檔案(redo log file)

預設情況下會有 ib_logfile0和ib_logfile1作為 redo log file 。每個InnoDB至少有一個重做日志檔案組(group),檔案組下有兩個重做日志檔案,使用者可以設定多個鏡像日志組(mirrored log groups)

索引組織表(index organized table)

MySQL預設建立一個6位元組大小的指針(_rowid)

InnoDB邏輯存儲結構

所有的資料都被邏輯地存放在一個空間内,稱之為表空間(tablespace),表空間又由段(segment),區(extent)、頁(page)組成,頁在某些文檔中也成為塊(block)

高性能mysql釋出時間_高性能MySQL分析

表空間

如果啟用了 innodb_file_per_table的參數,每張表的資料可以單獨放到一個表空間内 ,其中存放的是資料、索引、和插入緩沖Bitmap頁,其他類的資料如復原(undo)資訊、插入緩沖索引頁、系統事務資訊、二次寫緩沖還是放在原本的共享表空間。

表空間是由各個段組成的,包括資料段、索引段、復原段等。資料段就是B+樹的葉子節點(Leaf node segment),索引段即B+樹的非索引節點(Non-leaf node segment)。

區是連續頁組成的空間,任何情況下每個區的大小都為1MB,為了保證區中頁的連續性,InnoDB一次從磁盤申請4-5個區,預設情況頁大小為16KB,一個區中一共有64個連續的頁。InnoDB1.0.x引入壓縮頁,每個頁的大小可以通過key_block_size設定為2k、4k、8k。1.2.x版本新增了參數innodb_page_size,通過該參數可以将預設頁的大小設定為4k、8k。

InnoDB中常見的頁類型有:

資料頁(B-tree Node)

Undo頁(Undo Log Page)

系統頁(System Page)

事務資料頁(Transaction system Page)

插入緩沖頁位圖(Insert Buffer Bitmap)

插入緩沖空閑清單頁(Insert Buffer Free List)

未壓縮的二進制大對象頁(Uncompressed BLOB Page)

MySQL的存儲是面向列的(row-oriented),資料是按行存儲的。頁存放的記錄有硬性定義最多存放16KB/2 - 200行,即7992行。

InnoDB資料頁結構

資料頁由下面7個部分組成:

File Header(檔案頭)固定

Page Header(頁頭)固定

Infimun 和 Supremun Record 固定

頁中兩個虛拟的行記錄,Infimun是指比頁中任何主鍵更小的值,Supremun指比任何值都大的值,這兩個值在頁建立的時候建立,在任何時候情況下都不會删除。

User Record(使用者記錄,即行記錄)

存儲實際記錄,B+樹索引組織。

Free Space(空閑空間)

空閑空間,連結清單資料結構。一條記錄被删除後會放到空閑空間。

Page Directory(頁目錄)

存放了記錄的相對位置,這些記錄指針稱之為槽(slots)或者目錄槽(dictionary slots),稀疏目錄,可能包含多條記錄。

B+樹索引不能找到實際的記錄,而是找到記錄的頁。

File Trailer(檔案結尾資訊)

檢測頁是否完整寫入了磁盤,checksum值。

高性能mysql釋出時間_高性能MySQL分析

行溢出資料

InnoDB會将一條記錄中的某些列存儲在真正的資料列之外,BLOB,LOB字段可能不一定會将字段放在溢出頁面,VARCHAR也有可能會放進溢出頁面。

Oracle VarCHAR2最多存放4000位元組,MSSQL最多8000位元組,MySQL最多65535(存在其他開銷,最長65532)。當發生行溢出時,資料存放在頁類型Uncompress BLOB頁面。資料頁隻儲存資料的前768位元組。

lock與latch

latch一般稱為闩鎖,輕量級,要求鎖定的時間非常短。在InnoDB中,分為mutex(互斥量)與rwlock(讀寫鎖)。用來保證并發線程操作臨界資源的正确性,并且通常沒有死鎖檢測的機制。

lock的對象是事務,用來鎖定的是資料庫中的對象,如表、頁、行。在commit或者rollback之後釋放,有死鎖檢測機制。

鎖的類型

共享鎖(S Lock):允許事務讀一行資料

排他鎖(X Lock):允許事務更新或删除一行資料

上述兩種都是悲觀鎖,樂觀鎖就是CAS(Compare and Swap)

一緻性非鎖定讀(consistent nonlocking read)

是指InnoDB通過MVCC(Multi Version Concurrency Control)讀取資料庫目前行的方式。如果讀取的行正在進行update或者delete操作,則讀取一個快照。在Read Committed和Repeatedable Read中使用。前者讀取最新的快照,後者使用事務開始時的快照。

一緻性鎖定讀(locking read)

也可以顯式的對讀取加鎖,有兩種操作:

select ... for update(加一個X鎖)

select ... lock in share mode(加一個S鎖)

行鎖的3種算法

Record Lock:單個行記錄的鎖

Gap Lock:鎖定一個範圍,不包括記錄本身

Next-Key Lock:Gap+Record,鎖定範圍以及記錄本身。用來解決幻影相關問題(Phantom)

針對的是索引的區間,但是當查詢條件指定唯一索引值(隻針對主鍵索引/聚集索引)時,會降級為Record Lock,若是二級索引則不會。而且InnoDB還會對二級索引的下一個鍵值加上Gap Lock。

例如,二級索引b列有1,3,6,9。當使用X鎖鎖定3時(where b<=3 for update),會NKL鎖定了範圍(1-3),同時會使用GL鎖定下一個鍵值(3-6)。

利用這個機制可以用一個事務,首先select id from t where col=xxx lock in share mode,接下來insert t (col) values (xxx),能夠保證一定插入不存在的值。

死鎖

兩個事務執行時,因争奪鎖資源互相等待的場景。

解決死鎖最簡單的就是逾時,通過innodb_lock_wait_timeout控制逾時時間。

目前普遍使用的是wait-for graph(主動檢測的方式),這要求資料庫儲存兩種資訊:

鎖的資訊連結清單

事務的等待清單

通過上述資訊,可以在事務請求鎖并發生等待時都進行判斷,在上述兩個資訊構造的圖中是否存在回路,如果存在就表示存在死鎖。

采用深度優先算法實作,InnoDB1.2之前采用遞歸方式,之後采用非遞歸提高了性能。

事務的實作(ACID)

事務的隔離性由鎖來實作,redo log(重做日志)保證事務的原子性和持久性,undo log()保證事務的一緻性。

redo恢複送出事務修改的頁操作,是實體日志,記錄的是頁的實體修改操作。

undo復原某個行記錄到特定版本,是邏輯日志,記錄的是行的修改記錄。

redo

存在 redo log buffer和redo log file,buffer寫入file時需要調用fsync操作,此操作取決于磁盤性能,決定了事務送出的性能也就是資料庫的性能。

UNIX的寫操作

一般情況下,對硬碟(或者其他持久儲存設備)檔案的write操作,更新的隻是記憶體中的頁緩存(page cache),而髒頁面不會立即更新到硬碟中,而是由作業系統統一排程,如由專門的flusher核心線程在滿足一定條件時(如一定時間間隔、記憶體中的髒頁達到一定比例)内将髒頁面同步到硬碟上(放入裝置的IO請求隊列)。

因為write調用不會等到硬碟IO完成之後才傳回,是以如果OS在write調用之後、硬碟同步之前崩潰,則資料可能丢失。雖然這樣的時間視窗很小,但是對于需要保證事務的持久化(durability)和一緻性(consistency)的資料庫程式來說,write()所提供的“松散的異步語義”是不夠的,通常需要OS提供的同步IO(synchronized-IO)原語來保證

fsync的功能是確定檔案fd所有已修改的内容已經正确同步到硬碟上,該調用會阻塞等待直到裝置報告IO完成。除了同步檔案的修改内容(髒頁),fsync還會同步檔案的描述資訊(metadata,包括size、通路時間st_atime & st_mtime等等),因為檔案的資料和metadata通常存在硬碟的不同地方,是以fsync至少需要兩次IO寫操作

undo

delete和update操作産生的删除語句并不是馬上執行,而是将delete_flag标記為1,最後有purge操作來統一完成。用undo log來執行,執行之後的空間不會回收,隻會用于重用。