前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >性能优化|索引优化最佳实战

性能优化|索引优化最佳实战

作者头像
AI码师
发布2020-11-19 15:54:53
3930
发布2020-11-19 15:54:53
举报

点击上方蓝字关注我们 文末有惊喜

初始化SQL

代码语言:javascript
复制
CREATE TABLE `student` (
 `id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
 `stu_name` VARCHAR ( 24 ) NOT NULL DEFAULT '' COMMENT '学生姓名',
 `stu_age` INT ( 11 ) NOT NULL DEFAULT '0' COMMENT '学生年龄',
 `stu_addr` VARCHAR ( 20 ) NOT NULL DEFAULT '' COMMENT '地址',
 `birthday` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '出生日期',
 PRIMARY KEY ( `id` ),
 KEY `idx_name_age_addr` ( `stu_name`, `stu_age`, `stu_addr` ) USING BTREE 
) ENGINE = INNODB AUTO_INCREMENT = 4 DEFAULT CHARSET = utf8 COMMENT = '学生档案表';
INSERT INTO student ( stu_name, stu_age, stu_addr, birthday )
VALUES
 (
  '刘备',
  100,
  '三国蜀',
 NOW());
INSERT INTO student ( stu_name, stu_age, stu_addr, birthday )
VALUES
 (
  '张飞',
  110,
  '三国蜀',
 NOW());
INSERT INTO student ( stu_name, stu_age, stu_addr, birthday )
VALUES
 (
  '关羽',
  90,
 '三国蜀',
 NOW());

表描述:创建了一个联合索引和一个主键索引

索引优化常见手段

全值匹配

代码语言:javascript
复制
EXPLAIN SELECT * FROM student WHERE stu_name= '关羽';

在这里插入图片描述

查看索引长度是74=(3*24+2),可以算出联合索引中只使用了name前缀索引.

代码语言:javascript
复制
EXPLAIN SELECT * FROM student WHERE stu_name= '关羽' and stu_age = 20;

查看索引长度是78=(3*24+2)+4,可以算出联合索引中只使用了stu_name和stu_age前缀索引.

代码语言:javascript
复制
EXPLAIN SELECT * FROM student WHERE stu_name= '关羽' and stu_age = 20 and stu_addr = '三国蜀'

查看索引长度是140=(324+2)+4+(320+2),可以算出联合索引中只使用了完整的联合索引

最左前缀索引

代码语言:javascript
复制
EXPLAIN SELECT * FROM student WHERE stu_name= '关羽'

可以看出使用到了前缀索引。

代码语言:javascript
复制
EXPLAIN SELECT * FROM student WHERE stu_age= 20;

key_len为空,说明没有使用索引,因为违背了最左前缀规则,跳过了name,直接使用age,mysql就直接使用全表扫描了。

代码语言:javascript
复制
EXPLAIN SELECT * FROM student WHERE stu_age= 20 and stu_name = '刘备' and stu_addr ='三国';

在这里插入图片描述

看截图,mysql使用了联合索引,但是命名不遵循最左前缀原则啊,这是因为mysql5.7中。在执行器中,mysql会对sql语句进行优化,调整三个字段的顺序,满足最左前原则。

不要在索引列上使用函数计算和类型转换,否则会导致索引失效,使用全表扫描

代码语言:javascript
复制
EXPLAIN SELECT * FROM student WHERE left(stu_name,2) = '张飞'

按照最左前缀法则,上面的语句应该会使用索引的,但是由于在索引字段上面使用了函数,导致索引失效,mysql在使用优化器的时候,发现索引字段上面使用了函数,将会放弃索引查找,因为它觉得全表扫描会更快。

在这里插入图片描述

代码语言:javascript
复制
explain select * from student where stu_name =1000

stu_name 发生了隐式类型转换,导致索引失效。

索引字段使用了范围查询,右边索引字段将不会使用索引查询,因为使用范围查询后,索引不能按顺序进行比较,则会直接使用全表扫描

代码语言:javascript
复制
EXPLAIN SELECT * FROM student WHERE stu_name = '张飞' and stu_age > 100 and stu_addr = '吴国';

在这里插入图片描述

从索引长度上看,应该只走了name+age的联合索引,addr没用联合索引查询。

select查询中,最好只包括索引字段,不要全字段查询

代码语言:javascript
复制
explain select stu_name from student where stu_name = '刘备'
代码语言:javascript
复制
explain select * from student where stu_name = '刘备'

在这里插入图片描述

第二条语句extra字段值为空,但是第一条是using index,说明使用了索引覆盖,第二条语句为空的原因是mysql在索引树上面查询到数据之后,还需要把去主键索引表中提取数据完整记录;而第一条定位到数据后,mysql看到select 里面的字段在索引内,所以就直接能拿到数据。

在where语句中,尽量不要使用不等于、is null,is not null等比较语句,因为mysql只能使用全表扫描 完成查询,非空判断的替换语句可以为字段设置默认值,查询的时候和这个默认值进行比较。

代码语言:javascript
复制
explain select * from student where stu_name != '刘备'

索引失效

不要用范围字段 in、or等,mysql会根据检索数据量、表大小等多个因素决定是否使用索引

以 ‘%’开头的模糊查询语句,会导致索引失效。

代码语言:javascript
复制
explain select * from student where stu_name like '%备%'

在这里插入图片描述

因为我们开发过程中模糊查询肯定是必须的,那么怎么优化呢?

使用索引覆盖,select 查询的字段包括在索引中
代码语言:javascript
复制
explain select stu_name from student where stu_name like '%备%'

深入索引优化

我们很好奇,mysql是如何选择合适的索引的?

其实在mysql底层,使用了一个trace的工具,帮助你分析每条SQL语句,它会按照索引去查询语句,预估消耗的时间,然后再按照全表扫描,预估消耗的时间,最后比较两者消耗时间,选择是否走索引还是全表扫描,执行如下sql,查看结果2,分析trace字段的结果:

代码语言:javascript
复制

SET SESSION optimizer_trace = "enabled=on",
end_markers_in_json = ON;

SELECT
* 
FROM
 student 
WHERE
 stu_age > 10 
ORDER BY
 stu_age;
SELECT
* 
FROM
 information_schema.OPTIMIZER_TRACE;

预估表的访问成本

使用索引预估的访问成本

在这里插入图片描述

比较成本大小,则决定使用索引查询。

order by优化

  • SQL1
代码语言:javascript
复制
explain 
SELECT
*
FROM
 student 
WHERE
 stu_name = '刘备' 
ORDER BY
 stu_addr;

在这里插入图片描述

执行计划发现使用了文件排序 using filesort,原因是由于where 后面的语句过滤字段和排序字段中间跳过了age字段,索引排序是在文件中排序。

  • SQL2
代码语言:javascript
复制
explain 
SELECT
*
FROM
 student 
WHERE
 stu_name = '刘备' and stu_age =  16
ORDER BY
 stu_addr;

在这里插入图片描述

分析执行计划,发现using filesort没有了,因为满足了最左前缀原则了。加上了stu_age字段,直接可以在索引中完成排序。

  • SQL3
代码语言:javascript
复制
explain 
SELECT
*
FROM
 student 
WHERE
 stu_name = '刘备'
ORDER BY
 stu_addr,stu_age

在这里插入图片描述

执行计划中出现了filesort,原因是排序字段与索引建立的顺序不一致,不满足最左前缀法则,mysql没有使用索引进行排序。

  • SQL4
代码语言:javascript
复制
explain 
SELECT
*
FROM
 student 
WHERE
 stu_name = '刘备'
ORDER BY
 stu_age, stu_addr desc

分析执行计划,发现又出现了using filesort,使用了文件排序,按照排序确实按照最左前缀了啊,不应该啊。仔细观察发现。两个字段的排序方向不一致,这会导致索引无法进行排序,在索引树中:只能使用排序方向一致的索引,否则就需要使用文件排序了。

  • SQL5
代码语言:javascript
复制
explain 
SELECT
*
FROM
 student 
WHERE
 stu_name in('刘备','张飞')
ORDER BY
 stu_age

在这里插入图片描述

执行计划发现明明按照最左前缀的顺序查询和排序的,为什么又出现了文件排序呢?因为in查询属于范围查询,会导致右侧所有索引失效。

  • order by 总结:
    • 排序字段尽量是索引字段
    • 尽量使用索引覆盖
    • where字段和排序字段遵循最左前缀
    • 出现filesort ,尽量优化成 using index,在索引中排序肯定比使用文件排序要快得多。
    • group by 和order by 优化点是一样的,默认会按照字段排序的,如果不需要排序 则可以在group by 之后加上order by null,省去排序的消耗。

文件排序 using filesort

在上面优化中,发现在mysql中排序分为文件排序和索引排序,在无法使用索引排序的情况下,我们就得考虑如何优化文件排序了。文件排序分为两种:

  • 单路排序 单路排序是一次性取出所有满足条件的数据的所有字段,保存在sort buffer中,然后在sort buffer排序完成之后,直接返回结果。
  • 双路排序 双路排序是取出排序的字段和能够定位数据的行标识,保存在sort_buffer中,完成排序后,再按照行标识按顺序从文件中取出需要查询的字段,这个比单路排序多了一步操作。
单路排序和双路排序分别在什么情况下使用呢?
  • 如果查询的字段比sort buffer 空间要大(大小由系统变量max_length_for_sort_data来决定的,默认为1024字节),则使用双路排序,因为一次性无法从文件中取出所有字段放在sort buffer中。
  • 相反,如果查询的字段总长度比sort buffer 空间小,则使用单路排序。
如何区分执行器使用的是单路排序还是双路排序呢?

我们可以使用上面提到的trace工具来分析:

代码语言:javascript
复制

SET SESSION optimizer_trace = "enabled=on",
end_markers_in_json = ON;

SELECT
*
FROM
 student 
WHERE
 stu_name ='a'
ORDER BY
 stu_addr,stu_age;
SELECT
* 
FROM
 information_schema.OPTIMIZER_TRACE;
代码语言:javascript
复制
   {
      "join_execution": {
        "select#": 1,
        "steps": [
          {
            "filesort_information": [
              {
                "direction": "asc",
                "table": "`student`",
                "field": "stu_addr"
              },
              {
                "direction": "asc",
                "table": "`student`",
                "field": "stu_age"
              }
            ] /* filesort_information */,
            "filesort_priority_queue_optimization": {
              "usable": false,
              "cause": "not applicable (no LIMIT)"
            } /* filesort_priority_queue_optimization */,
            "filesort_execution": [
            ] /* filesort_execution */,
            "filesort_summary": {
              "rows": 0,
              "examined_rows": 0,
              "number_of_tmp_files": 0,
              "sort_buffer_size": 204800 // sort buffer大小
              "sort_mode": "<sort_key, packed_additional_fields>" // 单路排序
            } /* filesort_summary */
          }
        ] /* steps */
      } /* join_execution */
    }

分析trace结果中主要的字段:sort_buffer_size:204800 mysql设置的默认值为1m "sort_mode": "<sort_key, packed_additional_fields>" 单路排序 因为我们查询的字段长度肯定是小于1m的,我们现在设置下sort_buffer_size大小,然后再trace下结果:

代码语言:javascript
复制
// 修改默认值,记得测试完之后修改回默认值(1m)
 set max_length_for_sort_data = 10;

再执行上面的语句,查看结果为:

代码语言:javascript
复制
       "filesort_summary": {
              "rows": 0,
              "examined_rows": 0,
              "number_of_tmp_files": 0,
              "sort_buffer_size": 57344,
              "sort_mode": "<sort_key, rowid>" // 双路排序 因为包含了rowid,需要根据rowid再去提取查询的字段。
            } /* filesort_summary */

sort_mode 字段中包含了rowid,所以排序模式已经更改为双路排序了。// 设置回原来的值 set max_length_for_sort_data = 1024;

作者:乐哉

图片:来源于网络,如有侵权,联系删除。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 乐哉开讲 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 索引优化常见手段
    • 全值匹配
      • 最左前缀索引
        • 不要在索引列上使用函数计算和类型转换,否则会导致索引失效,使用全表扫描
          • 索引字段使用了范围查询,右边索引字段将不会使用索引查询,因为使用范围查询后,索引不能按顺序进行比较,则会直接使用全表扫描
            • select查询中,最好只包括索引字段,不要全字段查询
              • 在where语句中,尽量不要使用不等于、is null,is not null等比较语句,因为mysql只能使用全表扫描 完成查询,非空判断的替换语句可以为字段设置默认值,查询的时候和这个默认值进行比较。
                • 不要用范围字段 in、or等,mysql会根据检索数据量、表大小等多个因素决定是否使用索引
                  • 以 ‘%’开头的模糊查询语句,会导致索引失效。
                    • 使用索引覆盖,select 查询的字段包括在索引中
                • 深入索引优化
                  • 我们很好奇,mysql是如何选择合适的索引的?
                    • order by优化
                      • 文件排序 using filesort
                        • 单路排序和双路排序分别在什么情况下使用呢?
                        • 如何区分执行器使用的是单路排序还是双路排序呢?
                    相关产品与服务
                    云数据库 SQL Server
                    腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档