06期:使用 OPTIMIZER_TRACE 窥探 MySQL 索引选择的秘密

news2024/7/6 20:07:46

这里记录的是学习分享内容,文章维护在 Github:studeyang/leanrning-share。

优化查询语句的性能是 MySQL 数据库管理中的一个重要方面。在优化查询性能时,选择正确的索引对于减少查询的响应时间和提高系统性能至关重要。但是,如何确定 MySQL 的索引选择策略?MySQL 的优化器是如何选择索引的?

在这篇《索引失效了?看看这几个常见的情况!》文章中,我们介绍了索引区分度不高可能会导致索引失效,而这里的“不高”并没有具体量化,实际上 MySQL 会对执行计划进行成本估算,选择成本最低的方案来执行。具体我们还是通过一个案例来说明。

案例

还是以人物表为例,我们来看一下优化器是怎么选择索引的。

建表语句如下:

CREATE TABLE `person` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) NOT NULL,
  `score` int(11) NOT NULL,
  `age` int(11) NOT NULL,
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_name_score` (`name`,`score`) USING BTREE,
  KEY `idx_age` (`age`) USING BTREE,
  KEY `idx_create_time` (`create_time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

然后插入 10 万条数据:

create PROCEDURE `insert_person`()
begin
    declare c_id integer default 3;
    while c_id <= 100000 do
	    insert into person values(c_id, concat('name',c_id), c_id + 100, c_id + 10, date_sub(NOW(), interval c_id second));
	    -- 需要注意,因为使用的是now(),所以对于后续的例子,使用文中的SQL你需要自己调整条件,否则可能看不到文中的效果
	    set c_id = c_id + 1;
    end while;
end;
CALL insert_person();

可以看到,最早的 create_time2023-04-14 13:03:44

我们通过下面的SQL语句对person表进行查询:

explain select * from person where NAME>'name84059' and create_time>'2023-04-15 13:00:00'

通过执行计划,我们可以看到 type=All,表示这是一次全表扫描。接着,我们将 create_time 条件中的 13 点改为 15 点,再次执行查询:

explain select * from person where NAME>'name84059' and create_time>'2023-04-15 15:00:00'

这次执行计划显示 type=range,key=create_time,表示 MySQL 优化器选择了 create_time 索引来执行这个查询,而不是使用 name_score 联合索引。

也许你会对此感到奇怪,接下来,我们一起来分析一下背后的原因。

OPTIMIZER_TRACE 工具介绍

为了更好地理解 MySQL 优化器的工作原理,我们可以使用一个强大的调试工具:OPTIMIZER_TRACE。它是在 MySQL 5.6 及之后的版本中提供的,可以查看详细的查询执行计划,包括查询优化器的决策、选择使用的索引、连接顺序和优化器估算的行数等信息。

当开启 OPTIMIZER_TRACE 时,MySQL 将会记录查询的执行计划,并生成一份详细的报告。这个报告可以提供给开发人员或数据库管理员进行分析,以了解 MySQL 是如何决定执行查询的,进而进行性能优化。

在 MySQL 中,开启 OPTIMIZER_TRACE 需要在查询中使用特定的语句,如下所示:

SET optimizer_trace='enabled=on';
SELECT * FROM mytable WHERE id=1;
SET optimizer_trace='enabled=off';

当执行查询后,MySQL将会生成一个 JSON 格式的执行计划报告。

需要注意的是,开启 OPTIMIZER_TRACE 会增加查询的执行时间和资源消耗,因此只应该在需要调试和优化查询性能时使用。

官方文档在这里:https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_OPT_TRACE.html

全表扫描的总成本

MySQL 在查询数据之前,首先会根据可能的执行方案生成执行计划,然后依据成本决定走哪个执行计划。这里的成本,包括 IO 成本和 CPU 成本:

  • IO 成本,是从磁盘把数据加载到内存的成本。默认情况下,读取数据页的 IO 成本常数是 1(也就是读取 1 个页成本是 1)。
  • CPU 成本,是检测数据是否满足条件和排序等 CPU 操作的成本。默认情况下,检测记录的成本是 0.2。

MySQL 维护了表的统计信息,可以使用下面的命令查看:

SHOW TABLE STATUS LIKE 'person'

