一叠荠、前置索引的好處
使用前置索引的好處
Normally, the entire value of a column is used to build the index - this is fine for short data types (integers and the like) but can result in a lot of data in the index for longer data types (CHAR and VARCHAR, for example). Using an index prefix allows you to make a trade off between the space required for the index and the cardinality of the index.
二、前置索引的坑
第一種情況
- 索引的長度和對應(yīng)字段中的長度一致(name 與 idx_of_name)
| areas | CREATE TABLE `areas` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`areaid` varchar(20) NOT NULL,
`name` varchar(50) NOT NULL,
`cityid` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_of_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=3145 DEFAULT CHARSET=utf8 |
+-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> explain select id, name from areas order by name;
+----+-------------+-------+-------+---------------+-------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+-------------+---------+------+------+-------------+
| 1 | SIMPLE | areas | index | NULL | idx_of_name | 152 | NULL | 3144 | Using index |
+----+-------------+-------+-------+---------------+-------------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql> explain select * from areas where name = '龍?zhí)秴^(qū)';
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-----------------------+
| 1 | SIMPLE | areas | ref | idx_of_name | idx_of_name | 152 | const | 1 | Using index condition |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-----------------------+
1 row in set (0.01 sec)
mysql> explain select id, name from areas group by name;
+----+-------------+-------+-------+---------------+-------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+-------------+---------+------+------+-------------+
| 1 | SIMPLE | areas | index | idx_of_name | idx_of_name | 152 | NULL | 3144 | Using index |
+----+-------------+-------+-------+---------------+-------------+---------+------+------+-------------+
1 row in set (0.01 sec)
第二種情況:
- 索引的長度和對應(yīng)字段中的長度不一致(name 與 idx_of_name)
| areas | CREATE TABLE `areas` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`areaid` varchar(20) NOT NULL,
`name` varchar(50) NOT NULL,
`cityid` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_of_name` (`name`(6))
) ENGINE=InnoDB AUTO_INCREMENT=3145 DEFAULT CHARSET=utf8 |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> explain select id, name from areas order by name;
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
| 1 | SIMPLE | areas | ALL | NULL | NULL | NULL | NULL | 3144 | Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+------+----------------+
1 row in set (0.01 sec)
mysql> explain select * from areas where name = '龍?zhí)秴^(qū)';
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+
| 1 | SIMPLE | areas | ref | idx_of_name | idx_of_name | 20 | const | 1 | Using where |
+----+-------------+-------+------+---------------+-------------+---------+-------+------+-------------+
1 row in set (0.01 sec)
mysql> explain select id, name from areas group by name;
+----+-------------+-------+------+---------------+------+---------+------+------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+---------------------------------+
| 1 | SIMPLE | areas | ALL | NULL | NULL | NULL | NULL | 3144 | Using temporary; Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+------+---------------------------------+
1 row in set (0.01 sec)
雖然在查詢上用到了索引刘陶,但是在排序時请契,索引不生效咳榜。
使用索引就一定能加快查詢效率么?不一定爽锥。上面的例子就告訴我們涌韩,前置索引
并不是一個萬能藥,它的確可以幫助我們在一個過長的字段中建立索引氯夷。但同時也會導(dǎo)致排序(order by, group by)查詢都無法使用前置索引臣樱。
三、如何計算出前置索引的最佳長度
關(guān)于如何計算出,最恰當(dāng)?shù)脑O(shè)置索引的長度雇毫,總體思想:
# 全列選擇性
SELECT COUNT(DISTINCT column_name) / COUNT(*) FROM table_name;
# 測試某一長度前綴的選擇性
SELECT COUNT(DISTINCT LEFT(column_name, prefix_length)) / COUNT(*) FROM table_name;
當(dāng)前置的選擇性越接近全列的選擇性的時候玄捕,索引效果越好。
通匙炻#可以索引開始的幾個字符桩盲,而不是全部值,以節(jié)約空間并取得好的性能席吴。這使索引需要的空間變小赌结,但是也會降低選擇性。索引選擇性不重復(fù)的索引值和表中所有行的比值孝冒。高選擇性的索引有好處柬姚,因為它使MySQL在查找匹配的時候可以過濾更多的行。唯一索引的選擇率為1庄涡,為最佳值
量承。
具體操作如下:
CREATE TABLE `areas` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`areaid` varchar(20) NOT NULL,
`name` varchar(50) NOT NULL,
`cityid` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3145 DEFAULT CHARSET=utf8 |
+-------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select count(distinct(name))/count(id) from areas;
+---------------------------------+
| count(distinct(name))/count(id) |
+---------------------------------+
| 0.8954 |
+---------------------------------+
1 row in set (0.01 sec)
mysql> select count(distinct left(name, 2))/count(id) from areas;
+-----------------------------------------+
| count(distinct left(name, 2))/count(id) |
+-----------------------------------------+
| 0.8648 |
+-----------------------------------------+
1 row in set (0.00 sec)
mysql> select count(distinct left(name, 3))/count(id) from areas;
+-----------------------------------------+
| count(distinct left(name, 3))/count(id) |
+-----------------------------------------+
| 0.8909 |
+-----------------------------------------+
1 row in set (0.01 sec)
mysql> select count(distinct left(name, 5))/count(id) from areas;
+-----------------------------------------+
| count(distinct left(name, 5))/count(id) |
+-----------------------------------------+
| 0.8941 |
+-----------------------------------------+
1 row in set (0.01 sec)
mysql> select count(distinct left(name, 6))/count(id) from areas;
+-----------------------------------------+
| count(distinct left(name, 6))/count(id) |
+-----------------------------------------+
| 0.8954 |
+-----------------------------------------+
1 row in set (0.00 sec)
前置索引為6時,選擇性和列值選擇性相同穴店。那么就設(shè)定該索引的長度為6位撕捍。
mysql> select max(length(name)) from areas;
+-------------------+
| max(length(name)) |
+-------------------+
| 45 |
+-------------------+
1 row in set (0.11 sec)
mysql> select * from areas where length(name) = 45;
+------+--------+-----------------------------------------------+--------+
| id | areaid | name | cityid |
+------+--------+-----------------------------------------------+--------+
| 2624 | 530925 | 雙江拉祜族佤族布朗族傣族自治縣 | 530900 |
| 2965 | 622927 | 積石山保安族東鄉(xiāng)族撒拉族自治縣 | 622900 |
+------+--------+-----------------------------------------------+--------+
2 rows in set (0.01 sec)
四、索引的最大長度
mysql> select 255 * 3 from dual;
+---------+
| 255 * 3 |
+---------+
| 765 |
+---------+
1 row in set (0.00 sec)
mysql> show create table areas;
CREATE TABLE `areas` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`areaid` varchar(20) NOT NULL,
`name` varchar(255) NOT NULL,
`cityid` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_of_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=3145 DEFAULT CHARSET=utf8 |
mysql> alter table areas change `name` `name` varchar(256) not null;
ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes
mysql> alter table areas drop index `idx_of_name`;
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> alter table areas change `name` `name` varchar(256) not null;
Query OK, 3144 rows affected (0.06 sec)
Records: 3144 Duplicates: 0 Warnings: 0
mysql> show create table areas;
CREATE TABLE `areas` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`areaid` varchar(20) NOT NULL,
`name` varchar(256) NOT NULL,
`cityid` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3145 DEFAULT CHARSET=utf8
mysql> alter table areas add index `idx_of_name` (name);
ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes
添加索引時泣洞,如果不指定索引的長度忧风,MySQL默認(rèn)會選擇該字段的長度作為索引長度。其實這里
alter table areas add index `idx_of_name` (name)
等同于
alter table areas add index `idx_of_name` (name(256))
MySQL 索引最大 bytes 為 767球凰, 255 * 3 < 767
但是 256 * 3 > 767
所以狮腿,varchar(255)時候,創(chuàng)建默認(rèn)索引可以成功呕诉,但是varchar(256)時缘厢,創(chuàng)建默認(rèn)索引就不成功。
并且由上述列子可知甩挫,索引和字段相互影響贴硫,當(dāng)索引設(shè)置為 idx_of_name
(name(255))
時,要改變字段的長度捶闸,也會提示 Specified key was too long
錯誤夜畴。
總結(jié)
根據(jù)具體的業(yè)務(wù)需求來選擇索引,并不是索引都會加快查詢速度删壮。
參考
錯誤使用MySQL前綴索引導(dǎo)致的慢查詢
MySQL 前綴索引
mysql前綴索引
mysql省市區(qū)郵政編碼和區(qū)號級聯(lián)sql文件