赞
踩
我们容易有以一个误区,就是在经常使用的查询条件上都建立索引,索引越多越好,那到底是不是这样呢?
因为索引对于改善查询性能的作用是巨大的,所以我们的目标是尽量使用索引。但这并不意味着索引越多越好,因为索引会占用内存,还需要维护,并且索引还会影响增删改速度(还要对(辅助)索引进行插入/删除)。
所以我们创建索引时有什么能参考的属性,或者要遵守的原则呢?
我们先来看一个重要的属性列的离散度,公式如下:
count(distinct(column_name)) : count(*) -- 列的全部不同值个数:所有数据行行数
数据行数相同的情况下,分子越大,列的离散度就越高。简单来说,如果列的重复值越多,离散度就越低,重复值越少,离散度就越高。
我们来思考一个问题:在下图所示的表中,name 上面建立索引和在 gender上面建立索引有什么区别?
当我们用在gender上建立的索引去检索数据的时候,由于重复值太多,需要扫描的行数就更多。例如,我们现在在gender列上面创建一个索引,然后看一下执行计划。
- ALTER TABLE user_innodb ADD INDEX idx_user_gender(gender); -- 创建索引(耗时比较久)
- EXPLAIN SELECT * FROM `user_innodb` WHERE gender=0;
而name的离散度更高,比如“杨八”的这名字,只需要扫描一行。
- ALTER TABLE user_innodb ADD INDEX idx_user_name(name);
- EXPLAIN SELECT * FROM `user_innodb` WHERE name='杨八';
如果在B+Tree里面的重复值太多,MySQL的优化器发现走索引跟使用全表扫描差不了多少的时候,就算建了索引,也不一定会走索引。
这个给我们的启发是什么?建立索引,要使用离散度(选择度)更高的字段。
当字段值比较长的时候,建立索引会消耗很多的空间,搜索起来也会很慢。我们可以通过截取字段的前面一部分内容建立索引,这个就叫前缀索引。
创建一张商户表,因为地址字段比较长,在地址字段上建立前缀索引
- create table shop(address varchar(120) not null);
- alter table shop add key(address(12)); -- 截取12个字符作为前缀索引是最优的吗?
问题是,截取多少呢?截取得多了,达不到节省索引存储空间的目的,截取得少了,重复内容太多,字段的散列度(选择性)会降低。怎么计算不同的长度的选择性呢?
先看一下字段在全部数据中的选择度计算公式:
- select count(distinct address) / count(*) from shop;
- select count(distinct left(address, n)) / count(*) as subn from shop;
count(distinct left(address,n)) / count(*) 的结果是会随着 n 的变大而变大。举个例子,现在有两个address(东大街长兴小区,东大街福乐小区),那么 distinct(address,2) < distinct(address,3)
==>所以,截取的长度越长就会越接近字段在全部数据中的选择度
==>所以,我们要权衡索引大小和查询速度。
举个例子,通过不同长度去计算,与全表的选择性对比:
- SELECT COUNT(DISTINCT(address))/COUNT(*) sub, -- 字段在全部数据中的选择度
- COUNT(DISTINCT(LEFT(address,5)))/COUNT(*) sub5, -- 截取前5个字符的选择度
- COUNT(DISTINCT(LEFT(address,7)))/COUNT(*) sub7,
- COUNT(DISTINCT(LEFT(address,9)))/COUNT(*) sub9,
- COUNT(DISTINCT(LEFT(address,10)))/COUNT(*) sub10, -- 截取前10个字符的选择度
- COUNT(DISTINCT(LEFT(address,11)))/COUNT(*) sub11,
- COUNT(DISTINCT(LEFT(address,12)))/COUNT(*) sub12,
- COUNT(DISTINCT(LEFT(address,13)))/COUNT(*) sub13,
- COUNT(DISTINCT(LEFT(address,15)))/COUNT(*) sub15
- FROM shop;
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
| sub | sub5 | sub7 | sub9 | sub10 | sub11 | sub12 | sub13 | sub15 |
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
| 0.9993 | 0.0225 | 0.4663 | 0.8618 | 0.9734 | 0.9914 | 0.9943 | 0.9943 | 0.9958 |
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
可以看到在截取 11 个字段时 sub11(0.9993) 就已经很接近字段在全部数据中的选择度 sub(0.9958)了,而且长度也相较后面更短一些, 综合考虑比较合适。
ALTER TABLE shop ADD KEY (address(11));
其实,用不用索引,最终都是优化器说了算。优化器是基于什么的优化器?基于cost开销(CostBaseOptimizer),它不是基于规则(Rule-BasedOptimizer),也不是基于语义。怎么样开销小就怎么来。 另外,SQL语句是否使用索引,跟数据库版本、数据量、数据选择度都有关系。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。