听说mysql还会选错索引

2023-10-28 14:08

本文主要是介绍听说mysql还会选错索引,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

大家都知道,mysql 一个表中可以创建多个索引,但是在执行一条查询语句的时候,mysql 只能选一个索引,如果我们没有指定 mysql 使用某个索引,那么就是由 mysql 的优化器来决定要使用哪个索引了,然而,mysql 也是会有选错的时候。

 

前面的文章,我们有介绍过执行一条查询 sql 语句分别会经历那些过程,执行一条sql语句都经历了什么? 存在多个索引的情况下,优化器一般会通过比较扫描行数、是否需要临时表以及是否需要排序等,来作为选择索引的判断依据。

 

我们先来新建一个表,创建两个普通索引。

 

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;

 

这里我们使用存储过程往表里插入 10w 测试数据,如果对 mysql 的存储过程不熟悉,请看我在代码中的注释,应该能看得懂得。

 

#定义分割符号,mysql 默认分割符为分号;,这里定义为 //
#分隔符的作用主要是告诉mysql遇到下一个 // 符号即执行上面这一整段sql语句
delimiter //#创建一个存储过程,并命名为 testData
create procedure testData() #下面这段就是表示循环往表里插入10w条数据
begindeclare i int;set i=1;while(i<=100000)doinsert into t values(i, i, i);set i=i+1;end while;
end //  #这里遇到//符号,即执行上面一整段sql语句delimiter ; #恢复mysql分隔符为;call testData(); #调用存储过程

 

数据插入完成后,我们来看下面这条 sql 语句。

 

select * from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1;

 

由于主键id、a、b 三个字段的值其实都是一样的,所以其实这条 sql 语句的结果集为空,没有符合条件的记录。

 

我们来看看 mysql 该是怎么选择索引的,这里有三个索引可用,分别是主键索引、索引a、索引b。

 

如果选择主键索引虽然可以减少回表过程,但是只能走全表扫描,需要扫描 10w 条记录。

 

如果选择索引 a,则只需在 a 索引上扫描 1k 条记录,然后回到主键索引上过滤掉不满足 b 条件的记录,最后再按 b 排序即可。

 

如果选择索引 b,则需要在 b 索引上扫描 5w 条记录,然后同样回到主键索引上过滤掉不满足 a 条件的记录,因为索引有序,所以使用 b 索引不需要额外排序。

 

我们来使用执行计划看下 mysql 究竟会选择哪个索引。

 

mysql> explain select * from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1;
+----+-------------+-------+-------+---------------+------+---------+------+-------+------------------------------------+
| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows  | Extra                              |
+----+-------------+-------+-------+---------------+------+---------+------+-------+------------------------------------+
|  1 | SIMPLE      | t     | range | a,b           | b    | 5       | NULL | 50128 | Using index condition; Using where |
+----+-------------+-------+-------+---------------+------+---------+------+-------+------------------------------------+
1 row in set (0.12 sec)

 

可以看出 mysql 是选择使用索引 b,虽然扫描行数要多一些,但因为索引本身是有序的,使用索引 b 可以避免排序,mysql 认为这个排序的代价高于扫描行数。

 

上面这个选择是 mysql 优化器内部的分析,那么实际情况又如何呢,我们可以分别执行一下 sql 语句,使用 force index(a) 强制使用索引 a 来对比下,看下两者具体花费的时间。

 

mysql> select * from t where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1;
Empty set (0.65 sec)mysql> select * from t force index(a) where (a between 1 and 1000) and (b between 50000 and 100000) order by b limit 1;
Empty set (0.05 sec)

 

从结果可以看到其实使用索引 a 显然速度会更快,所以这就是属于 mysql 选错了索引的情况,那我们怎么避免这种情况呢,我们可以把 sql 语句改成下面这样的,即把 order by b 改成 order by b,a 。

 

select * from t where a between 1 and 1000 and b between 50000 and 100000 order by b,a limit 1;

 

这样的话,在 mysql 看来无论是使用索引 a 还是索引 b 都需要排序了,那就只能选择扫描行更少的索引了,所以 mysql 会选择索引 a,从而达到避免 mysql 选错索引的目的,我们可以看下优化后的这条 sql 的执行计划。

 

mysql> explain select * from t where a between 1 and 1000 and b between 50000 and 100000 order by b,a limit 1;
+----+-------------+-------+-------+---------------+------+---------+------+------+----------------------------------------------------+
| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows | Extra                                              |
+----+-------------+-------+-------+---------------+------+---------+------+------+----------------------------------------------------+
|  1 | SIMPLE      | t     | range | a,b           | a    | 5       | NULL |  999 | Using index condition; Using where; Using filesort |
+----+-------------+-------+-------+---------------+------+---------+------+------+----------------------------------------------------+
1 row in set (0.00 sec)

 

