【MySQL】索引原理(四):创建索引的几点建议,列的离散度,前缀索引

我们容易有以一个误区,就是在经常使用的查询条件上都建立索引,索引越多越好,那到底是不是这样呢?

因为索引对于改善查询性能的作用是巨大的,所以我们的目标是尽量使用索引。但这并不意味着索引越多越好,因为索引会占用内存,还需要维护,并且索引还会影响增删改速度(还要对(辅助)索引进行插入/删除)。

所以我们创建索引时有什么能参考的属性,或者要遵守的原则呢?

1.列的离散度

我们先来看一个重要的属性列的离散度,公式如下:

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的优化器发现走索引跟使用全表扫描差不了多少的时候,就算建了索引,也不一定会走索引。

在这里插入图片描述

这个给我们的启发是什么?建立索引,要使用离散度(选择度)更高的字段。

2.前缀索引

当字段值比较长的时候,建立索引会消耗很多的空间,搜索起来也会很慢。我们可以通过截取字段的前面一部分内容建立索引,这个就叫前缀索引。

创建一张商户表,因为地址字段比较长,在地址字段上建立前缀索引

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));

3.几点具体的建议

  1. 索引的个数不要过多(浪费空间,更新变慢)
  2. 在用于 where 判断 order 排序和 join 的(on)字段上创建索引
  3. 区分度低的字段,例如性别,不要建索引(离散度太低,导致扫描行数过多)
  4. 更新频繁的值,不要作为主键或者索引(页分裂)
  5. 不建议用无序的值作为索引,例如身份证、UUID(在索引比较时需要转为ASCII,并且插入时可能造成页分裂)
  6. 若在多个字段都要创建索引的情况下,联合索引优于单值索引
  7. 联合索引把散列性高(区分度高)的值放在前面

其实,用不用索引,最终都是优化器说了算。优化器是基于什么的优化器?基于cost开销(CostBaseOptimizer),它不是基于规则(Rule-BasedOptimizer),也不是基于语义。怎么样开销小就怎么来。 另外,SQL语句是否使用索引,跟数据库版本、数据量、数据选择度都有关系。

猜你喜欢

转载自blog.csdn.net/qq_33762302/article/details/114006123
今日推荐