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. VideoPipe可视化视频结构化框架更新总结(2023-3-30)

    项目地址:https://github.com/sherlockchou86/video_pipe_c 往期文章:https://www.cnblogs.com/xiaozhi_5638/p/1696 ...

  2. 基于docker和cri-dockerd部署k8sv1.26.3

    cri-dockerd是什么? 在 Kubernetes v1.24 及更早版本中,我们使用docker作为容器引擎在k8s上使用时,依赖一个dockershim的内置k8s组件:k8s v1.24发 ...

  3. 如何做到API文档规范化

    定义一个好的 API 文档是优秀研发人员的标准配置,在执行接口测试之前,测试人员一定会先拿到开发给予的接口文档. 测试人员可以根据这个文档编写接口测试用例,优秀的文档可以区分好的用户体验和坏的用户体验 ...

  4. IBM Cloud Computing Practitioners 2019 (IBM云计算从业者2019)Exam答案

    Cloud Computing Practitioners 2019 IBM Cloud Computing Practitioners 2019 (IBM云计算从业者2019)Exam答案,加粗的为 ...

  5. 四月十七日Java基础知识点

    1.默认构造方法:如果class前面有public修饰符,则默认的构造方法也会是public的.由于系统提供的默认构造方法往往不能满足需求,所以用户可以自己定义类的构造方法来满足需要,一旦用户为该类定 ...

  6. php对接snmp设备详细讲解

    1.Php安装snmp扩展 1.基础环境准备 Php7.2版本 yum -y install php72w-snmp Php7.4版本 yum install net-snmp php-snmp ne ...

  7. 【深度学习】【图像分类网络】(一)残差神经网络ResNet以及组卷积ResNeXt

    ResNet网络 论文:Deep Residual Learning for Image Recognition 网络中的亮点: 1 超深的网络结构(突破了1000层) 上图为简单堆叠卷积层和池化层的 ...

  8. django渲染模版时比实际少了8小时?

    这是因为django的时间是UTC时间. 我们通过改配置文件将其改成本地时间 修改配置文件 # 将时间从UTC转化成当前时间 TIME_ZONE = 'Asia/Shanghai' # USE_TZ ...

  9. Django笔记二十九之中间件介绍

    本文首发于公众号:Hunter后端 原文链接:Django笔记二十九之中间件介绍 这一节介绍一下 Django 的中间件. 关于中间件,官方文档的解释为:中间件是一个嵌入 Django 系统的 req ...

  10. 浏览器发送POST请求、DELETE请求

    1.浏览器发送POST请求 方法一: var xml = new XMLHttpRequest(); var url = "http://127.0.0.1:8800/admin/user& ...