【MySQL】MySQL索引优化——从原理分析到实践对比

news2024/10/7 2:24:51

目录

使用TRACE分析MySQL优化

开启TRACE

TRACE 结果集

ORDER BY & GROUP BY 优化

优化方式

分页优化

不同场景的优化方式

JOIN关联优化

算法介绍

优化方式

COUNT优化

优化方式


使用TRACE分析MySQL优化

某些情况下,MySQL是否走索引是不确定的=[,,_,,]:3,那、我就想确定。。。咋办?

首先,在FROM 表名后加上FORCE INDEX(索引名称)可以强制MySQL走索引

举个🌰

SELECT name FROM app_user FORCE INDEX(index_age) WHERE age > 9;

当然本着尊重以及信赖MySQL的原则,还是不要强迫他(˶‾᷄ ⁻̫ ‾᷅˵)。。毕竟MySQL有自己的一套很靠谱的优化方式,针一条SQL语句,我们可以通过TRACE来查看他的优化结果

开启TRACE

使用下面👇的语句开启TRACE(开启会影响性能,因此默认关闭,只会在做分析的时候开启)

set session optimizer_trace="enabled=on",end_markers_in_json=on

​在执行语句下面加一行,举个🌰 

SELECT name FROM app_user WHERE age > 9;
SELECT * FROM information_schema.OPTIMIZER_TRACE;

TRACE 结果集

如下是一个trace结果集的示例(完整版太长,部分省略)

