1、資料庫核心知識點
資料庫系列:MySQL慢查詢分析和效能最佳化
資料庫系列:MySQL索引最佳化總結(綜合版)
資料庫系列:高併發下的資料欄位變更
資料庫系列:覆蓋索引和規避回表
資料庫系列:資料庫高可用及無損擴容
資料庫系列:使用高區分度索引列提升效能
2、MySQL完整學習
MySQL全面瓦解30篇:合輯地址
1 背景
有時候我們需要在字元型別的欄位上建設索引,但是如果該欄位的值都普遍比較大的話,會讓索引變得大而且慢。
根據我們之前的瞭解,每個磁碟塊(disk)儲存的內容是有限的(InnoDB儲存引擎中頁的大小為16KB),如果一個頁中可以儲存的索引記錄越多,那麼查詢效率就會提高,因為查詢次數、查詢深度會變少。
但是索引整個字元列會讓我們索引內容特別大,會導致單個頁儲存的索引記錄減少,就需要更多的頁去儲存,B+Tree的樹深度變深,查詢效率大幅降低。
比較有效的一種辦法是指定索引的欄位長度,比如可以索引開始的部分字元,這樣可以大大節約索引空間(每個頁),從而提高索引效率。
# enp_name 如果值普遍太長會導致索引空間爆棚
create index idx_emp_empname on emp(emp_name)
# 可能合適的做法
create index idx_emp_empname on emp(emp_name(5))
2 關於索引選擇性
索引的選擇性是指不重複的索引值(也稱為基數,cardinality) 和 資料表的記錄總數的比值,範圍從1/#T到1之間。索引的選擇性越高則查詢效率越高,因為選擇性高的索引可以讓MySQL在查詢時過濾掉更多的行。唯一索引的選擇性是1,這是最好的索引選擇性,效能也是最好的。
以下圖為例,欄位emp_name的索引長度設定為4。如果不設定長度,一旦有大量長字串元素,會佔據大量的空間,而單個Disk的空間是有限的。
就會導致樹的層級很高,搜尋的IO次數變多,索引效能降低。
★說明:#T 指資料表的記錄總數
★ 參考書籍《高效能MySQL》 的5.3.2小節的第3段 : 一般情況下某個字首的選擇性也是足夠高的,足以滿足查詢效能。對於BLOB,TEXT,或者很長的VARCHAR型別的列,必須使用字首索引,因為MySQL不允許索引這些列的完整長度。
3 如何計算索引的選擇性
3.1 分析
提供一個具有500w資料的僱員表emp,準備在僱員姓名 emp_name 欄位在做索引。
emp_name有些值還是挺長的,如果全部拿來做索引,那勢必導致索引表很龐大。我們先來檢索下這個emp_name,看看有沒有什麼特徵。
1 mysql> select distinct emp_name from emp;
2 +----------+
3 | emp_name |
4 +----------+
5 | ali_user |
6 | brandy_user |
7 | cancys_user |
8 | davide_user |
9 | elasne_user |
10 | finest_user |
11 | ......... |
12 | gagnannan_user |
13 | halande_user |
14 | ivil_user |
15 | kikol_user |
16 | Cecilia_user |
17 | Abigail_user |
18 | Blanche_user |
19 | Elizabeth_user |
20 | ....... |
21 +----------+
22 4999850 row in set
從上面的資料進行分析,可以得到以下結論:
- 索引長度肯定不能太短(比如2或者3),前6個字元的區分度非常高,如果選擇2或者3,會掃出大量的資料,增加計算成本。
- 不能不設定索引的長度,字尾 _user 是一致的,沒有任何的區分度優勢,反而會佔用很多無效的儲存空間,增加 I/O次數。
- 目前看,最合適的可能是6或者7,前幾個字元的區分度很高,這只是推斷,需要用演算法判定。
3.2 字首索引的長度的判斷方法
判斷的演算法:
select count(distinct left(`c_name`,calcul_len))/count(*) from t_name;
可以套入emp表進行計算,如下:
SELECT
count(DISTINCT LEFT(emp_name, 3)) / count(*) AS sel3,
count(DISTINCT LEFT(emp_name, 4)) / count(*) AS sel4,
count(DISTINCT LEFT(emp_name, 5)) / count(*) AS sel5,
count(DISTINCT LEFT(emp_name, 6)) / count(*) AS sel6,
count(DISTINCT LEFT(emp_name, 7)) / count(*) AS sel7
FROM
emp
3.3 新增字首索引
下面是新增帶有索引長度的字首索引語法:
ALTER TABLE t_name ADD KEY (c_name[(lenth)]);
測試emp_name最合適的長度,因為empname的長度基本維持在6個字元左右,少數量超過6長度,所以指定empname索引長度時6是最建議的
mysql> SELECT
count(DISTINCT LEFT(emp_name, 3)) / count(*) AS sel3,
count(DISTINCT LEFT(emp_name, 4)) / count(*) AS sel4,
count(DISTINCT LEFT(emp_name, 5)) / count(*) AS sel5,
count(DISTINCT LEFT(emp_name, 6)) / count(*) AS sel6,
count(DISTINCT LEFT(emp_name, 7)) / count(*) AS sel7
FROM
emp;
+--------+--------+--------+--------+--------+
| sel3 | sel4 | sel5 | sel6 | sel7 |
+--------+--------+--------+--------+--------+
| 0.0012 | 0.0076 | 0.0400 | 0.1713 | 0.1713 |
+--------+--------+--------+--------+--------+
1 row in set
我們可以使用 不同的長度來測試檢索效率
當長度為2的時候,匹配度低於 0.0012,檢索效率自然比較慢
mysql> create index idx_emp_empname on emp(emp_name(2));
Query OK, 0 rows affected
Records: 0 Duplicates: 0 Warnings: 0
mysql> select * from emp where emp_name='LsHfFJA';
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| id | empno | emp_name | job | mgr | hiredate | sal | comn | depno |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| 4582071 | 4582071 | LsHfFJA | SALEMAN | 1 | 2021-01-23 16:46:03 | 2000 | 400 | 106 |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
1 row in set (1.793 sec)
當長度為6的時候,匹配度低於 0.1713,檢索效率就比較高
mysql> create index idx_emp_empname on emp(emp_name(6));
Query OK, 0 rows affected
Records: 0 Duplicates: 0 Warnings: 0
mysql> select * from emp where emp_name='LsHfFJA';
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| id | empno | emp_name | job | mgr | hiredate | sal | comn | depno |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
| 4582071 | 4582071 | LsHfFJA | SALEMAN | 1 | 2021-01-23 16:46:03 | 2000 | 400 | 106 |
+---------+---------+---------+---------+-----+---------------------+------+------+-------+
1 row in set (0.003 sec)
4 總結
選擇索引長度應該要選擇足夠長的字首以保證較高的選擇性,同時又不能太長,以便節約空間。
最好的要求就是字首的基數(cardinality)接近於完整列的基數,以達到足夠優秀的效能。