in/or到底能不能用索引应该是肯定的,但有时生效有时不生效,这个能不能量化计算?这是本文想讨论和解答的问题。


in到底用不用索引感觉像一桩悬疑片!古早时期的面经,统一说不走索引,在一些程序员脑海中从此留下不可磨灭的印记。

有些从业时间较长的程序员脑子里的第一反应就是不走索引,上个月我就曾经被同事这样质疑过。

但是那是mysql5.5以前的老黄历了,现在都到8.0+了,5.5(甚至更早)以后可以肯定的是它会走索引。

但必然走索引吗?不一定。

我搜索引擎上搜索关键词 in/or 和 索引,出来一大片文章,一般都会说,in/or能走索引,但后面跟的条件个数多了就不走索引了。

但问题就来了,这个多了到底是多少才算多?

对于一个动态查询的SQL,我咋知道到底走不走索引?

如何量化计算呢?

这时候就语焉不详或者直接跳过。

大名鼎鼎的《阿里巴巴JAVA开发手册》倒是一刀切。

最好不超过1000。

人家这规范只是推荐,也不是强制,是吧,不能吐槽。

而且超过1000就算用上了range级别的查询,那可能也快不到哪里去啊,对于要求快速响应的互联网需求来说这推荐好像没毛病。

但这不是重点,今天的重点在于,我一定要搞清楚,在保证explain 的type为range而不是ALL全表扫描的前提下,到底select * from table where id in (1,2,3.....x)这个x能到多少。

问题

首先建一张测试表,来一步复现一下,走与不走索引的情况。

mysql

版本:5.7.19

引擎:innodb

创建一个测试表

