MySQL創建高性能索引

来源:https://www.cnblogs.com/dooor/archive/2022/04/10/mysql.html
-Advertisement-
Play Games

參考《高性能MySQL》第3版 1 索引基礎 1.1 索引作用 在MySQL中,查找數據時先在索引中找到對應的值,然後根據匹配的索引記錄找到對應的數據行,假如要運行下麵查詢語句: 如果在uid在建有索引,則MySQL將使用該索引先找到uid為5的行,也就是說MySQL先在索引上按值進行查找,然後返回 ...


參考《高性能MySQL》第3版

1 索引基礎

1.1 索引作用

在MySQL中,查找數據時先在索引中找到對應的值,然後根據匹配的索引記錄找到對應的數據行,假如要運行下麵查詢語句:

SELECT	* FROM  USER  WHERE uid = 5;

如果在uid在建有索引,則MySQL將使用該索引先找到uid為5的行,也就是說MySQL先在索引上按值進行查找,然後返回所有包含該值的數據行。

1.2 MySQL索引常用數據結構

MySQL索引是在存儲引擎層面實現的,不是在伺服器實現的。所以,沒有統一的索引標準:不同存儲引擎的索引工作方式不一樣。

1.2.1 B-Tree

大多數的MySQL引擎都支持這種索引B-Tree,即時多個存儲引擎支持同一種類型的索引,其底層實現也可能不同。比如InnoDB使用的是B+Tree。
存儲引擎以不同的方式實現B-Tree,性能也各有不同,各有優勢。如,MyISAM使用首碼壓縮技術是的索引更小,當InnoDB則按照原數據格式進行存儲,MyISAMy索引通過數據的物理位置引用被索引的行,而InnoDB根據組件應用被索引的行。
B-Tree所有值都是順序存儲的,並且每一個葉子頁到根的距離相同。如下圖大致反應了InnoDB索引是如何工作的,MyISAM使用的結構有所不同。但基本實現是類似的。

實例圖說明
每個節點占用一個磁碟塊,一個節點上有兩個升序排序的關鍵字和三個指向子樹根節點的指針,指針存儲的是子節點所在磁碟塊的地址。兩個關鍵詞劃分成的三個範圍域對應三個指針指向的子樹的數據的範圍域。以根節點為例,關鍵字為 16 和 34,P1 指針指向的子樹的數據範圍為小於 16,P2 指針指向的子樹的數據範圍為 16~34,P3 指針指向的子樹的數據範圍為大於 34。 查找關鍵字過程:

  1. 根據根節點找到磁碟塊 1,讀入記憶體。【磁碟 I/O 操作第 1 次】
  2. 比較關鍵字 28 在區間(16,34),找到磁碟塊 1 的指針 P2。
  3. 根據 P2 指針找到磁碟塊 3,讀入記憶體。【磁碟 I/O 操作第 2 次】
  4. 比較關鍵字 28 在區間(25,31),找到磁碟塊 3 的指針 P2。
  5. 根據 P2 指針找到磁碟塊 8,讀入記憶體。【磁碟 I/O 操作第 3 次】
  6. 在磁碟塊 8 中的關鍵字列表中找到關鍵字 28。 
    缺點
  7. 每個節點都有key,同時也包含data,而每個頁存儲空間是有限的,如果data比較大的話會導致每個節點存儲的key數量變小;
  8. 當存儲的數據量很大的時候會導致深度較大,增大查詢時磁碟io次數,進而影響查詢性能。

1.2.2 B+Tree索引

B+樹是對B樹的變種。與B樹區別:B+樹只在葉子節點存儲數據,非葉子節點只存儲key值及指針。
在B+樹上有兩個指針,一個指向根葉子節點,另一個指向關鍵字最小的葉子節點,而且所有葉子節點(即數據節點)之間是一種鏈式環結構,因此可以對B+樹進行兩種查找運算:一種是對於組件的範圍查找,另一種是從根節點開始,進行隨機查找。
B*樹與B+數類似,區別在於B*數非葉子節點之間也有鏈式環結構。

1.2.2 Hash索引

哈希索引基於哈希表實現,只有精準匹配索引所有列的查詢才有效。對於每一行數據,存儲引擎都會對所有的索引列計算一個哈希碼(hash code),哈希碼是一個較小的值,並且不同鍵值的行計算出來的哈希碼也不一樣。哈希索引將所有的哈希碼存儲在索引中,同時在哈希表中保存指向每個數據行的指針。
在MySQL中只有Memory預設索引類型就是使用的哈希索引,memory也支持B-Tree索引。同時,Memory引擎支持非唯一哈希索引,如果多個列的哈希值相同,索引會以鏈表的方式存放多個指針相同一個哈希條目中。類似HashMap。

