33 - MySQL调优之索引:索引的失效与优化

不知道你是否跟我有过同样的经历,那就是作为一个开发工程师,经常被 DBA 叫过去“批评”,而最常见的就是申请创建新的索引或发现慢 SQL 日志了。

记得之前有一次迭代一个业务模块的开发,涉及到了一个新的查询业务,需要根据商品类型、订单状态筛选出需要的订单,并以订单时间进行排序。由于 sku 的索引已经存在了,我在完成业务开发之后,提交了一个创建 status 的索引的需求,理由是 SQL 查询需要使用到这两个索引:

select * from order where status =1 and sku=10001 order by create_time asc

然而,DBA 很快就将这个需求驳回了,并给出了重建一个 sku、status 以及 create_time 组合索引的建议,查询顺序也改成了 sku=10001 and status=1。当时我是知道为什么要重建组合索引,但却无法理解为什么要添加 create_time 这列进行组合。

从执行计划中,我们可以发现使用到了索引,那为什么 DBA 还要求将 create_time 这一列加入到组合索引中呢?这个问题我们在[第 32 讲]中提到过,相信你也已经知道答案了。通过故事我们可以发现索引知识在平时开发时的重要性,然而它又很容易被我们忽略,所以今天我们就来详细聊一聊索引。

1、MySQL 索引存储结构

索引是优化数据库查询最重要的方式之一,它是在 MySQL 的存储引擎层中实现的,所以每一种存储引擎对应的索引不一定相同。我们可以通过下面这张表格,看看不同的存储引擎分别支持哪种索引类型:

img

B+Tree 索引和 Hash 索引是我们比较常用的两个索引数据存储结构,B+Tree 索引是通过 B+ 树实现的,是有序排列存储,所以在排序和范围查找方面都比较有优势。如果你对 B+Tree 索引不够了解,可以通过该链接了解下它的数据结构原理。

Hash 索引相对简单些,只有 Memory 存储引擎支持 Hash 索引。Hash 索引适合 key-value 键值对查询,无论表数据多大,查询数据的复杂度都是 O(1),且直接通过 Hash 索引查询的性能比其它索引都要优越。

在创建表时,无论使用 InnoDB 还是 MyISAM 存储引擎,默认都会创建一个主键索引,而创建的主键索引默认使用的是 B+Tree 索引。不过虽然这两个存储引擎都支持 B+Tree 索引,但它们在具体的数据存储结构方面却有所不同。

InnoDB 默认创建的主键索引是聚族索引(Clustered Index),其它索引都属于辅助索引(Secondary Index),也被称为二级索引或非聚族索引。接下来我们通过一个简单的例子,说明下这两种索引在存储数据中的具体实现。

首先创建一张商品表,如下:

CREATE TABLE `merchandise`  (
  `id` int(11) NOT NULL,
  `serial_no` varchar(20)  DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `unit_price` decimal(10, 2) DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

然后新增了以下几行数据,如下:

img

如果我们使用的是 MyISAM 存储引擎,由于 MyISAM 使用的是辅助索引,索引中每一个叶子节点仅仅记录的是每行数据的物理地址,即行指针,如下图所示:

img

如果我们使用的是 InnoDB 存储引擎,由于 InnoDB 使用的是聚族索引,聚族索引中的叶子节点则记录了主键值、事务 id、用于事务和 MVVC 的回流指针以及所有的剩余列,如下图所示:

img

基于上面的图示,如果我们需要根据商品编码查询商品,我们就需要将商品编码 serial_no 列作为一个索引列。此时创建的索引是一个辅助索引,与 MyISAM 存储引擎的主键索引的存储方式是一致的,但叶子节点存储的就不是行指针了,而是主键值,并以此来作为指向行的指针。这样的好处就是当行发生移动或者数据分裂时,不用再维护索引的变更。

如果我们使用主键索引查询商品,则会按照 B+ 树的索引找到对应的叶子节点,直接获取到行数据:

select * from merchandise where id=7

如果我们使用商品编码查询商品,即使用辅助索引进行查询,则会先检索辅助索引中的 B+ 树的 serial_no,找到对应的叶子节点,获取主键值,然后再通过聚族索引中的 B+ 树检索到对应的叶子节点,然后获取整行数据。这个过程叫做回表。

在了解了索引的实现原理后,我们再来详细了解下平时建立和使用索引时,都有哪些调优方法呢?

2、覆盖索引优化查询

假设我们只需要查询商品的名称、价格信息,我们有什么方式来避免回表呢?我们可以建立一个组合索引,即商品编码、名称、价格作为一个组合索引。如果索引中存在这些数据,查询将不会再次检索主键索引,从而避免回表。

从辅助索引中查询得到记录,而不需要通过聚族索引查询获得,MySQL 中将其称为覆盖索引。使用覆盖索引的好处很明显,我们不需要查询出包含整行记录的所有信息,因此可以减少大量的 I/O 操作。

通常在 InnoDB 中,除了查询部分字段可以使用覆盖索引来优化查询性能之外,统计数量也会用到。例如,在[第 32 讲]我们讲 SELECT COUNT(*) 时,如果不存在辅助索引,此时会通过查询聚族索引来统计行数,如果此时正好存在一个辅助索引,则会通过查询辅助索引来统计行数,减少 I/O 操作。

通过 EXPLAIN,我们可以看到 InnoDB 存储引擎使用了 idx_order 索引列来统计行数,如下图所示:

img

3、自增字段作主键优化查询

上面我们讲了 InnoDB 创建主键索引默认为聚族索引,数据被存放在了 B+ 树的叶子节点上。也就是说,同一个叶子节点内的各个数据是按主键顺序存放的,因此,每当有一条新的数据插入时,数据库会根据主键将其插入到对应的叶子节点中。

如果我们使用自增主键,那么每次插入的新数据就会按顺序添加到当前索引节点的位置,不需要移动已有的数据,当页面写满,就会自动开辟一个新页面。因为不需要重新移动数据,因此这种插入数据的方法效率非常高。

如果我们使用非自增主键,由于每次插入主键的索引值都是随机的,因此每次插入新的数据时,就可能会插入到现有数据页中间的某个位置,这将不得不移动其它数据来满足新数据的插入,甚至需要从一个页面复制数据到另外一个页面,我们通常将这种情况称为页分裂。页分裂还有可能会造成大量的内存碎片,导致索引结构不紧凑,从而影响查询效率。

因此,在使用 InnoDB 存储引擎时,如果没有特别的业务需求,建议使用自增字段作为主键。

4、前缀索引优化

前缀索引顾名思义就是使用某个字段中字符串的前几个字符建立索引,那我们为什么需要使用前缀来建立索引呢?

我们知道,索引文件是存储在磁盘中的,而磁盘中最小分配单元是页,通常一个页的默认大小为 16KB,假设我们建立的索引的每个索引值大小为 2KB,则在一个页中,我们能记录 8 个索引值,假设我们有 8000 行记录,则需要 1000 个页来存储索引。如果我们使用该索引查询数据,可能需要遍历大量页,这显然会降低查询效率。

减小索引字段大小,可以增加一个页中存储的索引项,有效提高索引的查询速度。在一些大字符串的字段作为索引时,使用前缀索引可以帮助我们减小索引项的大小。

不过,前缀索引是有一定的局限性的,例如 order by 就无法使用前缀索引,无法把前缀索引用作覆盖索引。

5、防止索引失效

当我们习惯建立索引来实现查询 SQL 的性能优化后,是不是就万事大吉了呢?当然不是,有时候我们看似使用到了索引,但实际上并没有被优化器选择使用。

对于 Hash 索引实现的列,如果使用到范围查询,那么该索引将无法被优化器使用到。也就是说 Memory 引擎实现的 Hash 索引只有在“=”的查询条件下,索引才会生效。我们将 order 表设置为 Memory 存储引擎,分析查询条件为 id<10 的 SQL,可以发现没有使用到索引。

img

如果是以 % 开头的 LIKE 查询将无法利用节点查询数据:

img

当我们在使用复合索引时,需要使用索引中的最左边的列进行查询,才能使用到复合索引。例如我们在 order 表中建立一个复合索引 idx_user_order_status(order_nostatususer_id),如果我们使用 order_no、order_no+status、order_no+status+user_id 以及 order_no+user_id 组合查询,则能利用到索引;而如果我们用 status、status+user_id 查询,将无法使用到索引,这也是我们经常听过的最左匹配原则。

img

img

如果查询条件中使用 or,且 or 的前后条件中有一个列没有索引,那么涉及的索引都不会被使用到。

img

所以,你懂了吗?作为一名开发人员,如果没有熟悉 MySQL,特别是 MySQL 索引的基础知识,很多时候都将被 DBA 批评到怀疑人生。

6、总结

在大多数情况下,我们习惯使用默认的 InnoDB 作为表存储引擎。在使用 InnoDB 作为存储引擎时,创建的索引默认为 B+ 树数据结构,如果是主键索引,则属于聚族索引,非主键索引则属于辅助索引。基于主键查询可以直接获取到行信息,而基于辅助索引作为查询条件,则需要进行回表,然后再通过主键索引获取到数据。

如果只是查询一列或少部分列的信息,我们可以基于覆盖索引来避免回表。覆盖索引只需要读取索引,且由于索引是顺序存储,对于范围或排序查询来说,可以极大地极少磁盘 I/O 操作。

除了了解索引的具体实现和一些特性,我们还需要注意索引失效的情况发生。如果觉得这些规则太多,难以记住,我们就要养成经常检查 SQL 执行计划的习惯。

7、思考题

假设我们有一个订单表 order_detail,其中有主键 id、主订单 order_id、商品 sku 等字段,其中该表有主键索引、主订单 id 索引。

现在有一个查询订单详情的 SQL 如下,查询订单号范围在 5000~10000,请问该查询选择的索引是什么?有什么方式可以强制使用我们期望的索引呢?

select * from order_detail where order_id between 5000 and 10000;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

考试周刊杂志考试周刊杂志社考试周刊编辑部2023年第46期目录

教育教学研究 丰富作业形式 拓展课堂教学——“双减”下初中英语优化作业设计探析 王慧; 1-5 博学慎思明辨 撬动思维杠杆——论“思辨性阅读与表达”学习任务群范式构建 丁亚琴; 6-10《考试周刊》投稿邮箱&#xff1a;cn7kantougao163.com(注明投稿“《考试周刊》”) 崔…

Leetcode—35.搜索插入位置【简单】

2023每日刷题&#xff08;四十&#xff09; Leetcode—35.搜索插入位置 实现代码 int lower_bound(int* arr, int numsSize, int tar) {int left 0, right numsSize;int mid;// 左闭右开[left, right)while(left < right) {mid left (right - left) / 2;if(arr[mid] &…

企业网络中的身份安全

随着近年来数字化转型的快速发展&#xff0c;企业使用的数字身份数量急剧增长。身份不再仅仅局限于用户。它们现在扩展到设备、应用程序、机器人、第三方供应商和组织中员工以外的其他实体。即使在用户之间&#xff0c;也存在不同类型的身份&#xff0c;例如属于IT管理员、远程…

vue3+ts 指令拖拽案例

<template><div class"box" v-move><div class"header"></div><div>内容</div></div> </template><script setup lang"ts"> import { ref, Directive, DirectiveBinding } from "vu…

【EasyExcel】导出excel并支持自定义设置数据行背景颜色等

需求背景&#xff1a; 根据查询条件将列表数据导出&#xff0c;并筛选出满足某个条件的数据&#xff0c;将满足条件的数据的背景颜色设置成黄色。 &#xff08;本文例子如&#xff1a;name出现的次数大于等于2&#xff0c;将相关数据背景颜色都设置为黄色&#xff09; …

智慧工厂人员定位系统源码,融合位置物联网、GIS可视化等技术,实现对人员、物资精确定位管理

智慧工厂人员定位系统源码&#xff0c;UWB高精度定位系统源码 随着中国经济发展进入新常态&#xff0c;在资源和环境约束不断强化的背景下&#xff0c;创新驱动传统制造向智能制造转型升级&#xff0c;越发成为企业生存发展的关键。智能工厂作为实现智能制造的重要载体&#xf…

[操作系统]京东一面~进程相关汇总

1 进程、线程、协程的概念 进程&#xff1a; 静态程序的运行状态就叫进程。是资源分配的基本单位。 线程&#xff1a; 是进程的一个执行单元&#xff0c;是进程内的调度实体。是CPU调度的独立单位。线程也被称为轻量级进程。 协程&#xff1a; 是一种比线程更加轻量级的存在。…

多线程04 死锁,线程可见性

前言 前面我们讲到了简单的线程安全问题以及简单的解决策略 其根本原因是cpu底层对线程的抢占式调度策略,随机调度 其他还有一些场景的问题如下 1.多个线程同时修改一个变量问题 2.执行的操作指令本身不是原子的 比如自增操作就分为三步,加载,自增,保存 3.内存可见性问题 4.指令…

cesium不同版本对3dtiles的渲染效果不同,固定光照的优化方案

cesium不同版本对3dtiles的渲染效果不同&#xff0c;固定光照的优化方案&#xff0c;避免map.fixedLight true,导致的光照效果太强&#xff0c;模型太亮的问题。 问题来源&#xff1a; 1.Cesium1.47版本加载tileset.json文件跟Mars3d最新版加载文件存在差异效果 Cesium1.47…

小航助学题库蓝桥杯题库stem选拔赛(22年3月)(含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSD…

ESP32-Web-Server编程-建立第一个网页

ESP32-Web-Server编程-建立第一个网页 HTTP 简述 可能你每天都要刷几个短视频&#xff0c;打开几个网页来娱乐一番。当你打开一个网络上的视频或者图片时&#xff0c;其实际发生了下面的流程&#xff1a; 其中客户端就是你的浏览器啦&#xff0c;服务器就是远程一个存放视频或…

openGauss学习笔记-133 openGauss 数据库运维-例行维护-日维护检查项

文章目录 openGauss学习笔记-133 openGauss 数据库运维-例行维护-日维护检查项133.1 检查openGauss状态133.2 检查锁信息133.3 统计事件数据133.4 对象检查133.5 SQL报告检查133.6 备份133.7 基本信息检查 openGauss学习笔记-133 openGauss 数据库运维-例行维护-日维护检查项 …

系列二十二、各种注解

一、Import # 用法 1&#xff09;Import(User.class)&#xff1a;如果导入的是配置类&#xff0c;将会按照配置类正常解析&#xff0c;如果是个普通类就会解析成bean 2&#xff09;Import&#xff08;实现了ImportSelector接口的类.class&#xff09;&#xff1a;可以一次性注册…

Linux 栈回溯

目录 前言一、什么是栈回溯&#xff1f;二、栈回溯的实现原理三、参考阅读 前言 日常工作中&#xff0c;我们在开发软件程序时&#xff0c;经常会遇到程序奔溃的问题&#xff0c;导致程序奔溃的原因有很多&#xff0c;我们一般都是定位到相关代码&#xff0c;再去查询具体原因。…

FinOps和DevOps的未来会怎样?

FinOps&#xff08;或财务运营&#xff09;是一种文化实践&#xff0c;它将财务责任引入云的可变支出模型。这是一种将系统、最佳实践和文化相结合的战略方法&#xff0c;可提高组织了解云成本并做出明智决策的能力。 本质上&#xff0c;FinOps 是一个管理云运营费用&#xff…

C++:OJ练习(每日练习系列)

编程题&#xff1a; 题一&#xff1a;把字符串转换成整数 把字符串转换成整数_牛客题霸_牛客网 示例1 输入&#xff1a; "2147483647" 返回值&#xff1a; 2147483647思路一&#xff1a; 第一步&#xff1a;it从str的第一个字符开始遍历&#xff0c;定义一个最后输…

使用 ChatGPT 创建 Makefile 构建系统:从 Docker 开始

使用 Docker 搭配 ChatGPT 创建 Makefile 构建系统 Makefile 构建系统是嵌入式软件团队实现其开发流程现代化的基础。构建系统不仅允许开发人员选择各种构建目标&#xff0c;还可以将这些构建集成到持续集成/持续部署 (CI/CD) 流程中。使用诸如 ChatGPT 这样的人工智能 (AI) 工…

【docker】docker的基础命令

基础操作 docker info #查看docker的基本信息docker version #查看docker版本信息一、镜像操作 1、搜索镜像 docker search nginx2、下载镜像 docker pull nginx#从仓库中下载镜像&#xff0c;若没有指定标签&#xff0c;则下载最新的版本&#xff0c;也就是标签为: lat…

使用opencv实现图像滤波

1 图像滤波介绍 滤波是信号和图像处理中的基本任务之一&#xff0c;其旨在有选择地提取图像的某些特征&#xff0c;可以用于在给定应用程序的上下文中传达重要信息&#xff0c;例如&#xff0c;去除图像中的噪声、提取所需的视觉特征、图像重采样等。 1.1 图像滤波理论 图像…

Matplotlib线形图的创建_Python数据分析与可视化

线形图的创建 绘制线形图设置颜色和风格设置坐标轴上下限设置图形标签 绘制线形图 在所有图形中&#xff0c;最简单的应该就是线性方程y f (x) 的可视化了。来看看如何创建这个简单的线形图。要画Matplotlib图形时&#xff0c;都需要先创建一个图形fig 和一个坐标轴ax。创建图…