大多数情况下,mysql 都会选择正确的索引,选错索引算是比较少见的特殊情况了,文中的例子也是个特例,仅是给大家提供一个分析思路,当你遇到一些已经使用了索引但依然比较慢的 sql 语句的时候,可以尝试分析是否是 mysql 选错了索引的原因。

 

其实还有一些情况,会导致 mysql 选错索引,就是 mysql 预估扫描行的数据不够准确,而这个不准确通常是数据表有频繁的删除或更新操作导致的数据空洞造成的,关于这个原因,我会在后面再详细讲。

 

这篇文章如果对你有些启发,不妨点个赞吧,感谢支持,当然如果对文中有不太明白的地方,欢迎留言。另外,不知道大家对 explain 这个命令熟悉不,如果不熟悉的话,我考虑再单独写一篇关于 explain 使用的文章。

 

这篇关于听说mysql还会选错索引的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/293727

相关文章

MySQL的JDBC编程详解

《MySQL的JDBC编程详解》:本文主要介绍MySQL的JDBC编程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、前置知识1. 引入依赖2. 认识 url二、JDBC 操作流程1. JDBC 的写操作2. JDBC 的读操作总结前言本文介绍了mysq

java.sql.SQLTransientConnectionException连接超时异常原因及解决方案

《java.sql.SQLTransientConnectionException连接超时异常原因及解决方案》:本文主要介绍java.sql.SQLTransientConnectionExcep... 目录一、引言二、异常信息分析三、可能的原因3.1 连接池配置不合理3.2 数据库负载过高3.3 连接泄漏

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

MySQL中On duplicate key update的实现示例

《MySQL中Onduplicatekeyupdate的实现示例》ONDUPLICATEKEYUPDATE是一种MySQL的语法,它在插入新数据时,如果遇到唯一键冲突,则会执行更新操作,而不是抛... 目录1/ ON DUPLICATE KEY UPDATE的简介2/ ON DUPLICATE KEY UP

MySQL分库分表的实践示例

《MySQL分库分表的实践示例》MySQL分库分表适用于数据量大或并发压力高的场景,核心技术包括水平/垂直分片和分库,需应对分布式事务、跨库查询等挑战,通过中间件和解决方案实现,最佳实践为合理策略、备... 目录一、分库分表的触发条件1.1 数据量阈值1.2 并发压力二、分库分表的核心技术模块2.1 水平分

Python与MySQL实现数据库实时同步的详细步骤

《Python与MySQL实现数据库实时同步的详细步骤》在日常开发中,数据同步是一项常见的需求,本篇文章将使用Python和MySQL来实现数据库实时同步,我们将围绕数据变更捕获、数据处理和数据写入这... 目录前言摘要概述:数据同步方案1. 基本思路2. mysql Binlog 简介实现步骤与代码示例1

使用shardingsphere实现mysql数据库分片方式

《使用shardingsphere实现mysql数据库分片方式》本文介绍如何使用ShardingSphere-JDBC在SpringBoot中实现MySQL水平分库,涵盖分片策略、路由算法及零侵入配置... 目录一、ShardingSphere 简介1.1 对比1.2 核心概念1.3 Sharding-Sp

MySQL 表空却 ibd 文件过大的问题及解决方法

《MySQL表空却ibd文件过大的问题及解决方法》本文给大家介绍MySQL表空却ibd文件过大的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录一、问题背景:表空却 “吃满” 磁盘的怪事二、问题复现:一步步编程还原异常场景1. 准备测试源表与数据

Mac电脑如何通过 IntelliJ IDEA 远程连接 MySQL

《Mac电脑如何通过IntelliJIDEA远程连接MySQL》本文详解Mac通过IntelliJIDEA远程连接MySQL的步骤,本文通过图文并茂的形式给大家介绍的非常详细,感兴趣的朋友跟... 目录MAC电脑通过 IntelliJ IDEA 远程连接 mysql 的详细教程一、前缀条件确认二、打开 ID

MySQL的配置文件详解及实例代码

《MySQL的配置文件详解及实例代码》MySQL的配置文件是服务器运行的重要组成部分,用于设置服务器操作的各种参数,下面:本文主要介绍MySQL配置文件的相关资料,文中通过代码介绍的非常详细,需要... 目录前言一、配置文件结构1.[mysqld]2.[client]3.[mysql]4.[mysqldum