该命令将返回包括表的行数、数据长度、索引大小等信息。这些信息可以帮助 MySQL 优化器做出更好的决策,选择更优的执行计划。我们使用上述命令查看 person 表的统计信息。

图中总行数为 100064 行(由于 MySQL 的统计信息是一个估算,多出 64 行是正常的),CPU 成本是 100064 * 0.2 = 20012.8 左右。

数据长度是 5783552 字节。对于 InnoDB 存储引擎来说,5783552 就是聚簇索引占用的空间,等于聚簇索引的页数量 * 每个页面的大小。InnoDB 每个页面的大小是 16KB,因此我们可以算出页的数量是 353,因此 IO 成本是 353 左右。

所以,全表扫描的总成本是 20365.8 左右。

追踪 MySQL 选择索引的过程

select * from person where NAME>'name84059' and create_time>'2023-04-15 13:00:00'

上面这条语句可能执行的策略有:

  • 使用 name_score 索引;
  • 使用 create_time 索引;
  • 全表扫描;

接着我们开启 OPTIMIZER_TRACE 追踪:

SET OPTIMIZER_TRACE="enabled=on",END_MARKERS_IN_JSON=on;
SET optimizer_trace_offset=-30, optimizer_trace_limit=30;

依次执行下面的语句。

select * from person where NAME >'name84059';
select * from person where create_time>'2023-04-15 13:00:00';
select * from person;

然后查看追踪结果:

select * from information_schema.OPTIMIZER_TRACE;
SET optimizer_trace="enabled=off";

我从 OPTIMIZER_TRACE 的执行结果中,摘出了几个重要片段来重点分析:

1、使用 name_score 对 name84059<name 条件进行索引扫描需要扫描 26420 行,成本是 31705。

30435 是查询二级索引的 IO 成本和 CPU 成本之和,再加上回表查询聚簇索引的 IO 成本和 CPU 成本之和。

{
    "index": "idx_name_score",
    "ranges": [
        "name84059 < name"
    ] /* ranges */,
    "index_dives_for_eq_ranges": true,
    "rowid_ordered": false,
    "using_mrr": false,
    "index_only": false,
    "rows": 26420,
    "cost": 31705,
    "chosen": true
}

2、使用 create_time 进行索引扫描需要扫描 27566 行,成本是 33080。

{
    "index": "idx_create_time",
    "ranges": [
        "2023-04-15 13:00:00 < create_time"
    ] /* ranges */,
    "index_dives_for_eq_ranges": true,
    "rowid_ordered": false,
    "using_mrr": false,
    "index_only": false,
    "rows": 27566,
    "cost": 33080,
    "chosen": true
}

3、全表扫描 100064 条记录的成本是 20366。

{
    "considered_execution_plans": [
        {
            "plan_prefix": [
            ] /* plan_prefix */,
            "table": "`person`",
            "best_access_path": {
                "considered_access_paths": [
                    {
                        "access_type": "scan",
                        "rows": 100064,
                        "cost": 20366,
                        "chosen": true
                    }
                ] /* considered_access_paths */
            } /* best_access_path */,
            "cost_for_plan": 20366,
            "rows_for_plan": 100064,
            "chosen": true
        }
    ] /* considered_execution_plans */
}

所以 MySQL 最终选择了全表扫描方式作为执行计划。

把 SQL 中的 create_time 条件从 13:00 改为 15:00,再次分析 OPTIMIZER_TRACE 可以看到:

{
    "index": "idx_create_time",
    "ranges": [
        "2023-04-15 15:00:00 < create_time"
    ] /* ranges */,
    "index_dives_for_eq_ranges": true,
    "rowid_ordered": false,
    "using_mrr": false,
    "index_only": false,
    "rows": 6599,
    "cost": 7919.8,
    "chosen": true
}

因为是查询更晚时间的数据,走 create_time 索引需要扫描的行数从 33080 减少到了 7919.8。这次走这个索引的成本 7919.8 小于全表扫描的 20366,更小于走 name_score 索引的 31705。

所以这次执行计划选择的是走 create_time 索引。

人工干预

优化器有时会因为统计信息的不准确或成本估算的问题,实际开销会和 MySQL 统计出来的差距较大,导致 MySQL 选择错误的索引或是直接选择走全表扫描,这个时候就需要人工干预,使用强制索引了。

比如,像这样强制走 name_score 索引:

