最左前綴原則,就是最左優先,在建立多列索引時,要根據業務需求,where 子句中使用最頻繁的一列放在最左邊。 當咱們建立一個組合索引的時候,如 (a1,a2,a3),至關於建立了(a1)、(a1,a2)和(a1,a2,a3)三個索引,這就是最左匹配原則。html
先舉一個遵循最佳左前綴法則的例子mysql
## 假設ab是聯合索引
select * from testTable where a=1 and b=2
複製代碼
再來看看不遵循最佳左前綴的例子sql
select * from testTable where b=2
複製代碼
咱們來回想一下b有順序的前提:在a肯定的狀況下: 如今你的a都飛了,那b確定是不能肯定順序的,在一個無序的B+樹上是沒法用二分查找來定位到b字段的。 因此這個時候,是用不上索引的。你們懂了嗎?數據庫
範圍查詢右邊失效原理緩存
select * from testTable where a>1 and b=2
複製代碼
導讀:[官網解釋索引條件下推(ICP)](dev.mysql.com/doc/refman/…bash
如今你的a都飛了,那b確定是不能肯定順序的,在一個無序的B+樹上是沒法用二分查找來定位到b字段的。服務器
因此這個時候,是用不上索引的。你們懂了嗎?-condition-pushdown-optimization.html)markdown
索引下推(index condition pushdown )簡稱ICP,在Mysql5.6的版本上推出,用於優化查詢。函數
在不使用ICP的狀況下,在使用非主鍵索引(又叫普通索引或者二級索引)進行查詢時,存儲引擎經過索引檢索到數據,而後返回給MySQL服務器,服務器而後判斷數據是否符合條件 。oop
在使用ICP的狀況下,若是存在某些被索引的列的判斷條件時,MySQL服務器將這一部分判斷條件傳遞給存儲引擎,而後由存儲引擎經過判斷索引是否符合MySQL服務器傳遞的條件,只有當索引符合條件時纔會將數據檢索出來返回給MySQL服務器 。
索引條件下推優化能夠減小存儲引擎查詢基礎表的次數,也能夠減小MySQL服務器從存儲引擎接收數據的次數。
在開始以前先準備一張用戶表(user),其中主要幾個字段有:id、name、age、address。創建聯合索引(name,age)。
假設有一個需求,要求匹配姓名第一個爲陳的全部用戶,sql語句以下:
SELECT * from user where name like '陳%'
複製代碼
問題來了,若是有其餘的條件呢?假設又有一個需求,要求匹配姓名第一個字爲陳,年齡爲20歲的用戶,此時的sql語句以下:
SELECT * from user where name like '陳%' and age=20
複製代碼
Mysql5.6以前的版本
會忽略age這個字段,直接經過name進行查詢,在(name,age)這課樹上查找到了兩個結果,id分別爲2,1,而後拿着取到的id值一次次的回表查詢,所以這個過程須要回表兩次。
Mysql5.6及以後版本
InnoDB並無忽略age這個字段,而是在索引內部就判斷了age是否等於20,對於不等於20的記錄直接跳過,所以在(name,age)這棵索引樹中只匹配到了一個記錄,此時拿着這個id去主鍵索引樹中回表查詢所有數據,這個過程只須要回表一次。
固然上述的分析只是原理上的,咱們能夠實戰分析一下,所以陳某裝了Mysql5.6版本的Mysql,解析了上述的語句,以下圖
set optimizer_switch='index_condition_pushdown=off';
複製代碼
前綴索引也叫局部索引,好比給身份證的前 10 位添加索引,相似這種給某列部分信息添加索引的方式叫作前綴索引。
前綴索引能有效減少索引文件的大小,讓每一個索引頁能夠保存更多的索引值,從而提升了索引查詢的速度。但前綴索引也有它的缺點,不能在 order by 或者 group by 中觸發前綴索引,也不能把它們用於覆蓋索引。
當字符串自己可能比較長,並且前幾個字符就開始不相同,適合使用前綴索引;相反狀況下不適合使用前綴索引,好比,整個字段的長度爲 20,索引選擇性爲 0.9,而咱們對前 10 個字符創建前綴索引其選擇性也只有 0.5,那麼咱們須要繼續加大前綴字符的長度,可是這個時候前綴索引的優點已經不明顯,就沒有建立前綴索引的必要了
舉例說明
當要索引的列字符不少時 索引則會很大且變慢,能夠只索引列開始的部分字符串 節約索引空間 從而提升索引效率
例如:如今有一個地區表,發現 area 字段不少都是以 china 開頭的,那麼若是之前1-5位字符作前綴索引就會出現大量索引值重複的狀況,索引值重複性越低 查詢效率也就越高
前綴索引測試
// 建立一個測試表\
CREATE TABLE `x_test` (\
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,\
`x_name` varchar(255) NOT NULL,\
`x_time` int(10) NOT NULL,\
PRIMARY KEY (`id`)\
) ENGINE=InnoDB AUTO_INCREMENT=4145025 DEFAULT CHARSET=utf8mb4\
\
// 添加200萬條測試數據\
INSERT INTO x_test(x_name,x_time)
SELECT CONCAT(rand()*3300102,x_name),x_time
FROM x_test
WHERE id < 30000;
複製代碼
200萬 測試數據
SELECT * FROM x_test WHERE
x_name = '1892008.205824857823401.800099203178258.8904820949682635656.62526521254';
----
查詢時間:2.253s
複製代碼
alter table x_test add index(x_name(1))
再次查詢相同sql語句
SELECT * FROM x_test WHERE
x_name = '1892008.205824857823401.800099203178258.8904820949682635656.62526521254';
---
查詢時間:3.291s
複製代碼
當使用第一位字符建立前綴索引後 貌似查詢的時間更長了,由於只第一位字符而言索引值的重讀性太大了。200萬條數據全以數字開頭那麼平均20萬條的數據都是相同的索引值。
alter table x_test add index(x_name(4));
再次查詢相同sql語句
SELECT * FROM x_test WHERE
x_name = '1892008.205824857823401.800099203178258.8904820949682635656.62526521254';
---
查詢時間:0.703s
複製代碼
此次之前4位建立索引 大大減小了索引值的重複性 查詢速度從3秒提高到0.7秒
也就是之前7位來作索引則不會出現重複索引值的狀況了
alter table x_test add index(x_name(7));
再次查詢相同sql語句
SELECT * FROM x_test WHERE
x_name = '1892008.205824857823401.800099203178258.8904820949682635656.62526521254';
----
查詢時間:0.014s ( 首次執行無緩存狀態下 )
複製代碼
有時候須要索引很長的字符列,這會讓索引變得大且慢。一般能夠索引開始的部分字符,這樣能夠大大節約索引空間,從而提升索引效率。但這樣也會下降索引的選擇性。索引的選擇性是指不重複的索引值(也稱爲基數,cardinality)和數據表的記錄總數的比值,範圍從1/#T到1之間。索引的選擇性越高則查詢效率越高,由於選擇性高的索引可讓MySQL在查找時過濾掉更多的行。惟一索引的選擇性是1,這是最好的索引選擇性,性能也是最好的。
通常狀況下某個前綴的選擇性也是足夠高的,足以知足查詢性能。對於BLOB,TEXT,或者很長的VARCHAR類型的列,必須使用前綴索引,由於MySQL不容許索引這些列的完整長度。
訣竅在於要選擇足夠長的前綴以保證較高的選擇性,同時又不能太長(以便節約空間)。前綴應該足夠長,以使得前綴索引的選擇性接近於索引的整個列。換句話說,前綴的」基數「應該接近於完整的列的」基數「。
爲了決定前綴的合適長度,須要找到最多見的值的列表,而後和最多見的前綴列表進行比較。下面的示例是mysql官方提供的示例數據庫
在示例數據庫sakila中並無合適的例子,因此從表city中生成一個示例表,這樣就有足夠數據進行演示:
mysql> select database();
+------------+
| database() |
+------------+
| sakila |
+------------+
1 row in set (0.00 sec)
mysql> create table city_demo (city varchar(50) not null);
Query OK, 0 rows affected (0.02 sec)
mysql> insert into city_demo (city) select city from city;
Query OK, 600 rows affected (0.08 sec)
Records: 600 Duplicates: 0 Warnings: 0
mysql> insert into city_demo (city) select city from city_demo;
Query OK, 600 rows affected (0.07 sec)
Records: 600 Duplicates: 0 Warnings: 0
mysql> update city_demo set city = ( select city from city order by rand() limit 1);
Query OK, 1199 rows affected (0.95 sec)
Rows matched: 1200 Changed: 1199 Warnings: 0
mysql>
複製代碼
由於這裏使用了rand()函數,因此你的數據會與個人不一樣,固然那不影響聰明的你。
首先找到最多見的城市列表:
mysql> select count(*) as cnt, city from city_demo group by city order by cnt desc limit 10;
+-----+--------------+
| cnt | city |
+-----+--------------+
| 8 | Garden Grove |
| 7 | Escobar |
| 7 | Emeishan |
| 6 | Amroha |
| 6 | Tegal |
| 6 | Lancaster |
| 6 | Jelets |
| 6 | Ambattur |
| 6 | Yingkou |
| 6 | Monclova |
+-----+--------------+
rows in set (0.01 sec)
mysql>
複製代碼
注意到查詢結果,上面每一個值都出現了6-8次。如今查找到頻繁出現的城市前綴。先從3個前綴字母開始,而後4個,5個,6個:
mysql> select count(*) as cnt,left(city,3) as pref from city_demo group by pref order by cnt desc limit 10;
+-----+------+
| cnt | pref |
+-----+------+
| 25 | San |
| 15 | Cha |
| 12 | Bat |
| 12 | Tan |
| 11 | al- |
| 11 | Gar |
| 11 | Yin |
| 10 | Kan |
| 10 | Sou |
| 10 | Bra |
+-----+------+
10 rows in set (0.00 sec)
mysql> select count(*) as cnt,left(city,4) as pref from city_demo group by pref order by cnt desc limit 10;
+-----+------+
| cnt | pref |
+-----+------+
| 12 | San |
| 10 | Sout |
| 8 | Chan |
| 8 | Sant |
| 8 | Gard |
| 7 | Emei |
| 7 | Esco |
| 6 | Ying |
| 6 | Amro |
| 6 | Lanc |
+-----+------+
10 rows in set (0.01 sec)
mysql> select count(*) as cnt,left(city,5) as pref from city_demo group by pref order by cnt desc limit 10;
+-----+-------+
| cnt | pref |
+-----+-------+
| 10 | South |
| 8 | Garde |
| 7 | Emeis |
| 7 | Escob |
| 6 | Amroh |
| 6 | Yingk |
| 6 | Moncl |
| 6 | Lanca |
| 6 | Jelet |
| 6 | Tegal |
+-----+-------+
10 rows in set (0.01 sec)
複製代碼
mysql> select count(*) as cnt,left(city,6) as pref from city_demo group by pref order by cnt desc limit 10;
+-----+--------+
| cnt | pref |
+-----+--------+
| 8 | Garden |
| 7 | Emeish |
| 7 | Escoba |
| 6 | Amroha |
| 6 | Yingko |
| 6 | Lancas |
| 6 | Jelets |
| 6 | Tegal |
| 6 | Monclo |
| 6 | Ambatt |
+-----+--------+
rows in set (0.00 sec)
mysql>
複製代碼
經過上面改變不一樣前綴長度發現,當前綴長度爲6時,這個前綴的選擇性就接近完整列的選擇性了。甚至是同樣的。
固然還有另外更方便的方法,那就是計算完整列的選擇性,並使其前綴的選擇性接近於完整列的選擇性。下面顯示如何計算完整列的選擇性:
mysql> select count(distinct city) / count(*) from city_demo;
+---------------------------------+
| count(distinct city) / count(*) |
+---------------------------------+
| 0.4283 |
+---------------------------------+
row in set (0.05 sec)
mysql>
複製代碼
能夠在一個查詢中針對不一樣前綴長度的選擇性進行計算,這對於大表很是有用,下面給出如何在同一個查詢中計算不一樣前綴長度的選擇性:
mysql> 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
-> from city_demo;
+--------+--------+--------+--------+
| sel3 | sel4 | sel5 | sel6 |
+--------+--------+--------+--------+
| 0.3367 | 0.4075 | 0.4208 | 0.4267 |
+--------+--------+--------+--------+
1 row in set (0.01 sec)
mysql>
複製代碼
能夠看見當索引前綴爲6時的基數是0.4267,已經接近完整列選擇性0.4283。
在上面的示例中,已經找到了合適的前綴長度,下面建立前綴索引:
mysql> alter table city_demo add key (city(6));
Query OK, 0 rows affected (0.19 sec)
Records: 0 Duplicates: 0 Warnings: 0
複製代碼
mysql> explain select * from city_demo where city like 'Jinch%';
+----+-------------+-----------+-------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+-------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | city_demo | range | city | city | 20 | NULL | 2 | Using where |
+----+-------------+-----------+-------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
複製代碼
能夠看見正確使用剛建立的索引。
前綴索引是一種能使索引更小,更快的有效辦法,但另外一方面也有其缺點:
mysql沒法使用其前綴索引作ORDER BY和GROUP BY,也沒法使用前綴索引作覆蓋掃描。