CREATE TABLE `t_person` (
`id` int(11) NOT NULL,
`name` varchar(10) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

使用SQL

EXPLAIN SELECT id, NAME FROM t_person WHERE id IN (1)

查看执行计划

此时表里无数据,显示的是no matching row in const table.

少量数据

插入一条数据insert t_person (id,name) values(1,'张三')

使用SQL

EXPLAIN SELECT id, NAME FROM t_person WHERE id IN (1)

查看执行计划

使用了索引,还是效率最高的const(system生产环境不可能的吧),此时id in(1)相当于 id = 1。

在in里增加点条件。

sql变成EXPLAIN SELECT id, NAME FROM t_person WHERE id IN (1, 2)

查看执行计划

使用了索引,但级别下降到了range,即范围索引。

继续在in里增加条件。

sql变成EXPLAIN SELECT id, NAME FROM t_person WHERE id IN (1, 2,3)

查看执行计划

索引级别变成了ALL,即全表扫描,其实是索引失效了。

再往表里插入两条数据。此时总共3条数据。

insert t_person (id,name) values(2,'李四')
insert t_person (id,name) values(3,'王五')

再使用sqlEXPLAIN SELECT id, NAME FROM t_person WHERE id IN (1, 2,3)

查看执行计划

可以看到,随时表数据的增加,同样的sql执行计划从ALL变回了range,索引又生效了。

同样地,再增加一个in条件,EXPLAIN SELECT id, NAME FROM t_person WHERE id IN (1,2,3,4)的执行计划又变回了ALL,这里就不放图了。

多点数据

以上只是小打小闹撒撒水啦,总共几条数据,in的条件都快超过表数据了,执行计算都不用预估就知道全表扫描还好一点啦。

我再往表里插入100万条数据。

我先按照阿里的开发规范推荐的1000这个值作为临界值,先使用900个条件

再使用1100个条件

上图表明,这两种情况都使用到了range范围索引呢。

再加大剂量,直接上10万。

步子迈大了,咔,这下终于全表扫描了。

但是还是没找到临界值。

官网上寻找答案

https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html

我在这里寻找到了一个参数,描述的倒像是相似的问题。

这个方法说的是当使用in或or查询时,比如where in(1,2,3),执行引擎会先预估表中的数量,表中的数量将决定使用的查询方式,比如,如果表中只有3条数据,那么很明显,这时候直接全表扫描。

而这个预估的方法有2种,一是dive到index中即利用索引完成元组数的估算,简称index dive; 二是使用索引的统计数值,进行估算.

相比这2种方式,在效果上:

  • index dive: 速度慢,但能得到精确的值(MySQL的实现是数索引对应的索引项个数,所以精确)

  • index statistics: 速度快,但得到的值未必精确.

但eq_range_index_dive_limit这个参数确实跟今天的主题相关系数不大。很明显,这个值在mysql 5.7是200, 一开始的in后面的条件个数就是900,依然是走了range索引的。

stackoverflow

于是我找到了stackoverflow,在上面把msyql in count 这些关键词搜了一下,没有找到相关的问题。

然后我把问题详细描述了一下,提了一个新的问题,没想到啊,半个小时不到,人家就直接给我点踩,并给出了相似的已解答问题。

尴尬了。

我超喜欢stackoverflow,这里的人个个都是人才。

相似的问题在这里。

https://stackoverflow.com/questions/72361880/mysql-in-operator-on-large-number-of-values

这位仁兄也在in的使用中也有很多问号,in的条件卡在14000左右,超过就失去了range索引。

下面高赞答案提到了一个参数,range_optimizer_max_mem_size ,一看就很有搞头啊。

转到mysql官网,凭我的渣渣英语也能看明白,我知道,大概我找到答案了。

https://dev.mysql.com/doc/refman/5.7/en/range-optimization.html#equality-range-optimization

要控制范围优化器可用的内存,使用range_optimizer_max_mem_size系统变量:

  • 值为0表示“没有限制”。

  • 当值大于0时,优化器将跟踪在考虑范围访问方法时所消耗的内存。如果即将超过指定的限制,则放弃范围访问方法,转而考虑其他方法,包括全表扫描。这可能不太理想。如果发生这种情况,会出现以下警告(其中N是当前的range_optimizer_max_mem_size值)。

现在事情就很简单了。

range_optimizer_max_mem_size默认是8M,使用同样的SQL,in后面同样的条件为固定的19900个,

在range_optimizer_max_mem_size=8M,range_optimizer_max_mem_size=8情况下分别执行一下看效果。

range_optimizer_max_mem_size=8M时,走range索引。

range_optimizer_max_mem_size=8时,走ALL全表扫描。

破案了!

明明官网上就有答案,我却三过家门而不入。

结论

in两种情况会走全表扫描。

  • in后面条件导致sql大小超过range_optimizer_max_mem_size。
  • in后面条件个数接近或者等于表数量,执行引擎认为此时全表扫描更加合适。

推而广之,or也是一样的道理。

归根结底都是范围查询。

当然,总体来说,in后面条件越少越好,假设一张表有1000万条数据,in后面的条件有10000个,这时候就算走了range索引,估计效率也好不到哪里。

完

in用不用索引,啥时候能用啥时候不能用,一文说清的更多相关文章

  1. 利用索引与不用索引区别(profiles)

    1.定义 对数据库表的一列或多列的值进行排序的一种结构(Btree方式)=(相当于二分查找法) 2.优点 加快数据检索速度 3.缺点 1.占用物理存储空间 2.当对表中数据更新时,索引需要动态维护,降 ...

  2. SQLSERVER聚集索引与非聚集索引的再次研究(下)

    SQLSERVER聚集索引与非聚集索引的再次研究(下) 上篇主要说了聚集索引和简单介绍了一下非聚集索引,相信大家一定对聚集索引和非聚集索引开始有一点了解了. 这篇文章只是作为参考,里面的观点不一定正确 ...

  3. MySQL引擎、索引和优化(li)

    一.存储引擎 存储引擎,MySQL中的数据用各种不同的技术存储在文件(或者内存)中.这些技术中的每一种技术都使用不同的存储机制.索引技巧.锁定水平并且最终提供广泛的不同的功能和能力.通过选择不同的技术 ...

  4. sqlite索引的原理

    引言 这篇文章,里面讲到对于一个41G大小.包含百万条记录的数据库进行查询操作,如果利用了索引,可以把操作耗时从37s降到0.2s. 那么什么是索引呢?利用索引可以加快数据库查询操作的原理是什么呢? ...

  5. Oracle索引梳理系列(四)- Oracle索引种类之位图索引

    版权声明:本文发布于http://www.cnblogs.com/yumiko/,版权由Yumiko_sunny所有,欢迎转载.转载时,请在文章明显位置注明原文链接.若在未经作者同意的情况下,将本文内 ...

  6. 如何正确建立MYSQL数据库索引

    索引是快速搜索的关键.MySQL索引的建立对于MySQL的高效运行是很重要的.下面介绍几种常见的MySQL索引类型. 在数据库表中,对字段建立索引可以大大提高查询速度.假如我们创建了一个 mytabl ...

  7. MySQL数据库学习笔记(六)----MySQL多表查询之外键、表连接、子查询、索引

    本章主要内容: 一.外键 二.表连接 三.子查询 四.索引 一.外键: 1.什么是外键 2.外键语法 3.外键的条件 4.添加外键 5.删除外键 1.什么是外键: 主键:是唯一标识一条记录,不能有重复 ...

  8. 转载: SQL Server中的索引

    http://www.blogjava.net/wangdetian168/archive/2011/03/07/347192.html 1 SQL Server中的索引 索引是与表或视图关联的磁盘上 ...

  9. postgreSQL数据库(索引、视图)

    索引的含义与特点 索引是一个单独的.存储在磁盘上的数据库结构,它们包含对数据所有记录的引用指针,postgresql列类型都可以被索引,对相关列索引是提高查询操作效率的最佳途径.例如,查询select ...

  10. MySQL索引的查看创建和删除

    1.索引作用 在索引列上,除了上面提到的有序查找之外,数据库利用各种各样的快速定位技术,能够大大提高查询效率.特别是当数据量非常大,查询涉及多个表时,使用索引往往能使查询速度加快成千上万倍. 例如,有 ...

随机推荐

  1. Mac基本命令操作

    Mac使用常见命令 删除空目录:rmdir 目录 删除文件夹:rm -rf 文件夹 创建一个文件夹:mkdir 文件名 创建一个文件:touch 文件 修改一个文件:vi 文件名 重命名文件 mv 原 ...

  2. MySQL 8.0:无锁可扩展的 WAL 设计

    这篇文章整理自MySQL官方文档,介绍了8.0在预写式日志上实现上的修改,观点总结如下: 在8.0以前,为了保证flush list的顺序,redo log buffer写入过程需要加锁,无法实现并行 ...

  3. [SpringBoot]Spring Boot Framework @ Environment / ApplicationContext & SpringApplication

    [#]: 表示较为重要 1 Spring Boot Overview SpringBoot是一个快速开发框架,快速的将一些常用的第三方依赖整合(原理:通过Maven子父工程的方式),简化XML配置,全 ...

  4. 好奇心驱使下试验了 chatGPT 的 js 代码的能力

    手边的项目中有个函数,主要实现图片分片裁剪功能.可以优化一下. 也想看看 chatGPT 的代码理解能力,优化能力,实现能力,用例能力. 于是有了这篇文章. 实验结果总结: chatGPT 确实强大, ...

  5. 回车,换行,转义字符“\r”,“\n”是什么关系?

    1."回车"这个名词的来历. 关于"回车键"的来历,还得从机械英文打字机说起.在机械英文打字机上,有一个部件叫"字车",每打一个字符(原为单 ...

  6. Qt+MySql开发笔记:Qt5.9.3的msvc2017x64版本编译MySql8.0.16版本驱动并Demo连接数据库测试

    前言   mysql驱动版本msvc2015x32版本调好, mysql的mingw32版本的驱动上一个版本编译并测试好,有些三方库最低支持vs2017,所以只能使用msvc2017x64,基于Qt5 ...

  7. 如何在Godot中使用ParallaxBackground实现稳定的2d游戏背景[一问随笔]

    问题: 我尝试给2d游戏添加静态的背景,当角色运动速度很快时相机的渲染就跟不上角色了,背景会发生这样巨大的位移. 我将Camera2d节点和背景节点绑在一起,但根本无法解决这个问题. 我还尝试制作天空 ...

  8. 使用ChatGPT4协助完成读取文件中不同字的数量

    使用ChatGPT4识别:用java读取文件中不同字的个数. 解析:该程序将读取名为"file.txt"的文件,并计算文件中每个不同字的出现次数.它使用一些字符串操作来清理单词,并 ...

  9. MySQL-分组函数ROLLUP的基本用法

    一.ROLLUP简介 ROLLUP是GROUP BY子句的扩展. ROLLUP选项允许包含表示小计的额外行,通常称为超级聚合行,以及总计行. 通过使用ROLLUP选项,可以使用单个查询生成多个分组集. ...

  10. #Powerbi 季度时间进度计算

    上一篇我们学习了月度时间进度的计算方法,今天我们学习季度时间进度的测算. 思路:找出目前共计消耗了多少天(季度),目前日期所在的季度共有多少天,两者相除即是季度的时间进度 首先列出DAX函数:   本 ...