MySQL索引(三):选错索引

news2024/12/26 18:31:57

优化器选择索引的目的,是找到一个最优的执行方案,并用最小的代价去执行语句。

思考


假设有表结构:

-- T表结构:
CREATE TABLE `t` (
    `id` int(11) NOT NULL,
    `a` int(11) DEFAULT NULL,
    `b` int(11) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `a` (`a`),
    KEY `b` (`b`)
) ENGINE=InnoDB;

往表t中插入10万行记录, 取值按整数递增, 即:(1,1,1), (2,2,2), (3,3,3) 直到 (100000,100000,100000)。

插入数据的存储过程如下:

-- 插入数据存储过程:
delimiter ;;
create procedure idata()
begin
    declare i int;
    set i=1;
    while(i<=100000)do
        insert into t values(i, i, i);
        set i=i+1;
    end while;
end;;
delimiter ;

-- 调用存储过程
call idata();

接下来, 我们分析一条SQL语句:

mysql> select * from t where a between 10000 and 20000;

单独执行上述语句时,查询执行计划如下:

从执行计划来看,走了索引a,符合预期。

接着我们再做如下操作:

这里, session A的操作你已经很熟悉了, 它就是开启了一个事务。 随后, session B把数据都删除后, 又调用了 idata这个存储过程, 插入了10万行数据。

这时候, session B的查询语句select * from t where a between 10000 and 20000就不会再选择索引a了。可以通过慢查询日志(slow log) 来查看一下具体的执行情况。

为了说明优化器选择的结果是否正确, 我增加了一个对照, 即: 使用force index(a)来让优化器强制使用索引a。

在Session B中执行如下三条SQL语句:

set long_query_time=0;
select * from t where a between 10000 and 20000; /*Q1*/
select * from t force index(a) where a between 10000 and 20000;/*Q2*/
  • 第一句, 是将慢查询日志的阈值设置为0, 表示这个线程接下来的语句都会被记录入慢查询日志中;
  • 第二句, Q1是session B原来的查询;
  • 第三句, Q2是加了force index(a)来和session B原来的查询语句执行情况对比。

这三条SQL语句执行完成后的慢查询日志结果如下:

可以看到, Q1扫描了10万行, 显然是走了全表扫描, 执行时间是40毫秒。 Q2扫描了10001行,执行了21毫秒。 也就是说, 我们在没有使用force index的时候, MySQL用错了索引, 导致了更长的执行时间。

这个例子对应的是我们平常不断地删除历史数据和新增数据的场景。 这时, MySQL竟然会选错索引, 是不是有点奇怪呢?下面我们就来看一下优化器是如何选择索引的。

问:begin 和 start transaction with consistent snapshot 的区别是什么?

创建一致性视图的时机不同。

1)begin 是在第一次执行 SELECT 时创建一致性视图。

2)start transaction with consistent snapshot 是在该语句执行后,立即创建一致性视图。

优化器的逻辑


优化器选择索引的目的, 是找到一个最优的执行方案, 并用最小的代价去执行语句。

问1:扫描行数在优化器选择索引时发挥着怎样作用?

在数据库里面, 扫描行数是影响执行代价的因素之一。 扫描的行数越少, 意味着访问磁盘数据的次数越少, 消耗的CPU资源越少。当然, 扫描行数并不是唯一的判断标准, 优化器还会结合是否使用临时表、 是否排序等因素进行综合判断。

问2:扫描行数是怎么判断的?

MySQL在真正开始执行语句之前,并不能精确地知道满足这个条件的记录有多少条,而只能根据统计信息来估算记录数。

这个统计信息就是索引的“区分度”。 显然, 一个索引上不同的值越多, 这个索引的区分度就越好。 而一个索引上不同的值的个数, 我们称之为“基数”(cardinality) 。 也就是说, 这个基数越大, 索引的区分度越好。

可以使用 show index from xxx 方法, 看到一个索引的基数。 如下图所示, 就是表t的show index的结果。 虽然这个表的每一行的三个字段都是一样的, 但是在统计信息中, 这三个索引的基数值并不同, 而且其实都不准确。

