MySQL 性能調優——索引優化

本篇依然可以使用 explain 工具,分析 SQL 執行實際使用的索引。

聯合索引的最左匹配原則,非常重要的原則:

MySQL 會一直向右匹配直到遇到範圍查詢(>、<、between、like)就停止匹配,比如 where a = 3 and b = 4 and c < 5 and d = 6,如果建立 (a、b、c、d) 順序的聯合索引,d 是用不到聯合索引的,如果建立 (a、b、d、c) 順序的聯合索引則都可以用到,a、b、d 的順序可以任意調整。

= 和 in 可以亂序,比如 where a = 1 and b = 2 and c = 3 建立 (a、b、c) 聯合索引可以任意順序,MySQL 的查詢優化器會幫你優化成索引可以識別的格式。

如果建立 (a、b) 順序的聯合索引,where a = 1、where a = 1 and b =2 都會走聯合索引,而 where b = 2 就不會走聯合索引,而會走全表掃描。

索引是建立的越多越好嗎?

  • 數據量小的表不需要建立索引,建立會增加額外的索引開銷;
  • 索引會增加寫操作的成本,索引越多,修改數據所需要的時間越長;
  • 太多的索引會增加查詢優化器的選擇時間;
  • 更多的索引也意味着需要更多的空間;

索引對於數據庫的影響非常關鍵,索引的主要作用就是告訴存儲引擎如何快速的找到需要的數據。當表中的數據比較少時,索引的作用可能還不是很明顯,因爲此時表中的數據基本上可以完全緩存在內存中,就算進行全表掃描也不會太慢。而隨着表中數據越來越多,查詢頻率也越來越高,內存已經不能完全緩存所有數據的時候,索引的作用就會顯得越來越重要。

爲什麼要使用索引?

  • 索引大大減少了存儲引擎需要掃描的數據量。以 InnoDB 來說,發生一次 IO,最小的存儲單位是以頁爲單位的,所以一頁內存儲的信息越多,那麼讀取效率也就越快,默認情況下,InnoDB 一頁的大小爲 16k,由於索引的大小比數據要小的多,所以一頁內可以存儲更多的索引,因此通過索引查找所需要讀取的頁非常少,減少了存儲引擎需要掃描數據的數據量,加快了查找速度;
  • 索引可以幫助我們進行排序以避免使用臨時表;
  • 數據行的物理地址通常是隨機分佈的,採用索引進行查找,可以把隨機I/O變爲順序I/O,可以更加充分的發揮磁盤的 I/O 性能。

索引是不是越多越好?

  • 索引會增加寫操作的成本,索引越多,修改數據所需要的時間越長;
  • 太多的索引會增加查詢優化器的選擇時間;

MySQL 的索引是在存儲引擎層實現的,而不是在 MySQL 的服務器層實現的,這也就決定了不同存儲引擎上的索引可能用的方式是不同的。同是也不是所有的存儲引擎都支持所有的索引類型。即使是同一種類型的索引,在不同的存儲引擎上其底層的實現也可能不相同。

1.BTree索引和Hash索引

通常索引的數據結構是 B+ 樹,除此之外還有 Hash 結構。

1.BTree索引

二叉查找樹

二叉查找樹的查找用的是二分查找。時間複雜度 O(logn)。

優點:查詢效率非常高;
缺點:有可能變成線性查找樹,時間複雜度變爲 O(n)。

B-tree 的特徵:

  • 根節點包括 2 個孩子;
  • 樹中每個節點最多含有 m 個孩子(m >= 2);
  • 除根節點和葉節點外,其他每個節點至少有 ceil(m/2) 個孩子;
  • 所有葉子節點都位於同一層。

相比於二叉查找樹、B 樹,B+ 樹更適合用來做存儲索引,原因:

  • B+ 樹的磁盤讀寫代價更低;
  • B+ 樹的查詢效率更加穩定;
  • B+ 樹更有利於對數據庫的掃描,B+ 樹只需要遍歷葉子節點即可實現對全部關鍵字的掃描。

B-tree 索引以 B+ 樹的結構存儲數據,在 B+ 樹中,每一個葉子節點都包含一個指向下一個葉子節點的指針,這樣方便葉子節點之間的遍歷。下面看一下 B+ 樹的存儲結構:

B+ 樹的存儲結構

從圖中可以看出,B+ 樹是一種平衡的查找樹,每一個葉子節點到根節點的距離都是相同的,並且所有的記錄節點都是按照鍵值的大小來順序存放到同一層葉子節點上的,並且各個葉子節點是由指針進行連接的,這樣做的好處是方便進行快速查找。對於不同的存儲引擎來說,具體的實現可能不同,比如 InnoDB 葉子節點指向的是主鍵,MyISAM 則是指向了物理地址。

