最左前缀原则,就是最左优先,在建立多列索引时,要根据业务需求,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,也没法使用前缀索引作覆盖扫描。