explain select * from person FORCE INDEX(name_score) where NAME >'name84059' and create_time>'2023-04-15 13:00:00'

封面

相关文章

也许你对下面文章也感兴趣。

  • 索引失效了?看看这几个常见的情况!
  • MySQL查询性能慢,该不该建索引?
  • MySQL的事务隔离及实现原理

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

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

相关文章

scrapy框架爬取某壁纸网站美女壁纸 + MySQL持久化存储

文章目录 准备工作创建项目&#xff1a;设置&#xff08;settings&#xff09; 主程序入口meinv.py思路源代码 items 配置管道pipelines源代码 效果图总结 准备工作 创建项目&#xff1a; scraoy startproject bizhi cd bizhi scrapy genspider meinv bizhi360.com 设置&#…

ROS学习第二十九节——URDF之joint

此处留疑问&#xff0c;link,joint的origin子标签到底是怎么样的一种位置关系&#xff1f;&#xff1f;&#xff1f; https://download.csdn.net/download/qq_45685327/87717336 urdf 中的 joint 标签用于描述机器人关节的运动学和动力学属性&#xff0c;还可以指定关节运动的…

大数据-玩转数据-IDEA创建Maven工程

一、 IDEA集成Maven插件 打开IDEA&#xff0c;进入主界面后点击 file&#xff0c;然后点击 settings,在上面的快捷查找框中输入maven&#xff0c;查找与maven相关的设置&#xff0c;然后点击maven 修改maven的路径&#xff08;使用本地的Maven&#xff09;&#xff0c;以及修…

【流畅的Python学习笔记】2023.4.22

此栏目记录我学习《流畅的Python》一书的学习笔记&#xff0c;这是一个自用笔记&#xff0c;所以写的比较随意 元组 元组其实是对数据的记录&#xff1a;元组中的每个元素都存放了记录中一个字段的数据&#xff0c;外加这个字段的位置。简单试试元组的特性&#xff1a; char…

kong(1):Kong介绍

Kong是一款基于OpenResty&#xff08;Nginx Lua模块&#xff09;编写的高可用、易扩展的&#xff0c;由Mashape公司开源的API Gateway项目。Kong是基于NGINX和Apache Cassandra或PostgreSQL构建的&#xff0c;能提供易于使用的RESTful API来操作和配置API管理系统&#xff0c;…

复旦大学郁喆隽:网络制造出人的“幻象”,深度思考如何可能?

“人是什么?”这是亘古以来人们反复追问的一个古老命题。从元宇宙到ChatGPT&#xff0c;这个人人都在讨论、理解和实践互联网的时代&#xff0c;对“人”的自我定义和认知产生了哪些影响&#xff1f;    在3月12日复旦大学-华盛顿大学EMBA项目主办的“复调艺文沙龙”上&am…

计算长方形、三角形、圆形的面积和周长

系统设计框图&#xff1a; 图形模块的 概要设计&#xff08;设计数据结构和接口&#xff09;&#xff1a; 数据结构&#xff1a; float 表示面积和周长 长方形的数据&#xff08;一般typedef都是定义在对应模块的头文件中&#xff09; typedef struct{ float width; float he…

三菱GX Works2梯形图程序分段显示设置的具体方法示例

三菱GX Works2梯形图程序分段显示设置的具体方法示例 大家平时在使用GX Works2进行梯形图程序编辑时,默认是一整段在一起,程序步数较多时查看起来不是那么方便,下面就和大家分享如何通过声明编辑来实现程序分段显示。 具体方法可参考以下内容: 如下图所示,打开GX Works2编…

数据结构与算法(一):基础数据结构 算法概念、数组、链表、栈、队列

判断一个数是否是2的N次方&#xff1f; N & (N-1) 0 (N > 0)算题&#xff1a; 力扣 https://leetcode.cn/POJ http://poj.org/ 算法 算法概念 算法代表&#xff1a; 高效率和低存储 内存占用小、CPU占用小、运算速度快 算法的高效率与低存储&#xff1a;内存 C…

Oracle 定时任务job实际应用

Oracle 定时任务job实际应用 一、Oracle定时任务简介二、dbms_job涉及到的知识点三、初始化相关参数job_queue_processes四、实际创建一个定时任务&#xff08;一分钟执行一次&#xff09;&#xff0c;实现定时一分钟往表中插入数据4.1 创建需要定时插入数据的目标表4.2 创建定…