優點
索引自身只需要存儲對應的哈希值,所以索引的結構十分緊湊,哈希所以查找的速度非常快。
缺點

  1. 利用hash存儲的話需要將所有的數據文件添加到記憶體,比較耗費記憶體空間;
  2. 哈希索引數據並不是按順序存儲的,所以無法用於排序;
  3. 如果所有的查詢都是等值查詢,那麼hash確實很快,但是在企業或者實際工作環境中範圍查找的數據更多,而不是等值查詢,因此hash就不太適合了;
  4. 如果哈希衝突很多的話,索引維護操作的代價也會很高,這也是HashMap後期通過增加紅黑樹解決Hash衝突的問題;

2 高性能索引策略

2.1 聚簇索引與非聚簇索引

聚簇索引
不是單獨的索引類型,而是一種數據存儲方式,在InnoDB存儲引擎中聚簇索引實際在同一個結構中保存了鍵值和數據行。當表中有聚簇索引時,它的數據行實際上存放在索引的葉子頁中。因為無法同時把數據行存放在不同的地方,所以一個表中只能有一個聚簇索引(索引覆蓋可以模擬出多個聚簇索引的情況)。

聚簇索引優點

  1. 可以把相關數據保存在一起;
  2. 數據訪問更快,因為索引和數據保存在同一個樹中;
  3. 使用覆蓋索引掃描的查詢可以直接使用頁節點中的主鍵值;

缺點

  1. 聚簇數據最大限度地提高了IO密集型應用的性能,如果數據全部在記憶體,那麼聚簇索引就沒有什麼優勢;
  2. 插入速度嚴重依賴於插入順序,按照主鍵的順序插入是最快的方式;
  3. 更新聚簇索引列的代價很高,因為會強制將每個被更新的行移動到新的位置;
  4. 基於聚簇索引的表在插入新行,或者主鍵被更新導致需要移動行的時候,可能面臨頁分裂的問題;
  5. 聚簇索引可能導致全表掃描變慢,尤其是行比較稀疏,或者由於頁分裂導致數據存儲不連續的時候;

非聚簇索引
數據文件跟索引文件分開存放

2.2 首碼索引

有時候需要索引很長的字元串,這會讓索引變的大且慢,通常情況下可以使用某個列開始的部分字元串,這樣大大的節約索引空間,從而提高索引效率,但這會降低索引的選擇性,索引的選擇性是指:不重覆的索引值(也稱為基數cardinality)和數據表記錄總數的比值,範圍從1/#T到1之間。索引的選擇性越高則查詢效率越高,因為選擇性更高的索引可以讓mysql在查找的時候過濾掉更多的行。
一般情況下某個列首碼的選擇性也是足夠高的,足以滿足查詢的性能,但是對應BLOB,TEXT,VARCHAR類型的列,必須要使用首碼索引,因為mysql不允許索引這些列的完整長度,使用該方法的訣竅在於要選擇足夠長的首碼以保證較高的選擇性,通過又不能太長。
舉例
表結構及數據MySQL官網或GItHub下載
city Table Columns

欄位名 含義
city_id 城市主鍵ID
city 城市名
country_id 國家ID
last_update: 創建或最近更新時間
--計算完整列的選擇性
select count(distinct left(city,3))/count(*) as sel3,
    count(distinct left(city,4))/count(*) as sel4,
    count(distinct left(city,5))/count(*) as sel5,
    count(distinct left(city,6))/count(*) as sel6,
    count(distinct left(city,7))/count(*) as sel7,
    count(distinct left(city,8))/count(*) as sel8 
from citydemo;

可以看到當首碼長度到達7之後,再增加首碼長度,選擇性提升的幅度已經很小了。由此最佳創建首碼索引長度為7。

2.3 回表

要理解回表需要先瞭解聚族索引和普通索引。聚族索引即建表時設置的主鍵索引,如果沒有設置MySQL自動將第一個非空唯一值作為索引,如果還是沒有InnoDB會創建一個隱藏的row-id作為索引(oracle資料庫row-id顯式展示,可以用於分頁);普通索引就是給普通列創建的索引。普通列索引在葉子節點中存儲的並不是整行數據而是主鍵,當按普通索引查找時會先在B+樹中查找該列的主鍵,然後根據主鍵所在的B+樹中查找改行數據,這就是回表。

2.4 覆蓋索引

覆蓋索引在InnoDB中特別有用。MySQL中可以使用索引直接獲取列的數據,如果索引的葉子節點中已經包含要查詢的數據,那麼就沒必要再回表查詢了,如果一個索引包含(覆蓋)所有需要查詢的欄位的值,那麼該索引就是覆蓋索引。簡單的說:不回表直接通過一次索引查找到列的數據就叫覆蓋索引。
表信息

CREATE TABLE `t_user` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `uname` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

舉例

--將uid設置成主鍵索引後通過下麵的SQL查詢 在explain的Extra列可以看到“Using index”
explain select uid from t_user where uid = 1;