下面看一下 B-tree 索引的特點:

  • B-tree 索引能夠加快數據的查詢速度。通常情況下,索引的大小遠小於表中數據的大小,使用了 B-tree 索引後,存儲引擎就不再需要全表掃描來獲取需要的數據了,取而代之的是從索引的根節點開始搜索,在索引根節點存儲了指向下一層子節點的指針,存儲引擎根據這些指針向下一層進行查找,最終存儲引擎就可以根據 B-tree 索引找到符合要求的葉子節點;
  • B-tree 索引更適合進行範圍查找。因爲 B-tree 索引對索引是順序存儲的。

在什麼情況下可以用到 B-tree 索引?

  • 全值匹配的查詢,例如 where order_sn = ‘10001’;
  • 匹配最左前綴的查詢;
  • 匹配列前綴查詢,例如 where order_sn like ‘10%’;
  • 匹配範圍值的查詢,例如 where order_sn > ‘10001’ and order_sn < ‘90001’;
  • 精確匹配左前列並範圍匹配另外一列;
  • 只訪問索引的查詢;

B-tree 索引的使用限制?

  • 如果不是按照索引最左列開始查找,則無法使用索引;
  • 使用索引時不能跳過索引中的列;
  • Not in 和 < > 操作無法使用索引;
  • 如果查詢中有某個列的範圍查詢,則其右邊所有列都無法使用索引;

2.Hash索引

InnoDB 除了支持 BTree 索引,還支持 Hash 索引。Hash 索引性能理論上要高於 BTree 索引。

下面看一下 Hash 索引的特點:

  • Hash 索引是基於 Hash 表實現的,只有查詢條件精確匹配 Hash 索引中的所有列時,才能夠使用到 Hash 索引,也就是說 Hash 索引只能用到等值查詢中,範圍查詢和模糊查詢就不能使用 Hash 索引;
  • 對於 Hash 索引中的所有列,存儲引擎都會爲每一行計算一個 Hash 碼,Hash 索引中存儲的就是 Hash 碼,這也就是 Hash 索引只能進行全值匹配查詢的原因,因爲只有這樣,Hash 碼才能匹配,同是在 Hash 索引的表中還保存了每一個 Hash 索引所代表數據行的指針,由於 Hash 索引只存儲了鍵值和 Hash 碼以及對應行的指針,索引中並沒有保存字段的值,所以 Hash 索引的存儲結構是十分緊湊的,使得 Hash 索引找到數據的速度非常快。

Hash 索引的缺點?

  • 僅僅能滿足 “=”,“IN”,不能使用範圍查找;
  • 使用 Hash 索引查找數據必須進行二次查找,但是這一點對性能影響非常小;
  • 無法用於排序;
  • 不能利用部分索引鍵查詢;
  • 不能避免表掃描;
  • Hash 索引中 Hash 碼的計算可能存在 Hash 衝突,例如不能在姓名上使用 Hash 索引;

2.索引優化策略

正確的創建和使用索引是實現數據庫高性能查詢的基礎。我們在進行索引優化時,關注的重點通常是 where 語句中的過濾條件。

1.索引上不能使用表達式或函數

例如對索引上的日期列進行計算:

錯誤範例:

select ... from order where to_days(out_date)-to_days(current_date)<=30;

to_days(out_date) 是函數,out_date 是索引。

正確範例:

select ... from order where out_date<=date_add(current_date,interval 30 day);

經過這樣的修改,out_date 列上就不會使用函數了,這時就能正確使用到該列上的索引了。

2.前綴索引和索引的選擇性

MySQL 支持對字符串的前綴建立索引,這樣可以大大減少索引的空間,從而提高索引的查詢效率:

create index index_name on table(col_name(n));

在 create index 中指定索引寬度,對於 InnoDB,索引的最大寬度是 767 個字節,使用 UTF-8 編碼的話,算成字符大概是 255 個字符,對於 MyISAM 表,索引的最大寬度是 1000 個字節。如果超了這個最大寬度限制,是無法建立前綴索引的。雖然我們可以在大部分列上使用前綴索引增加索引效率,但是前綴索引降低了索引的選擇性。

索引的選擇性是不重複的索引值和表的記錄數的比值,索引的唯一性越高,其選擇性越高,唯一索引選擇性最高。由於前綴索引只是取了字符串數據的一部分來作爲索引的鍵值,所以其選擇性必然降低。

