MySQL索引优化

        MySQL版本:5.7.31。


1 B-Tree索引

        假设有如下数据表:

CREATE TABLE People (
last_name VARCHAR ( 50 ) NOT NULL,
first_name VARCHAR ( 50 ) NOT NULL,
dob date NOT NULL,
gender enum ( 'm', 'f' ) NOT NULL,
KEY ( last_name, first_name, dob ) 
);

        B-Tree对索引列是顺序组织存储的,所其对如下类型的查询有效:

  • 全值匹配
    • 全值匹配指的是和索引中的所有列进行匹配。
  • 匹配最左前缀
    • 可用于查找所有姓为Allen的人,即指使用索引的第一列。
  • 匹配列前缀
    • 也可以只匹配某一列的值的开头部分。例如可用于查找所有以J开头的姓的人。这里也只使用了索引的第一列。
  • 匹配范围值
    • 例如可用于查找姓在Allen和Barrymore之间的人。这里也只使用了索引的第一列。
  • 精确匹配某一列并范围匹配另外一列
    • 可用于查找所有姓为Allen,并且名字是字母K开头的人。即第一列last_name全匹配,第二列first_name范围匹配。
  • 只访问索引的查询
    • B-Tree通常可以支持“只访问索引的查询”,即查询只需要访问索引,而无需访问数据行,即“覆盖索引”。

        因为索引树中的节点是有序的,所以除了按值查找之外,索引还可以用于查询中的ORDER BY操作。但是使用B-Tree索引也会有一些限制:

  • 如果不是按照索引的最左列开始查找,则无法使用索引。
  • 不能跳过索引中的列。
  • 如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找。例如有查询如下:
    SELECT
        * 
    FROM
        people 
    WHERE
        last_name = 'Hou' 
        AND first_name LIKE 'R%' 
        AND dob = '1995-02-15'
    这个查询只能使用索引的前两列,因为这里LIKE是一个范围条件。这里描述的基本原则是:尽可能将需要做范围查询的列放到索引的后面,以便优化器能使用尽可能多的索引列。

2 哈希索引

        哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,哈希码是一个较小的值,并且不同键值的行计算出来的哈希码也不一样。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。因为索引自身只需存储对应的哈希值,所以索引的结构十分紧凑,这也让哈希索引查找的速度非常快。然而,哈希索引也有它的限制:

  • 哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序。
  • 哈希索引也不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容来计算哈希值的。例如,在数据列(A,B)上建立哈希索引,如果查询只有数据列A,则无法使用该索引。
  • 哈希索引只支持等值比较查询,包括=、IN()、<=>。但不支持任何范围查询,例如WHERE price > 100。
  • 如果哈希冲突很多的话,一些索引维护操作的代价也会很高。例如,如果在某个选择性很低(哈希冲突很多)的列上建立哈希索引,那么当从表中删除一行时,存储引擎需要遍历对应哈希值的链表中的每一行,找到并删除对应行的引用,冲突越多,代价越大。

3 索引策略

3.1 独立的列

        我们通常会看到一些查询不当地使用索引,或者使得MySQL无法使用已有的索引。如果查询中的列不是独立的,则MySQL就不会使用索引。“独立的列”是指索引列不能是表达式的一部分,也不能是函数的参数。如下所示:

SELECT
    actor_id 
FROM
    sakila.actor 
WHERE
    actor_id + 1 = 5;
SELECT
    ...
WHERE
    TO_DAYS( CURRENT_DATE ) - TO_DAYS( date_col ) <= 10;

        另一个例子如下,这里hire_time字段已经有了索引:

EXPLAIN select * from employees where date(hire_time)='2018-09-30';

        转化为日期范围查询,会走索引:

EXPLAIN select * from employees where hire_time>='2018-09-30 00:00:00' and hire_time<='2018-09-30 23:59:59'

3.2 使用索引扫描来做排序

        只有当索引的列顺序和ORDER BY子句的顺序完全一致,并且所有列的排序方向(倒序或正序)都一样时,MySQL才能够使用索引来对结果做排序。如果查询需要关联多张表,则只有当ORDER BY子句引用的字段全部为第一个表时,才能使用索引做排序。ORDER BY子句和查找型查询的限制是一样的:需要满足索引的最左前缀的要求;否则,MySQL都需要执行排序操作,而无法利用索引排序。

        有一种情况下ORDER BY子句可以不满足索引的最左前缀的要求,就是前导列为常量的时候。如果WHERE子句或者JOIN子句中对这些列指定了常量,就可以“弥补”索引的不足。例如下面的索引和对应的SQL:

UNIQUE KEY rental_date ( rental_date, inventory_id, customer_id ),
SELECT
    rental_id,
    staff_id 
FROM
    sakila.rental 
WHERE
    rental_date = '2029-02-29' 
