听说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更新某个字段拼接固定字符串的实现

《MySQL更新某个字段拼接固定字符串的实现》在MySQL中,我们经常需要对数据库中的某个字段进行更新操作,本文就来介绍一下MySQL更新某个字段拼接固定字符串的实现,感兴趣的可以了解一下... 目录1. 查看字段当前值2. 更新字段拼接固定字符串3. 验证更新结果mysql更新某个字段拼接固定字符串 -

python连接本地SQL server详细图文教程

《python连接本地SQLserver详细图文教程》在数据分析领域,经常需要从数据库中获取数据进行分析和处理,下面:本文主要介绍python连接本地SQLserver的相关资料,文中通过代码... 目录一.设置本地账号1.新建用户2.开启双重验证3,开启TCP/IP本地服务二js.python连接实例1.

Spring Boot项目中结合MyBatis实现MySQL的自动主从切换功能

《SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能》:本文主要介绍SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能,本文分步骤给大家介绍的... 目录原理解析1. mysql主从复制(Master-Slave Replication)2. 读写分离3.

Ubuntu中远程连接Mysql数据库的详细图文教程

《Ubuntu中远程连接Mysql数据库的详细图文教程》Ubuntu是一个以桌面应用为主的Linux发行版操作系统,这篇文章主要为大家详细介绍了Ubuntu中远程连接Mysql数据库的详细图文教程,有... 目录1、版本2、检查有没有mysql2.1 查询是否安装了Mysql包2.2 查看Mysql版本2.

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

Python3.6连接MySQL的详细步骤

《Python3.6连接MySQL的详细步骤》在现代Web开发和数据处理中,Python与数据库的交互是必不可少的一部分,MySQL作为最流行的开源关系型数据库管理系统之一,与Python的结合可以实... 目录环境准备安装python 3.6安装mysql安装pymysql库连接到MySQL建立连接执行S

MySQL双主搭建+keepalived高可用的实现

《MySQL双主搭建+keepalived高可用的实现》本文主要介绍了MySQL双主搭建+keepalived高可用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、测试环境准备二、主从搭建1.创建复制用户2.创建复制关系3.开启复制,确认复制是否成功4.同

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

Mysql表的简单操作(基本技能)

《Mysql表的简单操作(基本技能)》在数据库中,表的操作主要包括表的创建、查看、修改、删除等,了解如何操作这些表是数据库管理和开发的基本技能,本文给大家介绍Mysql表的简单操作,感兴趣的朋友一起看... 目录3.1 创建表 3.2 查看表结构3.3 修改表3.4 实践案例:修改表在数据库中,表的操作主要

mysql出现ERROR 2003 (HY000): Can‘t connect to MySQL server on ‘localhost‘ (10061)的解决方法

《mysql出现ERROR2003(HY000):Can‘tconnecttoMySQLserveron‘localhost‘(10061)的解决方法》本文主要介绍了mysql出现... 目录前言:第一步:第二步:第三步:总结:前言:当你想通过命令窗口想打开mysql时候发现提http://www.cpp