具體參數含義可以參考另一個文章《MySQL執行計劃Explain參數詳解》

覆蓋索引在組合索引中用的比較多,舉例

explain select age,uname from t_user where age = 10 ;   

當不建立組合索引時,會進行回表查詢

設置組合索引後再次查詢

create index index_user on t_user(age,uname);

2.5 索引匹配方式

2.5.1 最左匹配

在使用組合索引中,比如設置(age,name)為組合索引,單獨使用組合索引中最左列是可以匹配索引的,如果不使用最左列則不走索引。例如下麵SQL

--走索引
explain select * from t_user where age=10 and uname='zhang';

下麵的SQL不走索引

explain select * from t_user where  uname='zhang';

2.5.2 匹配列首碼

可以匹配某一列的值的開頭部分,比如like 'abc%'。

2.5.3 匹配範圍值

可以查找某一個範圍的數據。

explain select * from t_user where age>18;

2.5.4 精確匹配某一列並範圍匹配另外一列

可以查詢第一列的全部和第二列的部分

explain select * from t_user where age=18 and uname like 'zhang%';

2.5.5 只訪問索引的查詢

查詢的時候只需要訪問索引,不需要訪問數據行,本質上就是覆蓋索引。

 explain select age,uname,update_time from t_user 
            where age=18 and uname= 'zhang' and update_time='123';

3 索引優化最佳實踐

1. 當使用索引列進行查詢的時候儘量不要使用表達式,把計算放到業務層而不是資料庫層。

--推薦
select uid,age,uname from t_user where uid=1;

--不推薦
select uid,age,uname from t_user where uid+9=10;

2. 儘量使用主鍵查詢,而不是其他索引,因為主鍵查詢不會觸發回表查詢

3. 使用首碼索引
參考2.2 首碼索引
4. 使用索引掃描排序
mysql有兩種方式可以生成有序的結果:通過排序操作或者按索引順序掃描,如果explain出來的type列的值為index,則說明mysql使用了索引掃描來做排序。
掃描索引本身是很快的,因為只需要從一條索引記錄移動到緊接著的下一條記錄。但如果索引不能覆蓋查詢所需的全部列,那麼就不得不每掃描一條索引記錄就得回表查詢一次對應的行,這基本都是隨機IO,因此按索引順序讀取數據的速度通常要比順序地全表掃描慢。
mysql可以使用同一個索引即滿足排序,又用於查找行,如果可能的話,設計索引時應該儘可能地同時滿足這兩種任務。
只有當索引的列順序和order by子句的順序完全一致,並且所有列的排序方式都一樣時,mysql才能夠使用索引來對結果進行排序,如果查詢需要關聯多張表,則只有當orderby子句引用的欄位全部為第一張表時,才能使用索引做排序。order by子句和查找型查詢的限制是一樣的,需要滿足索引的最左首碼的要求,否則,mysql都需要執行順序操作,而無法利用索引排序。
舉例
表結構及數據MySQL官網或GItHub下載