ORDER BY
    inventory_id,
    customer_id

        另外需要说明的是,如果order by的条件不在索引列上,就会产生Using filesort,即文件排序。文件排序分为单路排序双路排序两种:

  • 单路排序:是一次性取出满足条件行的所有字段,然后在sort buffer中进行排序
  • 双路排序(又叫回表排序模式):是首先根据相应的条件取出相应的排序字段和可以直接定位行数据的行ID,即主键,然后在sort buffer中进行排序,排序完后需要再次取回其它需要的字段

        MySQL 通过比较系统变量 max_length_for_sort_data(默认1024字节)的大小和需要查询的字段总大小来判断使用哪种排序模式。

  • 如果 max_length_for_sort_data 比查询字段的总长度大,那么使用 单路排序模式
  • 如果 max_length_for_sort_data 比查询字段的总长度小,那么使用 双路排序模式

        group by与order by的优化思路是类似的,其实质是先排序后分组,遵照索引创建顺序的最左前缀法则。如果不需要排序,对于group by的优化可以加上order by null禁止排序。还有一点,where的优先级是要高于having的,所以能写在where中的限定条件就不要去having中进行限定了。

3.3 索引的取舍

        一些列的选择性通常不高,但可能很多查询都会用到,所以考虑到使用的频率,还是建议在创建不同组合索引的时候将该列作为前缀。但这违背了不应该在选择性低的列上创建索引的经验,但是可以通过下面的“诀窍”绕过:假如说该列是性别列SEX,如果某个查询不限制性别,那么可以通过在查询条件中新增AND SEX IN ( 'm', 'f' )来让MySQL选择该索引。这样写并不会过滤任何行,和没有这个条件时的结果相同。但是必须加上这个列的条件,MySQL才能够匹配索引的最左前缀。这个“诀窍”在这类场景中非常有效,但如果列有太多不同的值,就会让IN ()列表太长,这样做就不行了。

3.4 存储引擎不能使用索引中范围条件右边的列

        这里建立了一个联合索引:(name,age,position)

EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manager';

EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age > 22 AND position ='manager';

        从key_len列可以看到,联合索引没有用到范围索引右边的列。

3.5 != 或者 <> 无法使用索引

EXPLAIN SELECT * FROM employees WHERE name != 'LiLei';

3.6 is null,is not null 无法使用索引

EXPLAIN SELECT * FROM employees WHERE name is null;

        所以一般情况下建议设置字段为非空,给一个默认值。

3.7 like以通配符开头,mysql索引会失效变成全表扫描操作

EXPLAIN SELECT * FROM employees WHERE name like '%Lei';

EXPLAIN SELECT * FROM employees WHERE name like 'Lei%';

        假设有索引(a,b,c):

Where语句索引是否被使用
where a = 3Y,使用到a
where a = 3 and b = 5Y,使用到a,b
where a = 3 and b = 5 and c = 4Y,使用到a,b,c
where b = 3 或者 where b = 3 and c = 4 或者 where c = 4N
where a = 3 and c = 5Y,只用到a,但是c不可以,b中间断了
where a = 3 and b > 4 and c = 5Y,使用到a和b,c不能在范围之后,b断了
where a = 3 and b like 'kk%' and c = 4Y,使用到a,b,c
where a = 3 and b like '%kk' and c = 4Y,只用到a
where a = 3 and b like '%kk%' and c = 4Y,只用到a
where a = 3 and b like 'k%kk%' and c = 4Y,使用到a,b,c

        like KK%相当于常量,%KK和%KK%相当于范围。

        如果实在是要用双百分号的模糊查询,可以考虑使用覆盖索引:

EXPLAIN SELECT name,age,position FROM employees WHERE name LIKE '%Lei'

        如果覆盖索引不能满足要求的话,可以考虑使用ES。

3.8 字符串不加单引号索引失效

EXPLAIN SELECT * FROM employees WHERE name = '1000';

EXPLAIN SELECT * FROM employees WHERE name = 1000;

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL索引优化是提高查询性能的关键。下面是一些优化MySQL索引的方法: 1. 选择合适的索引 索引并非越多越好,需要根据实际情况选择合适的索引。通常来说,可以根据以下几个原则来选择索引: - 对经常查询的列进行索引 - 对频繁用于WHERE、JOIN、ORDER BY和GROUP BY子句的列进行索引 - 对选择性高的列进行索引(即不同值越多的列) 2. 删除不必要的索引 过多的索引会占用过多的磁盘空间并降低写操作的性能,因此需要删除不必要的索引。可以使用如下语句查询所有的索引,并根据实际情况删除不必要的索引: ``` SHOW INDEX FROM table_name; ``` 3. 避免全表扫描 全表扫描是指MySQL查询时没有使用索引,而是扫描整个表的数据。全表扫描会导致查询效率低下,因此需要避免全表扫描。可以通过优化查询语句,例如添加索引、改变表结构等方式来避免全表扫描。 4. 使用覆盖索引 覆盖索引是指查询语句只需要使用索引中的列就可以返回查询结果,而不需要再去查询表中的数据。覆盖索引可以减少查询的IO操作,提高查询效率。 5. 定期维护索引 索引需要定期维护,包括优化查询语句、删除不必要的索引、重新构建索引等。可以使用MySQL自带的OPTIMIZE TABLE命令来优化表的索引和碎片,从而提高查询性能。 以上就是MySQL索引优化的一些方法,需要根据实际情况进行选择和优化。需要注意的是,不同的索引优化策略对于不同的数据库环境和数据结构可能会有所不同,需要根据实际情况进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值