问3:MySQL是怎样得到索引的基数的呢?

下面简单介绍一下MySQL采样统计的方法。

  • 采样统计的时候, InnoDB默认会选择N个数据页, 统计这些页面上的不同值, 得到一个平均值, 然后乘以这个索引的页面数, 就得到了这个索引的基数。
  • 而数据表是会持续更新的, 索引统计信息也不会固定不变。 所以, 当变更的数据行数超过1/M的时候, 会自动触发重新做一次索引统计。

问4:在MySQL中,存储索引统计的方式(N、M值的选择)是如何设置的?

可以通过设置参数innodb_stats_persistent的值来选择:

  • 设置为on的时候, 表示统计信息会持久化存储。 这时, 默认的N是20, M是10。
  • 设置为off的时候, 表示统计信息只存储在内存中。 这时, 默认的N是8, M是16。

由于是采样统计, 所以不管N是20还是8, 这个基数都是很容易不准的。从上图可知,索引统计值(cardinality列,即扫描行数估计值)虽然不够准确,但大体上还是差不多的,那就说明选错索引还有别的原因。

注1:扫描行数不同于select count,是一个统计值,因而并不是非常精确的;

主2:主键索引的扫描行数直接通过show table status中表的行数(Rows)来估计,并不是通过上述采样方式获取的;

问5:为什么要采样统计呢?

因为把整张表取出来一行行统计, 虽然可以得到精确的结果, 但是代价太高了, 所以只能选择“采样统计”。

接下来, 我们再一起看看,在Session B中优化器预估的这两个语句的扫描行数是多少。

注:rows这个字段表示的是预计扫描行数。

其中, Q1的结果还是符合预期的, rows的值是104620(和采样统计的扫描行数接近); 但是Q2的rows值是37116, 偏差就大了。 而上图中我们用explain命令和慢日志记录看到的实际扫描rows是只有10001行, 是这个偏差误导了优化器的判断。

问6:上述优化器预估的两个语句的扫描行数分别为104620、37116,优化器为什么放着扫描37000行的执行计划不用, 却选择了扫描行数是100000的执行计划呢?

这是因为, 如果使用索引a, 每次从索引a上拿到一个值, 都要回到主键索引上查出整行数据(即考虑了回表的代价),这个代价优化器也要算进去的。而如果选择扫描10万行, 是直接在主键索引上扫描的, 没有额外的代价。

优化器会估算这两个选择的代价, 从结果看来, 优化器认为直接扫描主键索引更快。 当然, 从执行时间看来, 这个选择并不是最优的。

问7:对于Q2的执行计划,查询1w行数据,查询计划得出的扫描行数为什么是3.7w?

session B中先delete 10w行数据,再通过call idata()插入了10万行数据;由于session A开启了一致性读,且事务并未提交,则之前插入的10万行数据不能删除。所以之前的10w行数据每一行都有两个版本,旧版本是delete之前的数据,新版本是标记为deleted的数据。一共要扫描3w行数据;再加上到主键索引回表的数据,接近4w行;

索引选择异常和处理


其实大多数时候优化器都能找到正确的索引, 但偶尔你还是会碰到我们上面举例的这两种情况:原本可以执行得很快的SQL语句, 执行速度却比你预期的慢很多, 你应该怎么办呢?

如果索引选择异常,可通过如下几种策略更正:

1)采用 force index 强行选择一个索引。该策略有如下缺点:

  • 格式不优美;
  • 如果索引改了名字,这个语句也得改比较麻烦;
  • 如果以后迁移到别的数据库,这个语法还可能不兼容;

2)修改SQL语句,引导MySQL使用我们期望的索引。一般不建议使用,使用条件比较苛刻。

3)新建一个更合适的索引,来提供给优化器做选择或删掉误用的索引。

4)执行analyze table xxx,更新统计值,重新分析表,使得统计值更加准确。(由于索引统计信息不准确导致选错索引,可以使用该方法解决)

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

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

