本文節選自 MySQL 引擎架構與性能優化 https://url.wx-coder.cn/IF5HH,參考文檔聲明在 Awesome MySQL List https://parg.co/htL。算法
在數據結構與算法--索引 https://url.wx-coder.cn/O07eI 一節中,咱們討論了 B+Tree, LSM-Tree 這樣的文件索引以及全文索引的基礎算法,本文則會針對文件索引在關係型數據庫中的實際應用進行探討。sql
索引(Index)是幫助數據庫系統高效獲取數據的數據結構,而數據庫索引本質上是以增長額外的寫操做,與用於維護索引數據結構的存儲空間爲代價的,用於提高數據庫中數據檢索效率的數據結構。索引能夠幫助咱們快速地定位到數據而不須要每次搜索的時候都遍歷數據庫中的每一行。固然,索引不是創建的越多、越長越好,由於索引除了佔用空間以外,對後續數據庫的增長、刪除、修改都有額外的操做來更新索引。通常來講,小表使用全表掃描更快,中大表才使用索引,而超級大表索引基本無效,咱們可能須要藉助獨立的全文索引系統;MySQL 自帶的全文索引只能用於 InnoDB、MyISAM ,而且只能對英文進行全文檢索,通常使用 ES,Solr 這樣的全文索引引擎。數據庫
從索引的實現上,咱們能夠將其分爲彙集索引與非彙集索引,或稱輔助索引或二級索引,這兩大類;從索引的實際應用中,又能夠細分爲普通索引、惟一索引、主鍵索引、聯合索引、外鍵索引、全文索引這幾種。緩存
InnoDB 能夠看作是彙集索引,由於它的 B+ 樹的葉結點包含了完整的數據記錄。InnoDB 的數據文件自己就是索引文件,表數據文件自己就是按 B+Tree 組織的一個索引結構,這棵樹的葉節點 data 域保存了完整的數據記錄。這個索引的 key 是數據表的主鍵,所以 InnoDB 表數據文件自己就是主索引。InnoDB 的輔助索引 data 域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB 的全部輔助索引都引用主鍵做爲 data 域。性能優化
而 MyISAM 方式 B+ 樹的葉結點只是存儲了數據的地址,故稱爲非彙集索引。MyISAM 引擎使用 B+Tree 做爲索引結構,葉節點的 data 域存放的是數據記錄的地址;在 MyISAM 中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求 key 是惟一的,而輔助索引的 key 能夠重複。bash
在 InnoDB 中,又有聚簇索引和普通索引之分,聚簇索引根據主鍵來構建,葉子節點存放的是該主鍵對應的這一行記錄,根據主鍵查詢能夠直接利用聚簇索引定位到所在記錄。而普通索引根據申明這個索引時候的列來構建,葉子節點存放的是這一行記錄對應的主鍵的值,根據普通索引查詢須要先在普通索引上找到對應的主鍵的值,而後根據主鍵值去聚簇索引上查找記錄,俗稱回表。若是咱們查詢一整行記錄的話,必定要去聚簇索引上查找,而若是咱們只須要根據普通索引查詢主鍵的值,因爲這些值在普通索引上已經存在,因此並不須要回表,這個稱爲索引覆蓋,在必定程度上能夠提升查詢效率。數據結構
普通索引中還有惟一索引和聯合索引兩個特例,惟一索引在插入和修改的時候會校驗該索引對應的列的值是否已經存在,聯合索引將兩個列的值按照申明時候的順序進行拼接後在構建索引。架構
數據行並非存儲引擎管理的最小存儲單位,索引只可以幫助咱們定位到某個數據頁,每一次磁盤讀寫的最小單位爲也是數據頁,而一個數據頁內存儲了多個數據行,咱們須要瞭解數據頁的內部結構才能知道存儲引擎怎麼定位到某一個數據行,能夠參考 MySQL 存儲管理 https://url.wx-coder.cn/IF5HH 系列。併發
對索引列和字符串前綴長度,都參考選擇性(Selectivity)這個指標來肯定:選擇性定義爲不重複的索引值和數據總記錄條數的比值,其選擇性越高,那麼索引的查詢效率也越高,譬如對於性別這種參數,創建索引根本沒有意義。異步
Index Selectivity = Cardinality / #T
複製代碼
顯然選擇性的取值範圍爲 (0, 1]
,選擇性越高的索引價值越大,這是由 B+Tree 的性質決定的。在實際的數據庫中,咱們能夠經過如下語句來計算某列的選擇性:
SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM titles;
複製代碼
在 InnoDB 內部,表數據是優化主鍵快速查詢而排列分佈的,其查找速度是最快的,該索引中鍵值的邏輯順序決定了表中相應行的物理順序。即便表中沒有適合作主鍵的列,也推薦採用一個自動增加的整數主鍵(代理鍵),那麼這個表在增長數據的時候是順序存放的,並且後續在別的表參考該外鍵查詢的時候也會獲得優化。
若是在建立表時沒有顯式地定義主鍵(Primary Key),則 InnoDB 存儲引擎會按以下方式選擇或建立主鍵:
在分佈式 ID https://url.wx-coder.cn/tQ5eH 一文中咱們討論過度布式場景下的分佈式 ID 的選擇策略,而在數據庫中,咱們一樣會有這樣的考量。首先,MySQL 官方有明確的建議主鍵要儘可能越短越好,36 個字符長度的 UUID 不符合要求;若是主鍵是一個很長的字符串而且建了不少普通索引,將形成普通索引佔有很大的物理空間。而且主鍵最好是順序遞增的,不然在 InnoDB 引擎下,UUID 的無序性可能會引發數據位置頻繁變更,嚴重影響性能。
自增 ID 在插入的時候能夠保證相鄰的兩條記錄可能在同一個數據塊,而訂單號這樣的業務相關的連續性設計上可能沒有自增 ID 好,致使連續插入可能在多個數據塊,增長了磁盤讀寫次數。
主鍵就是惟一索引,可是惟一索引不必定是主鍵,惟一索引能夠爲空,可是空值只能有一個,主鍵不能爲空。對於單列索引,要求該列全部數據都不相同,但容許有 NULL 值;對於多列的聯合索引,要求這些列的組合是惟一的。惟一索引其自己既能夠做爲索引,實際中也能夠用以產生數據約束,防止增長或者修改後產生相同數據,從而保證數據的完整性。
對於字符串類型,能夠指定索引前綴長度(且對於 BLOB/TEXT 前綴長度參數是必須的),在 InnoDB 表中其前綴長度最長是 767 bytes,且參數 M 是用 bytes 計量的。因此太長的字符串,創建 B+Tree 索引浪費比較大,這時候用手動模擬 HASH 索引是個方法,不過這種方式對字符串沒法靈活的使用前綴方式查詢(例如 LIKE 這類的操做)。
單列索引指的是在表上爲某一個字段創建的索引,通常索引的建立選擇整型或者較小的定長字符串將更有利於效率的提高。聯合索引指的是多個字段按照必定順序組織的索引。以索引 (name, city, gender)
爲例,其首先是按照 name 字段順序組織的,當 name 字段的值相同時(如 Bush),其按照 city 字段順序組織,當 city 字段值相同時,其按照 gender 字段組織。因爲聯合索引上經過多個列構建索引,有時候咱們能夠將須要頻繁查詢的字段加到聯合索引裏面,譬如常常須要根據 name 查找 age 咱們能夠建一個 name 和 age 的聯合索引。
常見的條件聯合包括了 WHERE 條件聯合與 ORDER BY 條件聯合;所謂 WHERE 條件聯合指的是,對於 WHERE 條件中的等值條件,其字段使用與聯合索引的字段一致(順序能夠不一致)。
ORDER BY 聯合指的是若是 ORDER BY 後面的字段是聯合索引覆蓋 where 條件以後的一個字段,因爲索引已經處於有序狀態,MySQL 就會直接從索引上讀取有序的數據,而後在磁盤上讀取數據以後按照該順序組織數據,從而減小了對磁盤數據進行排序的操做。即對於未覆蓋 ORDER BY 的查詢,其有一項 Creating sort index,即爲磁盤數據進行排序的耗時最高;對於覆蓋 ORDER BY 的查詢,其就不須要進行排序,而其耗時主要體如今從磁盤上拉取數據的過程。
MySQL 的前綴索引能夠分爲三類:聯合索引前綴,like 前綴和字符串前綴。
聯合索引前綴指的是在創建多列索引的時候,必須按照從左到右的順序使用所有或部分的索引列,才能充分的使用聯合索引,好比:(col1, col2, col3)
使用 (col1)、(col1, col2)、(col1, col2, col3)
有效。在查詢語句中會一直向右匹配直到遇到範圍查詢 (>,<,BETWEEN,LIKE)
就中止匹配,其後的索引列將不會使用索引來優化查找了。
以 (name, city, interest)
三個字段聯合的索引爲例,若是查詢條件爲 where name='Bush';
那麼就只須要根據 B+樹定位到 name 字段第一個 Bush 所在的值,而後順序掃描後續數據,直到找到第一個不爲 Bush 的數據便可,掃描過程當中將該索引片的數據 id 記錄下來,最後根據 id 查詢聚簇索引獲取結果集。同理對於查詢條件爲 where name='Bush' and city='Chicago';
的查詢,MySQL 能夠根據聯合索引直接定位到中間灰色部分的索引片,而後獲取該索引片的數據 id,最後根據 id 查詢聚簇索引獲取結果集。
由此咱們能夠得出聯合索引前綴的注意點:
where name='Bush' and interest='baseball';
,對於該查詢,name 字段是可使用聯合索引的第一個字段過濾大部分數據的,可是對於 interest 字段,其沒法經過 B+ 樹的特性直接定位第三個字段的索引片數據,好比這裏的 baseball 可能分散在了第二條和第七條數據之中。最終,interest 字段其實進行的是覆蓋索引掃描。where name='Bush' and city>'Chicago' and interest='baseball';
,對於該查詢條件,首先能夠根據 name 字段過濾索引片中第一個字段的非 Bush 的數據,而後根據聯合索引的第二個字段定位到索引片的 Chicago 位置,因爲其是非等值條件,這裏 MySQL 就會從定位的 Chicago 往下順序掃描,因爲 interest 字段是可能分散在索引第三個字段的任何位置的,於是第三個字段沒法參與索引片的過濾。所以 B-Tree 的列順序很是重要,上述使用規則都和列順序有關。對於實際的應用,通常要根據具體的需求,建立不一樣列和不一樣列順序的索引。假設有索引 Index(A,B,C)
:
# 使用索引
A>5 AND A<10 - 最左前綴匹配
A=5 AND B>6 - 最左前綴匹配
A=5 AND B=6 AND C=7 - 全列匹配
A=5 AND B IN (2,3) AND C>5 - 最左前綴匹配,填坑
# 不能使用索引
B>5 - 沒有包含最左前綴
B=6 AND C=7 - 沒有包含最左前綴
# 使用部分索引
A>5 AND B=2 - 使用索引 A 列
A=5 AND B>6 AND C=2 - 使用索引的 A 和 B 列
複製代碼
使用索引對結果進行排序,須要索引的順序和 ORDER BY 子句中的順序一致,而且全部列的升降序一致(ASC/DESC)。若是查詢鏈接了多個表,只有在 ORDER BY 的列引用的是第一個表才能夠(須要按序 JOIN)。
# 使用索引排序
ORDER BY A - 最左前綴匹配
WHERE A=5 ORDER BY B,C - 最左前綴匹配
WHERE A=5 ORDER BY B DESC - 最左前綴匹配
WHERE A>5 ORDER BY A,B - 最左前綴匹配
# 不能使用索引排序
WHERE A=5 ORDER BY B DESC,C ASC - 升降序不一致
WHERE A=5 ORDER BY B,D - D 不在索引中
WHERE A=5 ORDER BY C - 沒有包含最左前綴
WHERE A>5 ORDER BY B,C - 第一列是範圍條件,沒法使用 BC 排序
WHERE A=5 AND B IN(1, 2) ORDER BY C - B 也是範圍條件,沒法用 C 排序
複製代碼
對於 like 前綴,其是指在使用 like 查詢時,若是使用的表達式爲 first_name like 'rMq%';
那麼其是能夠用到 first_name 字段的索引的。可是對於 first_name like '%Chu%';
,其就沒法使用 first_name 的索引。對於 like 前綴,MySQL 底層其實是使用了一個補全策略來使用索引的,好比這裏 first_name like 'rMq%';
,MySQL 會將其補全爲兩條數據:rMqAAAAA 和 rMqzzzzz,後面補所有分的長度爲當前字段的最大長度。在使用索引查詢時,MySQL 就使用這兩條數據進行索引定位,最後須要的結果集就是這兩個定位點的中間部分的數據。以下是使用 like 前綴的一個示意圖:
字符串前綴索引指的是隻取字符串前幾個字符創建的索引。在進行查詢時,若是一個字段值較長,那麼爲其創建索引的成本將很是高,而且查詢效率也比較低,字符串前綴索引就是爲了解決這一問題而存在的。字符串前綴索引主要應用在兩個方面:
譬如爲 first_name 字段創建了長度爲 4 的前綴索引,能夠看到,若是查詢使用的是 where first_name='qWhNIZqxcbD';
,那麼 MySQL 首先會截取等值條件的前四個字符,而後將其與字符串前綴索引進行比較,從而定位到前綴爲"qWhN"的索引片,而後獲取該索引片對應的磁盤數據,最後將獲取的磁盤數據的 first_name 字段與查詢的等值條件的值進行比較,從而獲得結果集。
字符串前綴索引最須要注意的一個問題是如何選擇前綴的長度,長度選擇合適時,前綴索引的過濾性將和對整個字段創建索引的選擇性幾乎相等。這裏咱們就須要用到前面講解的關於字段選擇性的概念,即字段選擇性爲對該字段分組以後,數據量最大的組的數據量佔總數據量的比例。這裏選擇前綴長度時,能夠理解爲,前綴的選擇性爲按照前綴分組以後,數據量最大的組佔總數據量的比例。以下表所示爲計算前綴長度的 SQL 公式:
select count(*) as cnt, first_name as perf from actor group by perf ORDER BY cnt desc limit 10; -- 0
select count(*) as cnt, left(first_name, 2) as perf from actor group by perf ORDER BY cnt desc limit 10; -- 2
select count(*) as cnt, left(first_name, 3) as perf from actor group by perf ORDER BY cnt desc limit 10; -- 3
select count(*) as cnt, left(first_name, 4) as perf from actor group by perf ORDER BY cnt desc limit 10; -- 4
複製代碼
覆蓋索引指的是對於查詢中使用的除去參與索引過濾掃描的全部字段將其加入到該查詢所使用的索引尾部的索引。覆蓋索引掃描的優勢在於因爲查詢中所使用的全部字段都在同一索引的字段,於是在進行查詢時只須要在索引中獲取相關數據便可,而不須要回磁盤掃描相應的數據,從而避免了查詢中最耗時的磁盤 I/O 讀取。對於以下查詢:
select a, b, c from t where a='a' and b='b';
複製代碼
該查詢中若是創建聯合索引(a, b, c),那麼這就是使用了覆蓋掃描的索引,由於對於該查詢,可使用索引的前兩個字段 a 和 b 根據 where 條件進行索引片的過濾,對過濾後的索引片直接在索引中讀取 a, b, c 三個字段的值便可,而無需回表掃描。
三星索引指的是對於一個查詢,設立了三個通用的索引條件知足的條件,創建的索引對於特定的查詢每知足一個條件就表示該索引獲得一顆星,當該索引獲得三顆星時就表示該索引對於該查詢是一個三星索引。三星索引是對於特定查詢的最優索引,創建三星索引的條件以下:
(WHERE COL=…)
做爲索引開頭的列;譬如對於以下的查詢,索引 (first_name, last_name, email)
就是一個三星索引:
SELECT first_name, last_name, email FROM user WHERE first_name = 'aa' ORDER BY last_name;
複製代碼
三星索引的建立過程能夠發現以下規律:
MySQL 查詢的時候會先經過索引定位到對應的數據頁,而後檢測數據頁是否在緩衝池內,若是在就直接返回,若是不在就去聚簇索引中經過磁盤 IO 讀取對應的數據頁並放入緩衝池。一個數據頁會包含多個數據行。緩存池經過 LRU 算法對數據頁進行管理,也就是最頻繁使用的數據頁排在列表前面,不常用的排在隊尾,當緩衝池滿了的時候會淘汰掉隊尾的數據頁。從磁盤新讀取到的數據頁並不會放在隊列頭部而是放在中間位置,這個中間位置能夠經過參數進行修。緩衝池也能夠設置多個實例,數據頁根據哈希算法決定放在哪一個緩衝池。
在 MySQL 存儲結構一文中,咱們討論過 MySQL 數據頁的存儲結構。
InnoDB 的內存主要有如下幾個部分組成:緩衝池 (buffer pool)、重作日誌緩衝池(redo log buffer)以及額外的內存池(additional memory pool),以下圖所示:
其中緩衝池佔最大塊內存,用來緩存各自數據,數據文件按頁(每頁 16K)讀取到緩衝池,按最近最少使用算法(LRU)保留緩存數據。緩衝池緩衝的數據類型有:數據頁、索引頁、插入緩衝、自適應哈希索引、鎖信息、數據字典信息等,其中數據頁和索引頁佔了絕大部份內存。日誌緩衝將重作日誌信息先放入這個緩衝區,而後按必定頻率(默認爲 1s)將其刷新至重作日誌文件。
InnoDB 經過一些列後臺線程將相關操做進行異步處理,同時藉助緩衝池來減少 CPU 和磁盤速度上的差別。當查詢的時候會先經過索引定位到對應的數據頁,而後檢測數據頁是否在緩衝池內,若是在就直接返回,若是不在就去聚簇索引中經過磁盤 IO 讀取對應的數據頁並放入緩衝池。一個數據頁會包含多個數據行。緩存池經過 LRU 算法對數據頁進行管理,也就是最頻繁使用的數據頁排在列表前面,不常用的排在隊尾,當緩衝池滿了的時候會淘汰掉隊尾的數據頁。從磁盤新讀取到的數據頁並不會放在隊列頭部而是放在中間位置,這個中間位置能夠經過參數進行修。緩衝池也能夠設置多個實例,數據頁根據哈希算法決定放在哪一個緩衝池。
InnoDB 存儲引擎的邏輯存儲結構和 Oracle 大體相同,全部數據都被邏輯地存放在一個空間中,咱們稱之爲表空間(tablespace)。表空間又由段(segment)、區(extent)、頁(page)組成。頁在一些文檔中有時也稱爲塊(block),1 extent = 64 pages,InnoDB 存儲引擎的邏輯存儲結構大體如圖所示:
表空間做爲存儲結構的最高層,全部數據都存放在表空間中,默認狀況下用一個共享表空間 ibdata1 ,若是開啓了 innodb_file_per_table 則每張表的數據將存儲在單獨的表空間中,也就是每張表都會有一個文件,
表空間由各個段構成,InnoDB 存儲引擎由索引組織的,而索引中的葉子節點用來記錄數據,存儲在數據段,而非葉子節點用來構建索引,存儲在索引段。區是由連續的頁組成,任何狀況下一個區都是 1MB,一個區中能夠有多個頁,每一個頁默認爲 16KB ,因此默認狀況下一個區中能夠包含 64 個連續的頁,頁的大小是能夠經過 innodb_page_size 設置,頁中存儲的是具體的行記錄。一行記錄最終以二進制的方式存儲在文件裏。
從物理意義上來看,InnoDB 表由共享表空間、日誌文件組(更準確地說,應該是 Redo 文件組)、表結構定義文件組成。若將 innodb_file_per_table 設置爲 on,則每一個表將獨立地產生一個表空間文件,以 ibd 結尾,數據、索引、表的內部數據字典信息都將保存在這個單獨的表空間文件中。表結構定義文件以 frm 結尾,這個是與存儲引擎無關的,任何存儲引擎的表結構定義文件都同樣,爲 .frm 文件。
默認狀況下,InnoDB 的後臺線程有 7 個,其中 4 個 IO thread, 1 個 Master thread, 1 個 Lock monitor thread, 一個 Error monitor thread。InnoDB 的主要工做都是在一個單獨的 Master 線程裏完成的。Master 線程的優先級最高,它主要分爲如下幾個循環:主循環(loop)、後臺循環(background loop)、刷新循環(flush loop)、暫停循環(suspend loop)。
其中主循環的僞代碼以下:
void master_thread() ( loop: for (int i =0; i <10; i++){
do thing once per second
sleep 1 second if necessary
}
do things once per ten seconds
goto loop;
}
複製代碼
MySQL 爲咱們提供了行鎖、表鎖、頁鎖三種級別的鎖,其中表鎖開銷小,加鎖快;不會出現死鎖;鎖定力度大,發生鎖衝突機率高,併發度最低。行鎖開銷大,加鎖慢;會出現死鎖;鎖定粒度小,發生鎖衝突的機率低,併發度高;頁鎖開銷和加鎖速度介於表鎖和行鎖之間;會出現死鎖;鎖定粒度介於表鎖和行鎖之間,併發度通常。每一個存儲引擎均可以有本身的鎖策略,例如 MyISAM 引擎僅支持表級鎖,而 InnoDB 引擎除了支持表級鎖外,也支持行級鎖(默認)。
行鎖 | 表鎖 | 頁鎖 | |
---|---|---|---|
MyISAM | √ | ||
BDB | √ | √ | |
InnoDB | √ | √ |
InnoDB 行鎖是經過給索引上的索引項加鎖來實現的,這一點 MySQL 與 Oracle 不一樣,後者是經過在數據塊中對相應數據行加鎖來實現的。InnoDB 這種行鎖實現特色意味着:只有經過索引條件檢索數據,InnoDB 才使用行級鎖,不然,InnoDB 將使用表鎖,一樣地,當 for update
的記錄不存在會致使鎖住全表。當表有多個索引的時候,不一樣的事務可使用不一樣的索引鎖定不一樣的行,另外,不管是使用主鍵索引、惟一索引或普通索引,InnoDB 都會使用行鎖來對數據加鎖。
InnoDB 的加鎖過程比較複雜,將全部掃描到的記錄都加鎖,範圍查詢會加間隙鎖,而後加鎖過程按照兩階段鎖 2PL 來實現,也就是先加鎖,而後全部的鎖在事物提交的時候釋放。加鎖的策略會和數據庫的隔離級別有關,在默認的可重複讀的隔離級別的狀況下,加鎖的流程還會和查詢條件中是否包含索引,是主鍵索引仍是普通索引,是不是惟一索引等有關。
譬如對於 select * from o_order where order_sn = '201912102322' for update;
這條 SQL 語句,在不一樣的索引狀況下其加鎖策略也不一致:
order_sn 是主鍵索引,這種狀況將在主鍵索引上的 order_sn = 201912102322
這條記錄上加排他鎖。
order_sn 是普通索引,而且是惟一索引,將會對普通索引上對應的一條記錄加排他鎖,對主鍵索引上對應的記錄加排他鎖。
order_sn 是普通索引,而且不是惟一索引,將會對普通索引上 order_sn = 201912102322
一條或者多條記錄加鎖,而且對這些記錄對應的主鍵索引上的記錄加鎖。這裏除了加上行鎖外,還會加上間隙鎖,防止其餘事務插入 order_sn = 201912102322
的記錄,然而若是是惟一索引就不須要間隙鎖,行鎖就能夠。
order_sn 上沒有索引,innoDB 將會在主鍵索引上全表掃描,這裏並無加表鎖,而是將全部的記錄都會加上行級排他鎖,而實際上 innoDB 內部作了優化,當掃描到一行記錄後發現不匹配就會把鎖給釋放,固然這個違背了 2PL 原則在事務提交的時候釋放。這裏除了對記錄進行加鎖,還會對每兩個記錄之間的間隙加鎖,因此最終將會保存全部的間隙鎖和 order_sn = 201912102322
的行鎖。
order_sn = 201912102322
這條記錄不存在的狀況下,若是 order_sn 是主鍵索引,則會加一個間隙鎖,而這個間隙是主鍵索引中 order_sn 小於 201912102322 的第一條記錄到大於 201912102322 的第一條記錄。試想一下若是不加間隙鎖,若是其餘事物插入了一條 order_sn = 201912102322
的記錄,因爲 select for update 是當前讀,即便上面那個事物沒有提交,若是在該事物中從新查詢一次就會發生幻讀。
若是沒有索引,則對掃描到的全部記錄和間隙都加鎖,若是不匹配行鎖將會釋放只剩下間隙鎖。回憶一下上面講的數據頁的結果中又一個最大記錄和最小記錄,Infimum 和 Supremum Record,這兩個記錄在加間隙鎖的時候就會用到。
此文還沒有涉及 MySQL 中索引優化的相關內容,能夠參考 MySQL 引擎架構與性能優化 https://url.wx-coder.cn/IF5HH 系列中性能優化的相關章節。