CREATE TABLE `rental` (
  `rental_id` int(11) NOT NULL AUTO_INCREMENT,
  `rental_date` datetime NOT NULL,
  `inventory_id` mediumint(8) unsigned NOT NULL,
  `customer_id` smallint(5) unsigned NOT NULL,
  `return_date` datetime DEFAULT NULL,
  `staff_id` tinyint(3) unsigned NOT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`rental_id`),
  UNIQUE KEY `rental_date` (`rental_date`,`inventory_id`,`customer_id`),
  KEY `idx_fk_inventory_id` (`inventory_id`),
  KEY `idx_fk_customer_id` (`customer_id`),
  KEY `idx_fk_staff_id` (`staff_id`),
  CONSTRAINT `fk_rental_customer` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`customer_id`) ON UPDATE CASCADE,
  CONSTRAINT `fk_rental_inventory` FOREIGN KEY (`inventory_id`) REFERENCES `inventory` (`inventory_id`) ON UPDATE CASCADE,
  CONSTRAINT `fk_rental_staff` FOREIGN KEY (`staff_id`) REFERENCES `staff` (`staff_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=16050 DEFAULT CHARSET=utf8mb4;

rental表在rental_date,inventory_id,customer_id上有rental_date的索引。使用rental_date索引為下麵的查詢做排序

--該查詢為索引的第一列提供了常量條件,而使用第二列進行排序,將兩個列組合在一起,就形成了索引的最左首碼
explain select rental_id,staff_id from rental 
where rental_date='2005-05-25' order by inventory_id desc

--下麵的查詢不會利用索引
explain select rental_id,staff_id from rental 
where rental_date>'2005-05-25' order by rental_date,inventory_id

5. union all,in,or都能夠使用索引,但是推薦使用in

explain select * from actor where actor_id = 1 union all select * from actor where actor_id = 2;
explain select * from actor where actor_id in (1,2);
explain select * from actor where actor_id = 1 or actor_id =2;

6. 範圍列可以用到索引
範圍條件是:<、<=、>、>=、between。範圍列可以用到索引,但是範圍列後面的列無法用到索引,索引最多用於一個範圍列。

7. 更新十分頻繁,數據區分度不高的欄位上不宜建立索引

  • 更新會變更B+樹,更新頻繁的欄位建議索引會大大降低資料庫性能;
  • 類似於性別這類區分不大的屬性,建立索引是沒有意義的,不能有效的過濾數據;
  • 一般區分度在80%以上的時候就可以建立索引,區分度可以使用 count(distinct(列名))/count(*) 來計算;

8. 創建索引的列,不允許為null,可能會得到不符合預期的結果

9.當需要進行表連接的時候,最好不要超過三張表,如果需要join的欄位,數據類型必須一致

10. 能使用limit的時候儘量使用limit

11. 單表索引建議控制在5個以內

12. 單索引欄位數不允許超過5個(組合索引)

13. 創建索引的時候應該避免以下錯誤概念

  • 索引越多越好
  • 過早優化,在不瞭解系統的情況下進行優化

4 索引監控

show status like 'Handler_read%';

參數 說明
Handler_read_first 讀取索引第一個條目的次數
Handler_read_key 通過index獲取數據的次數
Handler_read_last 讀取索引最後一個條目的次數
Handler_read_next 通過索引讀取下一條數據的次數
Handler_read_prev 通過索引讀取上一條數據的次數
Handler_read_rnd 從固定位置讀取數據的次數
Handler_read_rnd_next 從數據節點讀取下一條數據的次數

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前排提醒: 由於 Microsoft Docs 全是機翻。所以本文表格是我人腦補翻+審校。 如果有紕漏、模糊及時評論反饋。 序列式容器 序列容器是指在邏輯上以線性排列方式存儲給定類型元素的容器。 這些容器和數組非常類似,都是在邏輯上連續的(但記憶體不一定是連續的),與數組不同的是,容器可以非常方便的動 ...
  • 前言 今天玩啥?10行代碼夠玩嗎? Python憑藉其簡潔的代碼,贏得了許多開發者的喜愛。因此也就促使了更多開發者用Python開發新的模塊,從而形成良性迴圈, Python可以憑藉更加簡短的代碼實現許多有趣的操作。下麵我們來看看,我們用不超過10行代碼能實現些什麼有趣的功能。 一、生成二維碼 二維 ...
  • 高併發、多線程一直是Java編程中的難點,也是面試題中的要點。Java開發者也一直在嘗試使用多線程來解決應用伺服器的併發問題。但是多線程並不容易,為此一個新的技術出現了,這就是虛擬線程。 傳統多線程的痛點 但是編寫多線程代碼是非常不容易的,難以控制的執行順序,共用變數的線程安全性,異常的可觀察性等等 ...
  • 定時執行任務-springboot 先看兩個介面 這兩個介面springboot已經幫我們封裝好了,我們不需要去手動使用 TaskScheduler //任務調度者 TaskExecutor //任務執行者 具體步驟: 在啟動類上添加這個註解: @EnableScheduling//開啟定時功能的註 ...
  • 繪圖 很多程式如各種小游戲都需要在視窗中繪製各種圖形,除此之外,即使在開發JavaEE項目時,有時候也必須"動態"地向客戶 端生成各種圖形、圖表,比如 圖形驗證碼、統計圖等,這都需要利用AWT的繪圖功能。 組件繪圖原理 之前我們已經學習過很多組件,例如Button、Frame、Checkbox等等, ...
  • XName:該Cookie的名稱。一旦創建,該名稱便不可更改。 Value:該Cookie的值。如果值為Unicode字元,需要為字元編碼。如果值為二進位數據,則需要使用BASE64編碼。 Domain:可以訪問該Cookie的功能變數名稱。例如,如果設置為.zhihu.com,則所有以zhihu.com, ...
  • 1.環境準備 環境準備的統一資源提取碼為:1234 1.下載 VMware14中文版 14.1.3 VM14虛擬機 2.下載CentOs系統,建議版本最低7.0+ 3.安裝虛擬機,如果有雲伺服器,就不需要安裝虛擬機了 4.下載XFtp 和 XShell 5.下載Redis在Linux系統下的安裝包, ...
  • Qt 重載QComboBox,實現右側刪除鍵 前言 最近在做一個項目,這個項目用到一個QComboBox下拉框,做出來之後,功能都是能夠正常實現的。但是我想要實現之前看到的一個網頁的下拉框效果: 所以我就琢磨怎麼來實現這種效果。 方案 簡單來說,就是當QComboBox有選中內容時,滑鼠移動到右側下 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...