如何为Google Play的应用制作宣传视频

在用户打开我们的应用页面时&#xff0c;最先看到的是宣传视频&#xff0c;这是吸引潜在用户注意力的绝好机会&#xff0c;所以这对于 Google Play 来说是一件大事。 宣传视频和屏幕截图一起&#xff0c;都是引导用户去使用我们应用程序的第一步&#xff0c;能够让他们一打开应…

sibelius西贝柳斯2023中文版是什么打谱软件?如何下载

Sibelius是一款专业的音乐制谱软件&#xff0c;被广泛用于各类音乐创作、教育、表演等领域。通过Sibelius&#xff0c;用户可以快速、准确地制作各种类型的音乐谱面&#xff0c;同时支持多种音乐符号和效果的编辑、自定义和输出&#xff0c;可谓是音乐领域的必备工具之一。Sibe…

SQL Server tempdb 闩锁争用

当你反复创建和删除 TempDb 对象&#xff08;临时表、表变量等&#xff09;时&#xff0c;你可能会在 tempdb 中看到页面的闩锁争用。当你注意到tempdb 上的 PAGELATCH_* 争用&#xff08;sysprocesses 中的等待资源以 2: 开头&#xff09;时&#xff0c;请检查闩锁等待是否在 …

【语音之家】AI产业沙龙 —— 三星语言智能团队ICASSP2023论文分享会

由CCF语音对话与听觉专委会 、中国人工智能产业发展联盟&#xff08;AIIA&#xff09;评估组、三星电子中国研究院、语音之家、希尔贝壳共同主办的【语音之家】AI产业沙龙——三星语言智能团队ICASSP2023论文分享会&#xff0c;将于2023年4月25日18:30-20:20线上直播。 沙龙简介…

ERP系统有哪些功能模块?

一、ERP系统是什么 现在市面上的管理软件有很多&#xff0c;不少企业都会去选择一些操作简单便捷的软件&#xff0c;优化工作流程&#xff0c;提高工作效率&#xff0c;其中ERP系统就是常见的一种&#xff0c;ERP是企业资源计划(Enterprise Resource Planning)的简称&#xff…

深入了解Lock同步锁的优化

大家好&#xff0c;我是易安。 今天我们来简单谈谈在JDK1.5之后&#xff0c;Java提供的Lock同步锁。 相对于需要JVM隐式获取和释放锁的Synchronized同步锁&#xff0c;Lock同步锁&#xff08;以下简称Lock锁&#xff09;需要的是显示获取和释放锁&#xff0c;这就为获取和释放锁…

防止机械/移动硬盘休眠 - NoSleepHD

防止机械/移动硬盘休眠 - NoSleepHD 前言解决方案计算机硬盘移动硬盘 前言 机械硬盘休眠后唤醒需要一定时间&#xff0c;且频繁的启动和停止并不有利于硬盘的寿命&#xff0c;因此可根据自身需求防止机械硬盘休眠&#xff0c;下文以Win10系统为例介绍解决方案。 值得一提的是…

Java核心技术 卷1-总结-9

Java核心技术 卷1-总结-9 使用异常机制的技巧为什么要使用泛型程序设计定义简单泛型类泛型方法类型变量的限定 泛型类型的继承规则 使用异常机制的技巧 1.异常处理不能代替简单的测试。 使用异常的基本规则是&#xff1a;只在异常情况下使用异常机制。 2.不要过分地细化异常。…

第三章(3):深入理解Spacy库基本使用方法

第三章&#xff08;3&#xff09;&#xff1a;深入理解Spacy库基本使用方法 本章主要介绍了Spacy库的基本使用方法&#xff0c;包括安装、加载语言模型、分句、分词、词性标注、停用词识别、命名实体识别、依存分析和词性还原等内容。重点介绍了每个步骤的具体实现方式和应用场…

【TortoiseGit】安装和配置

转自 【TortoiseGit】TortoiseGit安装和配置详细说明_No8g攻城狮的博客-CSDN博客 一、TortoiseGit 简介 TortoiseGit 是基于 TortoiseSVN 的 Git 版本的 Windows Shell 界面。它是开源的&#xff0c;可以完全使用免费软件构建。 TortoiseGit 支持你执行常规任务&#xff0c;…