{
  "steps": [
    {
      "join_preparation": {
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `app_user`.`name` AS `name` from `app_user` where (`app_user`.`age` > 9)"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": {
        "select#": 1,
        "steps": [
          {……},
          {
            "substitute_generated_columns": {} /* substitute_generated_columns */
          },
          {
            "table_dependencies": [……] /* table_dependencies */
          },
          {
            "ref_optimizer_key_uses": [
            ] /* ref_optimizer_key_uses */
          },
          {
            "rows_estimation": [
              {
                "table": "`app_user`",
                "range_analysis": {
                  "table_scan": {
                    "rows": 992599,
                    "cost": 102998
                  } /* table_scan */,
                  "potential_range_indexes": [
                    {
                      "index": "PRIMARY",
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    {
                      "index": "id_app_user_name",
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    {
                      "index": "index_age",
                      "usable": true,
                      "key_parts": [
                        "age",
                        "id"
                      ] /* key_parts */
                    }
                  ] /* potential_range_indexes */,
                  "setup_range_conditions": [
                  ] /* setup_range_conditions */,
                  "group_index_range": {
                    "chosen": false,
                    "cause": "not_group_by_or_distinct"
                  } /* group_index_range */,
                  "skip_scan_range": {
                    "potential_skip_scan_indexes": [
                      {
                        "index": "index_age",
                        "usable": false,
                        "cause": "query_references_nonkey_column"
                      }
                    ] /* potential_skip_scan_indexes */
                  } /* skip_scan_range */,
                  "analyzing_range_alternatives": {
                    "range_scan_alternatives": [……] 
                    /* range_scan_alternatives */,
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    } /* analyzing_roworder_intersect */
                  } /* analyzing_range_alternatives */
                } /* range_analysis */
              }
            ] /* rows_estimation */
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`app_user`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 992599,
                      "access_type": "scan",
                      "resulting_rows": 992599,
                      "cost": 102996,
                      "chosen": true
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 992599,
                "cost_for_plan": 102996,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {……} /* attaching_conditions_to_tables */
          },
          {
            "finalizing_table_conditions": [……] /* finalizing_table_conditions */
          },
          {
            "refine_plan": [
              {
                "table": "`app_user`"
              }
            ] /* refine_plan */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": {
        "select#": 1,
        "steps": [
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */
}

主要字段含义

  • "steps":步骤

    • "join_preparation"

      • 第一阶段:准备阶段,会进行SQL格式化
    • "join_optimization"

      • 第二阶段:优化阶段
      • "condition_processing"  条件处理

        • 联合索引的顺序优化就是在这一步
      • "table_dependencies"   表依赖详情
      • "rows_estimation"         预估表的访问成本(选择依据)

        • "table_scan"          全表扫描情况

          • "rows"             扫描行数
          • "cost"              查询成本

            • 主要依据,除了扫描行数还会考虑回表等别的消耗,无单位,MySQL一般会选小的)
        • "potential_range_indexes" 查询可能使用到的索引

          • "index"

            • "PRIMARY" 主键索引
            • 其他的表示辅助索引
        • "analyzing_range_alternatives"  分析各个索引的成本

          • "rowid_ordered" 使用该索引获取的记录是否按照主键排序
          • "index_only"       是否使用覆盖索引
          • "rows"                扫描行数
          • "cost"                 索引使用成本
          • "chosen"            是否确认选择该索引
      • "considered_execution_plans"

        • "best_access_path"                      最优访问路径

          • "considered_access_paths"  最终选择的访问路径

            • "rows_to_scan"         扫描行数
            • "access_type"           访问类型
            • "range_details"

              • "used_index"      使用索引
              • "resulting_rows" 扫描行数
              • "cost"                 查询成本
              • "chosen"            确定选择

ORDER BY & GROUP BY 优化

Extra中的值表示了ORDER BY是否走索引,Extra中的值是Using index condition表示ORDER BY走索引,Extra中的值是Using filesort表示ORDER BY未走索引;ORDER by默认升序,如果ORDER by使用降序(与索引的排序方式不同),于是会产生Using filesort(MySQL8.0以上的版本有降序索引可以支持这种查询优化

GROUP BYORDER by很类似,其实质是先排序后分组

Using filesort 原理

排序方式

  • 单路排序:一次性取出满足条件的所有字段,然后在sort buffer中进行排序

  • 双路排序(回表排序模式):首先根据相应的条件取出相应字段的排序字段和ID,然后在sort buffer中进行排序,排序完需要再次取回其他需要的字段

如果使用了Using filesort,那么使用上面介绍的TRACE工具🔧就会有相应的信息,即sort_mode信息

如果是单路排序sort_mode字段的信息为<sort_key,additional_fields>或者<sort_key,packed_additional_fields>;如果是双路排序sort_mode字段的信息为<sort_key,rowid>

那么,如何MySQL是如何判断是否使用了Using filesort的?

自问自答:通过比较系统变量max_length_for_sort_data(默认1024字节)的大小来判断使用哪种排序

  • 字段总长度小于max_length_for_sort_data,使用单路

  • 字段总长度大于max_length_for_sort_data,使用双路

优化方式

  1. MySQL支持两种方式的排序:filesort(效率低)index(效率高)Using index是指MySQL扫描索引本身就能完成排序
  2. ORDER by满足两种情况会使用Using index

    1. ORDER by使用索引最左前列
    2. 使用WHERE子句和ORDER by子句条件列组合满足索引最左前列
  3. 尽量在索引列上完成排序,遵循索引建立(索引创建的顺序)时的最左前缀法则,如果ORDER by的条件不在索引列上,就会产生Using filesort
  4. 尽量使用覆盖索引
  5. 遵循索引创建的最左前缀法则,对于GROUP BY的优化如果不需要排序的可以加上ORDER by null禁止排序,注意WHERE高于HAVING,能写在WHERE中就不要使用HAVING

分页优化

由于limit的查询方式,因此使用limit进行分页效率并不高

我们举个🌰:limit 1000,10

实际执行的时候,会从头开始查询到1010条数据,然后再舍弃掉前1000条。。。没错就手笔是这么阔(˶‾᷄ ⁻̫ ‾᷅˵)

既然limit效率不高,那么如何进行优化呢?

不同场景的优化方式

  1. 自增且连续的主键(要求数据中间无缺失)

      WHERE id LIMIT 1000,10可以优化为WHERE id>1000 LIMIT 10

  1. 不一定连续且非按主键进行排序

举个🌰

优化前

select * from app_user ORDER BY name limit 100000,5;

​优化后

select * from app_user a inner join (select id from app_user order by name limit 100000,5) b on a.id = b.id;

​分别使用Explain分析下

​优化前,也用到了索引,但是是使用了range进行范围扫描

优化后, 有三行数据,第一行看着虽然走的是全局扫描,但是他是由第三行派生出的,即只有一“页”(本案例中只有5行数据),第二行可以看出使用的是主键进行关联索引,typeeq_refref的速度还快,第三行虽然是遍历索引,但是没有进行回表,而且只查询五个值即可,因而优化后速度快很多

JOIN关联优化

先介绍下驱动表和被驱动表的概念( ̄∇ ̄)/

简单理解先执行的就是驱动表,不同的join类型MySQL选择的驱动表不同

  • inner join 不确定,MySQL会自行判断(一般选数据量小的表做驱动表)
  • left join 左边👈的是驱动表
  • right join 右边👉的是驱动表

算法介绍

  1. 嵌套循环连接算法 Nested-Loop Join(NLJ)

使用索引字段进行关联的关联查询一般会使用NLJ

简单理解就是拿一张表(驱动表)中的所有数据,一次一次的去另一张表(被驱动表)中查找对应行,最后取出两张表的结果合集

EXPLAIN select * from app_user a inner join app_user_copy1 b on a.id = b.id;

上面SQL执行的大致流程如下:

  1. 把b表中读取一行数据(b表有过滤条件会从过滤后的结果中读取)
  2. 取出关联字段(id)去a表中查找较
  3. 取出a表中满足条件的行,和b表中获取到的结果合并,返回
  4. 重复上面👆3个步骤
  1. 基于块的嵌套循环连接算法 Block Nested Loop Join(BNL)

使用非索引字段进行关联的关联查询一般会使用BNL

EXPLAIN select * from app_user a inner join app_user_copy1 b on a.age = b.age;

Extra中Using join buffer (hash join)表示使用BNL,可以看到两张表进行的都是全表扫描

上面SQL执行的大致流程如下:

  1. 把b表中的所有数据放入到join_buffer中(join_buffer是内存中的一块区域,默认大小256k,放不下就分段放)
  2. 把a表中的每一行数据取出来,跟join_buffer中的全部数据做比较
  3. 返回满足join条件的数据

为什么MySQL会根据关联字段是否有索引而使用不同的算法

首先,我们大致量化下两个算法总消耗

NLJ总消耗(有索引)

  • 磁盘扫描:a表行数 * 2(会先扫描索引之后直接扫描符合条件的数据行)
  • 内存判断:a表行数 * b索引

BNL总消耗(无索引)

  • 磁盘扫描:a表行数 + b表行数
  • 内存判断:a表行数 * b表行数(由于join_buffer中的数据是无序的,因此判断次数=a表行数 * b表行数)

如果在没索引的情况下使用NLJ,会导致磁盘扫描=a表行数 * b表行数,由于内存判断要比磁盘扫描快的多,因此在没索引的情况下,MySQL一般会选择BNL,而有索引则选择NLJ

优化方式

  1. 关联字段加索引
  2. 减少不必要的字段查询

  3. 加大join_buffer_size的大小(一次缓存的越多,内层扫描的次数就越少)

  4. 小表驱动大表(Explain结果集中小表在上)

    1. 可使用straight join设置左表驱动右表,不过只适用于inner join
    2. 尽量让MySQL的优化器自行判断(MySQL的优化器还是很稳的)
    3. 关于inexsits

      1. 当后半部分筛选出的结果集小于前面半部分,一般用in

        1. in可以理解为以后面部分的结果集的大小作为外层循环的遍历次数,做个简单的代换就是a IN b就相当于for(b.size){a},因此in会先执行b部分,b部分越小,也就相当于for循环次数越少
      2. 当后半部分筛选出的结果集大于前面半部分,一般用exsits

        1. exsits会先执行exsits前面的部分,做个简单的代换就是a EXSITS b就相当于for(a.size){b},即a部分越小,for循环次数越少

值得注意的是,这里的小表的“小”是指关联的表们分别按照各自的过滤条件进行过滤后,参与join的数据量,而非原始数据量

说白了就是先执行的那部分所得到的结果集越小,执行效率越高

COUNT优化

关于count(*)count(1)count(id)count(字段)哪种效率最高?

自问自答:

  • 字段有索引count(*)约等于count(1)>count(字段)>count(id)

    • 二级索引存储的数据比主键索引小
    • count(1)无需取字段,count(字段)需要取字段,理论上来说count(1)会比count(字段)快一点
    • count(*)被特别优化了下,按行累加,效率很高
  • 字段无索引count(*)约等于count(1)>count(id)>count(字段)

但其实MySQL优化到现在的版本,这四个的执行效率基本差不多,explain

​可以看到四条执行结果完全一致,SO你懂的(o^^o)/🎉

不过count(字段)跟其他3个有个很大的区别,当字段中有NULL时(如下图id5的数据行name的值为NULL

​我们分别执行下count(*)、count(1)、count(id)、count(name)

可以看到,当count某个字段时,如果该字段为NULL,则不会被统计到(其他三个都会统计到)

优化方式

  1. 查询MySQL自己维护的总行数

    1. 使用myisam存储引擎的表的总行数(不带WHERE的)会被myisam存储到磁盘上,查询无需计算,超级快
    2. innodb存储引擎由于MVCC机制,获取总行数需要实时计算
  2. 模糊获取

    1. 使用show table status like '表名'获取近似值,不准确,但很快并且无需额外操作
  3. 在Redis中维护总行数

    1. 插入删除操作都需要额外维护Redis,而且并非完全准确的
  4. 在数据库增加计数表

    1. 准确,但成本较高,插入删除操作都需要额外维护这张表

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/412828.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

给word文档中的公式标号

给word文档中的公式标号 首先编写一个公式 cosθa2b2−c22abcos\theta\frac{a^2b^2-c^2}{2ab} cosθ2aba2b2−c2​ 在公式的最后&#xff08;不要超出公式块&#xff09;输入#()并按回车&#xff1a; 即可在生成的框中输入公式序号&#xff1a;

Sobel 算子

1、简介 SobelSobelSobel 本质是基于图像空间域卷积&#xff0c;卷积的作用除了实现图像模糊或者去噪&#xff0c;还可以寻找一张图像上所有梯度信息&#xff0c;这些梯度信息是图像的最原始特征数据&#xff0c;进一步处理之后就可以生成一些比较高级的特征用来表示一张图像实…

快速搭建第一个SpringCloud程序

目录 1、Spring Boot项目脚手架快速搭建 1.1 生成工程基本配置 1.2 生成工程。 1.3 导入开发工具&#xff08;此处为Idea&#xff09; 1.4 运行代码 1.5 验证是否能访问 2、Spring Cloud环境搭建 2.1 版本匹配问题 2.2 Spring Cloud环境测试 3、引入Eureka Server 3…

《GPT-4技术报告》【中文版、英文版下载】

大预言模型时代已经到来&#xff0c;但是真正的智能之路还很长。 一、以下是连接&#xff0c;大家请自取。 英文原版&#xff1a;https://arxiv.org/pdf/2303.08774.pdfhttps://arxiv.org/pdf/2303.08774.pdf 中文翻译版本&#xff1a; 人工通用智能的星星之火:GPT-4的早期实…

【Linux】vi编辑器——命令模式

目录 行复制&#xff08; "yy") 示例&#xff1a; 结果&#xff1a; 粘贴 多行复制&#xff08;"nyy") 示例&#xff1a; 结果&#xff1a; 行间跳转 1G或gg-------------------跳转到文本内容的第一行 示例&#xff1a; 结果: G-----------…

刷题日记①

day01 1. %m.ns 打印输出 以下程序的运行结果是&#xff08;&#xff09;_表示空格 #include <stdio.h> int main(void) {printf("%s , %5.3s\n", "computer", "computer");return 0; }A computer , puter B computer , __com C compute…

磁共振成像(MRI)连接组学的应用

前言 如何将磁共振成像(MRI)连接组学应用于研究典型的大脑结构和功能&#xff0c;并在临床环境中用于诊断、预后或治疗&#xff1f;本文将讨论MRI连接组学在评估功能和结构连接方面的可能应用。简而言之&#xff0c;功能连接是一种测量功能磁共振(fMRI)衍生的血氧水平依赖(BOL…

好兄弟离职了,一周面试了20多场,我直呼内行

好兄弟离职之后&#xff0c;一周面试了20多场&#xff0c;最后进了阿里&#xff0c;分享一些面试经历&#xff0c;希望能对大家有帮助&#xff01; 我的面试感受 先说一个字 是真的 “ 累 ” 安排的太满的后果可能就是经常一天只吃一顿饭&#xff0c;一直奔波在路上 不扯这个…

MybatisPlus <= 3.5.3.1 TenantPlugin 组件 存在 sql 注入漏洞(CVE-2023-25330)

漏洞描述 MyBatis-Plus TenantPlugin 是 MyBatis-Plus 的一个为多租户场景而设计的插件&#xff0c;可以在 SQL 中自动添加租户 ID 来实现数据隔离功能。 MyBatis-Plus TenantPlugin 3.5.3.1及之前版本由于 TenantHandler#getTenantId 方法在构造 SQL 表达式时默认情况下未对…

关键词数据分析-搜索词和关键词分析工具

要搜索热门关键词获取&#xff0c;可以采用以下几种方法&#xff1a; 使用百度指数&#xff1a;百度指数是一个实用的工具&#xff0c;可用于查看关键词的热度趋势、搜索量等数据。在百度指数中&#xff0c;您可以输入您要搜索的关键词&#xff0c;并查看近期的相关数据。这可以…

ServletAPI详解(四)-HttpServletResponse

我们来看第三个方法,HttpServletResponse 在servlet运行原理中提到,servlet代码中的doXXX方法的目的就是根据请求计算响应,然后将响应数据设置到HttpServletResponse对象中,然后 Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式, 转成一个字符串, 并通过 Soc…

golang硬核技术(一)内存管理

目录前言tcmallocPageHeapCentralCacheThreadCache小对象分配中对象和大对象总结堆内存分配概念mheapheaparenamspanmcentralmcache微对象分配 tiny allocator小对象分配大对象分配栈内存分配分段栈连续栈栈在go中stackpoolstackLarge栈分配栈缩容前言 golang的内存分配&#…

QT学习笔记( APP 主界面开发项目\滑动界面的设计)

APP 主界面开发项目 本章与大家一起开发 APP 主界面。Qt C提供了像 QStackedWdget 与 QTableView 这种控 件可以方便的切换页面&#xff0c;但是这种切换页面的方法比较生硬&#xff0c;不能像手机一样滑动&#xff0c;往往这种 界面就会给用户较差的体验感。所以在传统的 Qt …

【GPT】让你事半功倍特别好用的5个GPT工具

文章目录前言一、现在还能开通ChatGPT4.0吗&#xff1f;二、推荐五款与ChatGPT的相关实用工具1.一款浏览器插件&#xff1a;ChatGPT for Google2.一款生成图片的AI工具&#xff1a;midjourney3.推荐两款AI自动生成PPT&#xff1a;闪击PPT、mindshow4.识别PFD文件内容对话&#…

信号与系统之《一文看懂傅里叶变换》

“傅里叶变换是一种非常有用的数学工具&#xff0c;它可以将一个复杂的信号分解成许多简单的频率成分。傅里叶变换在信号处理、图像处理、音乐、视频和通信等许多领域都有广泛的应用。相信大部分同学在毕业之后的一段时间之内都还没有理解到傅里叶变换的精髓&#xff0c;今天我…

【Java面试八股文宝典之MySQL篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day20

大家好&#xff0c;我是陶然同学&#xff0c;软件工程大三即将实习。认识我的朋友们知道&#xff0c;我是科班出身&#xff0c;学的还行&#xff0c;但是对面试掌握不够&#xff0c;所以我将用这100多天更新Java面试题&#x1f643;&#x1f643;。 不敢苟同&#xff0c;相信大…

(六)【软件设计师】计算机系统—原码反码补码移码

文章目录一、数据表示二、原码三、反码四、补码五、移码六、范围七、总结一、数据表示 各种数值在计算机中表示的形式称为机器数&#xff0c;其特点是采用二进制计数制&#xff0c;数的符号用0和1表示&#xff0c;小数点则隐含&#xff0c;表示不占位置。机器数对应的实际数值称…

使用zabbix监控avamar【二】

1、在“使用zabbix监控avamar【一】”中介绍了如何设置avamar端&#xff0c;并发送测试消息&#xff0c;本篇将介绍如何在zabbix server端进行配置。 2、在zabbix server的snmp trap日志文件中查找刚上传的报警信息 可以看到已经正常收到客户端信息。 3、在/etc/zabbix/snmpt…

2.0、Java继承与多态 - 方法重写与重载

2.0、Java继承与多态 - 方法重写与重载 重写&#xff08;Override&#xff09;&#xff1a;方法名一样&#xff0c;参数列表也一样 [ 重写也叫做覆写或者覆盖 ]&#xff1b; 重载&#xff08;Overload&#xff09;&#xff1a;方法名一样&#xff0c;但是参数列表不一样&#…

vue-cli 初始化工程

个人记录下vue-cli创建项目的步骤 卸载老版本的vue-cli (这不是必要的) npm uninstall vue-cli -g 如果本地使用 yarn的话,还需执行 yarn global remove vue-cli 安装全新的vue-cli npm install -g vue/cli 安装指定版本的vue-cli npm install -g vue/…