相关文章

区块链学习笔记(2)--区块链的交易模型part1

模型基础 区块链的tx分为两种模型&#xff0c;分别是比特币为代表的UTXO&#xff08;Unspent Transaction Output&#xff09;模型&#xff0c;和以太坊为代表的Account模型。前者适用于货币记账&#xff0c;后者适用于链上应用。 UTXO模型 类似于现金的交易模型 一个tx包含…

Redis 基础、Redis 应用

Redis 基础 什么是 Redis&#xff1f; Redis &#xff08;REmote DIctionary Server&#xff09;是一个基于 C 语言开发的开源 NoSQL 数据库&#xff08;BSD 许可&#xff09;。与传统数据库不同的是&#xff0c;Redis 的数据是保存在内存中的&#xff08;内存数据库&#xf…

php7.4安装pg扩展-contos7

今天接到一个需求&#xff0c;就是需要用thinkphp6链接pg(postgresql)数据库。废话不多说&#xff0c;直接上操作步骤 一、安装依赖 yum install -y sqlite-devel libxml2 libxml2-devel openssl openssl-devel bzip2 bzip2-devel libcurl libcurl-devel libjpeg libjpeg-dev…

Linux中的常用基本指令(下)

Linux常用基本指令 Linux中的基本指令12.head指令13.tail指令简单解释重定向与管道(重要) 14.date指令(时间相关的指令)15.cal指令(不重要)16.find指令(灰常重要&#xff09;17.grep指令(重要)18.which指令和alias指令19.zip/unzip指令&#xff1a;20.tar指令&#xff08;重要&…

Android 还在使用LogCat打日志?XLog框架;日志打印到控制台,打印到文件中。

目录&#xff1a; 为什么要打印日志&#xff1f;XLog是什么XLog如何使用 一、为什么要打印日志&#xff1f; 日志是我们系统出现错误时&#xff0c;最快速有效的定位工具&#xff0c;没有日志给出的错误信息&#xff0c;遇到报错你就会一脸懵逼&#xff1b;而且日志还可以用来…

zabbix“专家坐诊”第266期问答

问题一 Q&#xff1a;zabbix编译升级主要工作是不是将PHP,nginx,zabbix都重新编译安装一遍&#xff0c;细节的先不说 A&#xff1a;升级zabbix就可以 Q&#xff1a;这个OID是哪个OID A&#xff1a;mib文件里面有个snmp oid的值 那个就是oid。https://blog.csdn.net/qq_508853…

第八课 Unity编辑器创建的资源优化_特效篇(Particle System)详解

无论是CPU还是GPU&#xff0c;粒子系统对其的影响面都是不容小觑的。随着项目的重度化和3A化&#xff0c;玩家的口味变挑剔了、游戏玩法复杂度变高了、画面的特效表现变复杂了......所以我们还是更加谨慎地对待粒子系统。 特效&#xff08;Particle System&#xff09; 游戏效…

王道考研编程题总结

我还在完善中&#xff0c;边复习边完善&#xff08;这个只是根据我自身总结的&#xff09; 一、 线性表 1. 结构体 #define MaxSize 40 typedef struct{ElemType data[MaxSize]&#xff1b;int length; }SqList 2. 编程题 1. 删除最小值 题意 &#xff1a;从顺序表中删除…

ubuntu20.04安装OpenPcdet,CUDA版本11.8,显卡4090

本文参考这2篇文章的内容&#xff1a;https://blog.csdn.net/jin15203846657/article/details/122735375#comments_25352667 https://zhuanlan.zhihu.com/p/642158810 记录了自己安装OpenPcdet的过程。 OpenPcdet的安装需要cuda和pytorch版本严格关联。本例的CUDA版本&#xf…

初识EasyFramework

一、获取EF Git地址&#xff1a;https://github.com/HiWenHao/EFrameworkGitee地址&#xff1a;https://gitee.com/wang_xiaoheiiii/EFramework视频合集&#xff1a;EasyFramework介绍_哔哩哔哩_bilibiliQQ群: 711540505 二、 下载并初步了解 1. 下载完成后&#xff0c;可以看…

爬虫获取的数据如何用于市场分析

目录 一、网络爬虫基础 HTML解析器 API接口 数据库抓取 二、数据预处理 数据清洗 数据转换 数据整合 三、市场分析应用 消费者行为分析 竞争对手分析 市场趋势预测 四、案例分析 数据获取 数据预处理 市场分析 总结 在当今数据驱动的商业环境中&#xff0c;市…

C++小碗菜之二:软件单元测试

“没有测试的代码重构不能称之为重构&#xff0c;它仅仅是垃圾代码的到处移动” ——Corey Haines 目录 前言 什么是单元测试&#xff1f; 单元测试的组成 单元测试的命名 单元测试的独立性 Google Test 单元测试的环境配置与使用 1. Ubuntu下安装 Google Test 2. 编写…

Kubernetes架构原则和对象设计

云原生学习路线导航页&#xff08;持续更新中&#xff09; 快捷链接 Kubernetes常见问题解答 本文从 Google Borg系统的架构设计开始&#xff0c;深入讲解Kubernetes架构及组件的基本原理 1.什么是云计算 1.1.传统行业应用 假设有10台服务器&#xff0c;两个应用。小规模管…

力扣-图论-1【算法学习day.51】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非…

学习笔记056——Docker日志的清理问题

文章目录 Docker日志的清理问题1、Docke日志所在位置2、日志清理 Docker日志的清理问题 Ubuntu上部署Docker&#xff0c;运行一段时间后&#xff0c;会累计很多的日志量。 如果不及时处理&#xff0c;会占用系统空间&#xff0c;影响系统性能。 如何处理日志累计过大的问题&…

Python3:Pytest框架parametrize报错in “parametrize“ the number of names (4)

Python3&#xff1a;Pytest框架parametrize报错in “parametrize“ the number of names (4) 排查原因&#xff1a;是pytest入参时&#xff0c;需要4个参数&#xff0c;但是提供了3个参数 test_tenant_list:- ["http://xx:8081/scheduler/v1/tenancy/list",{"co…

Linux 35.6 + JetPack v5.1.4之RTP实时视频Python框架

Linux 35.6 JetPack v5.1.4之RTP实时视频Python框架 1. 源由2. 思路3. 方法论3.1 扩展思考 - 慎谋而后定3.2 扩展思考 - 拒绝拖延或犹豫3.3 扩展思考 - 哲学思考3.4 逻辑实操 - 方法论 4 准备5. 分析5.1 gst-launch-1.05.1.1 xvimagesink5.1.2 nv3dsink5.1.3 nv3dsink sync05…

GIt (一) Git的安装,项目搭建,远程仓库,分支

文章目录 一、 版本控制1.1 集中式版本控制1.2 分布式版本控制 二、 Git的安装及配置2.1 安装2.2 Git的配置2.2 查看配置 三、 Git基本理论3.1 工作区域3.2 文件状态 四、Git项目的搭建与操作4.1 初始化Git仓库4.2 常见的操作4.2.1 文件添加到暂存区4.2.2 文件提交更新4.2.3 查…

iview upload clearFiles清除回显视图

iview upload 上传完文件之后清除内容&#xff0c;打开会回显视图&#xff0c;清除不掉 关闭弹框时主动清除回显内容即可this.$refs.uploads.clearFiles() <FormItem label"上传附件:" :label-width"formNameWidth"><Upload action"/fms/ap…

JAVA |日常开发中Servlet详解

JAVA &#xff5c;日常开发中Servlet详解 前言一、Servlet 概述1.1 定义1.2 历史背景 二、Servlet 的生命周期2.1 加载和实例化2.2 初始化&#xff08;init 方法&#xff09;2.3 服务&#xff08;service 方法&#xff09;2.4 销毁&#xff08;destroy 方法&#xff09; 三、Se…