Mysql字首索引
MyISAM和INNODB儲存引擎的表預設建立的都是BTREE索引,MySQL目前還不支援函式索引,但是支援字首索引,即對索引欄位的前N個字元建立索引,字首索引的長度和儲存引擎有關,但對於MYISAM儲存引擎的表索引的字首長度可以達到1000位元組,而對於INNODB來說索引長度最長為767個位元組。注意字首的限制應為位元組單位進行測量,而create table語句當中字首長度解釋為字元數。在為使用多位元組字符集指定字首長度時一定要加以考慮。
有時候需要索引很長的字元列,這會讓索引變得大且慢。通常可以索引開始的部分字元,這樣可以大大節約索引空間,從而提高索引效率。但這樣也會降低索引的選擇性。索引的選擇性是指不重複的索引值(也稱為基數,cardinality)和資料表的記錄總數的比值,範圍從1/#T到1之間。索引的選擇性越高則查詢效率越高,因為選擇性高的索引可以讓MySQL在查詢時過濾掉更多的行。唯一索引的選擇性是1,這是最好的索引選擇性,效能也是最好的。
一般情況下某個字首的選擇性也是足夠高的,足以滿足查詢效能。對於BLOB,TEXT,或者很長的VARCHAR型別的列,必須使用字首索引,因為MySQL不允許索引這些列的完整長度。
訣竅在於要選擇足夠長的字首以保證較高的選擇性,同時又不能太長(以便節約空間)。字首應該足夠長,以使得字首索引的選擇性接近於索引的整個列。換句話說,字首的”基數“應該接近於完整的列的”基數“。
為了決定字首的合適長度,需要找到最常見的值的列表,然後和最常見的字首列表進行比較。下面的示例是mysql官方提供的示例資料庫
下載地址如下:
http://downloads.mysql.com/docs/sakila-db.zip
在示例資料庫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.02sec) 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 | +-----+--------------+ 10 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 | +-----+--------+ 10 rows in set (0.00 sec) mysql>
通過上面改變不同字首長度發現,當前綴長度為6時,這個字首的選擇性就接近完整咧的選擇性了。甚至是一樣的。
當然還有另外更方便的方法,那就是計算完整列的選擇性,並使其字首的選擇性接近於完整列的選擇性。下面顯示如何計算完整列的選擇性:
mysql> select count(distinct city) / count(*) from city_demo; +---------------------------------+ | count(distinct city) / count(*) | +---------------------------------+ | 0.4283 | +---------------------------------+ 1 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(12)); gbk編碼一個字元佔2個位元組,6個字元佔12個位元組。注意索引字首以位元組作為測量! Query OK, 0 rows affected (0.19 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql>
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>
可以看見正確使用剛建立的索引。
字首索引是一種能使索引更小,更快的有效辦法,但另一方面也有其缺點:
mysql無法使用其字首索引做ORDER BY和GROUP BY,也無法使用字首索引做覆蓋掃描。