如果我們想在一個很長的字符串上進行查找,就只能使用前綴索引,但是會使選擇性變得很差。其實對於支持 Hash 索引的存儲引擎,使用 Hash 索引會是更好的方式。

3.聯合索引

聯合索引是由多個字段組成的索引。

聯合索引中索引列的順序尤爲重要,那麼如何選擇索引列的順序呢?

  • 經常會被使用到的列優先,即放到聯合索引的最左邊;
  • 選擇性高的列優先;
  • 寬度小的列優先;

4.覆蓋索引

覆蓋索引是 select 的數據列只用從索引中就能夠取得,不必讀取數據行,換句話說查詢列要被所建的索引覆蓋。

優點:

  • 可以優化緩存,減少磁盤 IO 操作;
  • 由於 B-tree 索引是按照索引的鍵值進行存儲的,所以對於 IO 密集型的範圍查找來說,可以減少隨機 IO,變隨機 IO 操作變爲順序 IO 操作;
  • 對於 InnoDB 存儲引擎,可以避免對 InnoDB 主鍵索引的二次查詢。通常情況下,通過索引查找到相應鍵值後,還需要通過主鍵索引二次查詢才能獲取到數據行,而在覆蓋索引中,可以通過索引獲取全部的數據,避免二次查詢,減少相關 IO 操作;
  • 對於 MyISAM 存儲引擎,可以避免 MyISAM 表進行系統調用。對於 MyISAM 來說,只會緩存索引信息,數據則依賴於操作系統來緩存,因此我們訪問數據需要進行一次系統調用,而系統調用的性能通常會比較差,而在覆蓋索引中,可以通過索引獲取全部的數據,避免系統調用的產生。覆蓋索引對於優化 MyISAM 存儲引擎更加的有效;

無法使用覆蓋索引的情況:

  • 並不是所有的存儲引擎都支持覆蓋索引,也不是所有的索引類型都能建立覆蓋索引,只有在索引的葉子節點中,包括了鍵值的索引才能建立覆蓋索引(Hash 索引就不能作爲覆蓋索引來使用);
  • 查詢中包含了太多的列,也不適合建立覆蓋索引;
  • 使用了雙 % 號的 like 查詢無法使用覆蓋索引,這是由於 MySQL 底層的 API 所限制的;

5.使用索引來優化查詢

MySQL 有兩種方式生成有序的結果:

  • 通過排序操作;
  • 按照索引順序掃描數據。

MySQL 使用索引掃描來優化排序需要滿足兩個條件:

  • 索引的列順序和 order by 子句的順序完全一致;
  • 索引中所有列的方向(升序、降序)和 order by 子句完全一致;
  • order by 中的字段全部在關聯表中的第一張表中;

例如在 InnoDB 中:

show create table rental;
explain select * from rental where rental_date>'2019-01-01' order by rental_id;
explain select * from rental where rental_date>'2019-01-01' order by rental_id\G

5.使用索引來優化鎖

InnoDB 存儲引擎使用的是行級鎖,可以通過索引減少鎖定的行數,通過索引在存儲引擎層過濾掉我們不需要的行,減少鎖帶來的開銷,來優化性能。索引可以加快數據的處理速度,同時也加快了鎖的釋放。

6.索引的維護和優化

1、刪除重複和冗餘的索引。

如何知道哪些索引是重複或冗餘的呢?

可以通過 pt-duplicate-key-checker h=127.0.0.1 工具進行檢查。

2、查找未被使用過的索引。

3、更新索引統計信息及減少索引碎片。

可以通過 SQL 語句 analyze table table_name 重新生成表的統計信息。不同存儲引擎生成和保存統計信息的方式不太相同,所以運行 analyze table table_name 的成本也不太一樣。

對於 MyISAM 表來說,索引的統計信息會存儲在磁盤中,運行 analyze table table_name 需要進行一次全索引掃描,以重新計算索引的統計信息,在這個過程中,需要對錶進行鎖定。

對於 InnoDB 表來說,不會在磁盤存儲索引的統計信息,而是通過隨機的索引訪問的方式進行評估,並將其存儲在內存中,所以 InnoDB 表執行 analyze table table_name 效率會很高,但是生成的統計信息不會十分準確,只是一個估算值。另外在 B-tree 索引更新的時候可能會產生大量的碎片,降低查詢效率。

維護索引碎片可以通過SQL 語句 optimize table table_name 來維護,使用不當可能會導致鎖表。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章