SQL优化教程之in与range查询

前言

《高性能MySQL》里面提及用in这种方式可以有效的替代一定的range查询,提升查询效率, 因为在一条索引里面,range字段后面的部分是不生效的(ps.需要考虑 ICP) 。MySQL优化器将in这种方式转化成  n*m 种组合进行查询,最终将返回值合并,有点类似union但是更高效。

MySQL在 IN() 组合条件过多的时候会发生很多问题。查询优化可能需要花很多时间,并消耗大量内存。新版本MySQL在组合数超过一定的数量就不进行计划评估了,这可能导致MySQL不能很好的利用索引。

这里的 一定数 在MySQL5.6.5以及以后的版本中是由eq_range_index_dive_limit这个参数控制 。默认设置是10,一直到5.7以后的版本默认修改为200,当然可以手动设置的。5.6手册说明如下:

The eq_range_index_dive_limit system variable enables you to configure the number of values at which the optimizer switches from one row estimation strategy to the other. To disable use of statistics and always use index dives, set eq_range_index_dive_limit to 0. To permit use of index dives for comparisons of up to N equality ranges, set eq_range_index_dive_limit to N + 1. eq_range_index_dive_limit is available as of MySQL 5.6.5. Before 5.6.5, the optimizer uses index dives, which is equivalent to eq_range_index_dive_limit=0.

换言之,

eq_range_index_dive_limit = 0 只能使用index dive

0 < eq_range_index_dive_limit <= N 使用index statistics

eq_range_index_dive_limit > N 只能使用index dive

在MySQL5.7版本中将默认值从10修改成200目的是为了尽可能的保证范围等值运算(IN())执行计划尽量精准,因为IN()list的数量很多时候都是超过10的。

在MySQL的官方手册上有这么一句话:

the optimizer can estimate the row count for each range using dives into the index or index statistics.

大意:

优化器预估每个范围段--如"a IN (10, 20, 30)" 视为等值比较, 括3个范围段实则简化为3个单值,分别是10,20,30--中包括的元组数,用范围段来表示是因为 MySQL 的"range"扫描方式多数做的是范围扫描,此处单值可视为范围段的特例;

估计方法有2种:

  1. dive到index中即利用索引完成元组数的估算,简称index dive;
  2. index statistics:使用索引的统计数值,进行估算;

对比这两种方式

  1. index dive: 速度慢,但能得到精确的值(MySQL的实现是数索引对应的索引项个数,所以精确)
  2. index statistics: 速度快,但得到的值未必精确

简单说,**选项 eq_range_index_dive_limit 的值设定了 IN列表中的条件个数上线,超过设定值时,会将执行计划从 index dive 变成 index statistics **。

为什么要区分这2种方式呢?

  1. 查询优化器会使用代价估算模型计算每个计划的代价,选择其中代价最小的
  2. 单表扫描时,需要计算代价;所以单表的索引扫描也需要计算代价
  3. 单表的计算公式通常是:  代价 = 元组数 * IO平均值
  4. 所以不管是哪种扫描方式,都需要计算元组数
  5. 当遇到“a IN (10, 20, 30)”这样的表达式的时候,发现a列存在索引,则需要看这个索引可以扫描到的元组数由多少而计算其索引扫描代价,所以就用到了本文提到的“index dive”、“index statistics”这2种方式。

讨论主题

  1. range查询与索引使用
  2. eq_range_index_dive_limit的说明

range查询与索引使用

SQL如下:

SELECT * FROM pre_forum_post WHERE tid=7932552 AND invisible IN('0','-2') ORDER BY dateline DESC LIMIT 10;

索引如下:

PRIMARY(tid,position),
pid(pid),
fid(tid),
displayorder(tid,invisible,dateline)
first(tid,first)
new_auth(authorid,invisible,tid)
idx_dt(dateline)
mul_test(tid,invisible,dateline,pid)

看下执行计划:

root@localhost 16:08:27 [ultrax]> explain SELECT * FROM pre_forum_post WHERE tid=7932552 AND `invisible` IN('0','-2')
 -> ORDER BY dateline DESC LIMIT 10;
+----+-------------+----------------+-------+-------------------------------------------+--------------+---------+------+------+---------------------------------------+
| id | select_type | table | type | possible_keys  | key | key_len | ref | rows | Extra   |
+----+-------------+----------------+-------+-------------------------------------------+--------------+---------+------+------+---------------------------------------+
| 1 | SIMPLE | pre_forum_post | range | PRIMARY,displayorder,first,mul_test,idx_1 | displayorder | 4 | NULL | 54 | Using index condition; Using filesort |
+----+-------------+----------------+-------+-------------------------------------------+--------------+---------+------+------+---------------------------------------+
1 row in set (0.00 sec)

MySQL优化器认为这是一个range查询,那么(tid,invisible,dateline)这条索引中,dateline字段肯定用不上了,也就是说这个SQL最后的排序肯定会生成一个临时结果集,然后再结果集里面完成排序,而不是直接在索引中直接完成排序动作,于是我们尝试增加了一条索引。

root@localhost 16:09:06 [ultrax]> alter table pre_forum_post add index idx_1 (tid,dateline);
Query OK, 20374596 rows affected, 0 warning (600.23 sec)
Records: 0 Duplicates: 0 Warnings: 0
root@localhost 16:20:22 [ultrax]> explain SELECT * FROM pre_forum_post force index (idx_1) WHERE tid=7932552 AND `invisible` IN('0','-2') ORDER BY dateline DESC LIMIT 10;
+----+-------------+----------------+------+---------------+-------+---------+-------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------------+------+---------------+-------+---------+-------+--------+-------------+
| 1 | SIMPLE | pre_forum_post | ref | idx_1 | idx_1 | 3 | const | 120646 | Using where |
+----+-------------+----------------+------+---------------+-------+---------+-------+--------+-------------+
1 row in set (0.00 sec)
root@localhost 16:22:06 [ultrax]> SELECT sql_no_cache * FROM pre_forum_post WHERE tid=7932552 AND `invisible` IN('0','-2') ORDER BY dateline DESC LIMIT 10;
...
10 rows in set (0.40 sec)
root@localhost 16:23:55 [ultrax]> SELECT sql_no_cache * FROM pre_forum_post force index (idx_1) WHERE tid=7932552 AND `invisible` IN('0','-2') ORDER BY dateline DESC LIMIT 10;
...
10 rows in set (0.00 sec)

实验证明效果是极好的,其实不难理解,上面我们就说了in()在MySQL优化器里面是以多种组合方式来检索数据的,如果加了一个排序或者分组那势必只能在临时结果集上操作,也就是说索引里面即使包含了排序或者分组的字段依然是没用的。唯一不满的是MySQL优化器的选择依然不够靠谱。

总结下:在MySQL查询里面使用in(),除了要注意in()list的数量以及eq_range_index_dive_limit的值以外(具体见下),还要注意如果SQL包含排序/分组/去重等等就需要注意索引的使用。

eq_range_index_dive_limit的说明

还是上面的案例,为什么idx_1无法直接使用?需要使用hint强制只用这个索引呢?这里我们首先看下eq_range_index_dive_limit的值。

root@localhost 22:38:05 [ultrax]> show variables like 'eq_range_index_dive_limit';
+---------------------------+-------+
| Variable_name | Value |
+---------------------------+-------+
| eq_range_index_dive_limit | 2 |
+---------------------------+-------+
1 row in set (0.00 sec)

根据我们上面说的这种情况0 < eq_range_index_dive_limit <= N使用index statistics,那么接下来我们用OPTIMIZER_TRACE来一看究竟。

{
 "index": "displayorder",
 "ranges": [
 "7932552 <= tid <= 7932552 AND -2 <= invisible <= -2",
 "7932552 <= tid <= 7932552 AND 0 <= invisible <= 0"
 ],
 "index_dives_for_eq_ranges": false,
 "rowid_ordered": false,
 "using_mrr": false,
 "index_only": false,
 "rows": 54,
 "cost": 66.81,
 "chosen": true
}
// index dive为false,最终chosen是true
...
{
 "index": "idx_1",
 "ranges": [
 "7932552 <= tid <= 7932552"
 ],
 "index_dives_for_eq_ranges": true,
 "rowid_ordered": false,
 "using_mrr": false,
 "index_only": false,
 "rows": 120646,
 "cost": 144776,
 "chosen": false,
 "cause": "cost"
}

我们可以看到displayorder索引的cost是66.81,而idx_1的cost是120646,而最终MySQL优化器选择了displayorder这条索引。那么如果我们把eq_range_index_dive_limit设置>N是不是应该就会使用index dive计算方式,得到更准确的执行计划呢?

root@localhost 22:52:52 [ultrax]> set eq_range_index_dive_limit = 3;
Query OK, 0 rows affected (0.00 sec)
root@localhost 22:55:38 [ultrax]> explain SELECT * FROM pre_forum_post WHERE tid=7932552 AND `invisible` IN('0','-2') ORDER BY dateline DESC LIMIT 10;
+----+-------------+----------------+------+-------------------------------------------+-------+---------+-------+--------+-------------+
| id | select_type | table | type | possible_keys  | key | key_len | ref | rows | Extra |
+----+-------------+----------------+------+-------------------------------------------+-------+---------+-------+--------+-------------+
| 1 | SIMPLE | pre_forum_post | ref | PRIMARY,displayorder,first,mul_test,idx_1 | idx_1 | 3 | const | 120646 | Using where |
+----+-------------+----------------+------+-------------------------------------------+-------+---------+-------+--------+-------------+
1 row in set (0.00 sec)

optimize_trace结果如下

{
 "index": "displayorder",
 "ranges": [
 "7932552 <= tid <= 7932552 AND -2 <= invisible <= -2",
 "7932552 <= tid <= 7932552 AND 0 <= invisible <= 0"
 ],
 "index_dives_for_eq_ranges": true,
 "rowid_ordered": false,
 "using_mrr": false,
 "index_only": false,
 "rows": 188193,
 "cost": 225834,
 "chosen": true
}
...
{
 "index": "idx_1",
 "ranges": [
 "7932552 <= tid <= 7932552"
 ],
 "index_dives_for_eq_ranges": true,
 "rowid_ordered": false,
 "using_mrr": false,
 "index_only": false,
 "rows": 120646,
 "cost": 144776,
 "chosen": true
}
...
 "cost_for_plan": 144775,
 "rows_for_plan": 120646,
 "chosen": true

在备选索引选择中两条索引都被选择,在最后的逻辑优化中选在了代价最小的索引也就是idx_1 以上就是在等值范围查询中eq_range_index_dive_limit的值怎么影响MySQL优化器计算开销,从而影响索引的选择。另外我们可以通过profiling来看看优化器的统计耗时:

index dive

+----------------------+----------+
| Status | Duration |
+----------------------+----------+
| starting | 0.000048 |
| checking permissions | 0.000004 |
| Opening tables | 0.000015 |
| init  | 0.000044 |
| System lock | 0.000009 |
| optimizing | 0.000014 |
| statistics | 0.032089 |
| preparing | 0.000022 |
| Sorting result | 0.000003 |
| executing | 0.000003 |
| Sending data | 0.000101 |
| end  | 0.000004 |
| query end | 0.000002 |
| closing tables | 0.000009 |
| freeing items | 0.000013 |
| cleaning up | 0.000012 |
+----------------------+----------+

index statistics

+----------------------+----------+
| Status | Duration |
+----------------------+----------+
| starting | 0.000045 |
| checking permissions | 0.000003 |
| Opening tables | 0.000014 |
| init  | 0.000040 |
| System lock | 0.000008 |
| optimizing | 0.000014 |
| statistics | 0.000086 |
| preparing | 0.000016 |
| Sorting result | 0.000002 |
| executing | 0.000002 |
| Sending data | 0.000016 |
| Creating sort index | 0.412123 |
| end  | 0.000012 |
| query end | 0.000004 |
| closing tables | 0.000013 |
| freeing items | 0.000023 |
| cleaning up | 0.000015 |
+----------------------+----------+

可以看到当eq_range_index_dive_limit加大使用index dive时,优化器统计耗时明显比ndex statistics方式来的长,但最终它使用了作出了更合理的执行计划。统计耗时0.032089s vs .000086s,但是SQL执行耗时却是约0.03s vs 0.41s。

附:

如何使用optimize_trace

set optimizer_trace='enabled=on';

select * from information_schema.optimizer_trace\G

注:optimizer_trace建议只在session模式下开启调试即可

如何使用profile

set profiling=ON;
执行sql;
show profiles;
show profile for query 2;
show profile block io,cpu for query 2;

另外还可以看到memory,swaps,context switches,source 等信息

参考资料

[1]MySQL SQL优化系列之 in与range 查询

https://www.jb51.net/article/201251.htm

[2]MySQL物理查询优化技术---index dive辨析

http://blog.163.com/li_hx/blog/static/18399141320147521735442/

到此这篇关于SQL优化教程之in与range查询的文章就介绍到这了,更多相关SQL优化之in与range查询内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • MySQL中insert语句的使用与优化教程

    MySQL 表中使用 INSERT INTO SQL语句来插入数据. 你可以通过 mysql> 命令提示窗口中向数据表中插入数据,或者通过PHP脚本来插入数据. 语法 以下为向MySQL数据表插入数据通用的 INSERT INTO SQL语法: INSERT INTO table_name ( field1, field2,...fieldN ) VALUES ( value1, value2,...valueN ); 如果数据是字符型,必须使用单引号或者双引号,如:"value"

  • SQL语句优化之JOIN和LEFT JOIN 和 RIGHT JOIN语句的优化

    在数据库的应用中,我们经常需要对数据库进行多表查询,然而当数据量非常大时多表查询会对执行效率产生非常大的影响,因此我们在使用JOIN和LEFT JOIN 和 RIGHT JOIN语句时要特别注意: SQL语句的join原理: 数据库中的join操作,实际上是对一个表和另一个表的关联,而很多错误理解为,先把这两个表来一个迪卡尔积,然后扔到内存,用where和having条件来慢慢筛选,其实数据库没那么笨的,那样会占用大量的内存,而且效率不高,比如,我们只需要的一个表的一些行和另一个表的一些行,如果

  • MySQL优化之使用连接(join)代替子查询

    使用连接(JOIN)来代替子查询(Sub-Queries) MySQL从4.1开始支持SQL的子查询.这个技术可以使用SELECT语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中.例如,我们要将客户基本信息表中没有任何订单的客户删除掉,就可以利用子查询先从销售信息表中将所有发出订单的客户ID取出来,然后将结果传递给主查询,如下所示: DELETE FROM customerinfo WHERE CustomerID NOT in (SELECT CustomerID FR

  • 关于mysql中innodb的count优化问题分享

    一般采用二级索引去count:比如:id 是pk aid是secondary index 采用 复制代码 代码如下: select count(*) from table where id >=0;或select count(*) from table; 效果是一样的,都是默认使用pk索引,且都要全表扫描,虽然第一种性能可能高一些,但是没有明显区别. 但是如果用secondary index 复制代码 代码如下: select count(*) from table where aid>=0;

  • 分析MySQL中优化distinct的技巧

    有这样的一个需求:select count(distinct nick) from user_access_xx_xx; 这条sql用于统计用户访问的uv,由于单表的数据量在10G以上,即使在user_access_xx_xx上加上nick的索引, 通过查看执行计划,也为全索引扫描,sql在执行的时候,会对整个服务器带来抖动: root@db 09:00:12>select count(distinct nick) from user_access; +--------+ | count(dis

  • mysql in语句子查询效率慢的优化技巧示例

    表结构如下,文章只有690篇. 文章表article(id,title,content) 标签表tag(tid,tag_name) 标签文章中间表article_tag(id,tag_id,article_id) 其中有个标签的tid是135,查询标签tid是135的文章列表. 690篇文章,用以下的语句查询,奇慢: select id,title from article where id in( select article_id from article_tag where tag_id=

  • MySQL中对于not in和minus使用的优化

    优化前: select count(t.id) from test t where t.status = 1 and t.id not in (select distinct a.app_id from test2 a where a.type = 1 and a.rule_id in (152, 153, 154)) 17:20:57 laojiu>@plan PLAN_TABLE_OUTPUT ----------------------------------------- Plan ha

  • SQL优化之针对count、表的连接顺序、条件顺序、in及exist的优化

    本文详述了SQL优化中针对count.表的连接顺序.条件顺序.in及exist的优化,非常具有实用价值!详述如下: 一.关于count 看过一些网上关于count(*)和count(列)的文章,count(列)的效率一定比count(*)高吗? 其实个人觉得count(*)和count(列)根本就没有可比性,count(*)统计的是表里面的总条数,而count(列)统计的是当列的非空记录条数. 不过我们可以通过实验来比较一下: 首先创建测试表: drop table test purge; cr

  • MySQL查询优化:连接查询排序limit(join、order by、limit语句)介绍

    不知道有没有人碰到过这样恶心的问题:两张表连接查询并limit,SQL效率很高,但是加上order by以后,语句的执行时间变的巨长,效率巨低. 情况是这么一个情况:现在有两张表,team表和people表,每个people属于一个team,people中有个字段team_id. 下面给出建表语句: 复制代码 代码如下: create table t_team ( id int primary key, tname varchar(100) ); create table t_people (

  • MySQL SQL优化教程之in和range查询

    首先我们来说下in()这种方式的查询.在<高性能MySQL>里面提及用in这种方式可以有效的替代一定的range查询,提升查询效率,因为在一条索引里面,range字段后面的部分是不生效的.使用in这种方式其实MySQL优化器是转化成了n*m种组合方式来进行查询,最终将返回值合并,有点类似union但是更高效.同时它存在这一些问题: 老版本的MySQL在IN()组合条件过多的时候会发生很多问题.查询优化可能需要花很多时间,并消耗大量内存.新版本MySQL在组合数超过一定的数量就不进行计划评估了,

  • SQL优化教程之in与range查询

    前言 <高性能MySQL>里面提及用in这种方式可以有效的替代一定的range查询,提升查询效率, 因为在一条索引里面,range字段后面的部分是不生效的(ps.需要考虑 ICP) .MySQL优化器将in这种方式转化成  n*m 种组合进行查询,最终将返回值合并,有点类似union但是更高效. MySQL在 IN() 组合条件过多的时候会发生很多问题.查询优化可能需要花很多时间,并消耗大量内存.新版本MySQL在组合数超过一定的数量就不进行计划评估了,这可能导致MySQL不能很好的利用索引.

  • 52条SQL语句教你性能优化

    1, 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2,应尽量避免在 where 子句中对字段进行 null 值判断,创建表时NULL是默认值,但大多数时候应该使用NOT NULL,或者使用一个特殊的值,如0,-1作为默 认值. 3,应尽量避免在 where 子句中使用!=或<>操作符, MySQL只有对以下操作符才使用索引:<,<=,=,>,>=,BETWEEN,IN,以及某些时候的LIKE. 4,应尽量避免

  • mysql 查询重复的数据的SQL优化方案

    在mysql中查询不区分大小写重复的数据,往往会用到子查询,并在子查询中使用upper函数来将条件转化为大写.如: 复制代码 代码如下: select * from staticcatalogue WHERE UPPER(Source) IN (SELECT UPPER(Source) FROM staticcatalogue GROUP BY UPPER(Source) having count(UPPER(Source))>1) ORDER BY upper(Source) DESC; 这条

  • 掌握SQL Server实战教程之SQL Server的安装指南

    目录 前言 一. 数据库的介绍 1.1 数据库的分类 1.2 MS SQL介绍 二. MS SQL的安装 2.1 从网站下载安装包 2.2 开始安装 选择基本版本 2.3 安装SSMS 三. 连接数据库 3.1 数据库的连接 3.2 创建数据库 总结 前言 本文沐风晓月带你来了解一下sqlserver数据库的安装及简单使用,本文的主要任务: 安装SQL Server并能够成功的远程链接,然后执行几条简单的SQL语句进行测试即可 一. 数据库的介绍 1.1 数据库的分类 数据库的种类有很多,根据存

  • SQL优化经验总结

    一. 优化SQL步骤 1. 通过 show status和应用特点了解各种 SQL的执行频率     通过 SHOW STATUS 可以提供服务器状态信息,也可以使用 mysqladmin extende d-status 命令获得. SHOW STATUS 可以根据需要显示 session 级别的统计结果和 global级别的统计结果. 如显示当前session: SHOW STATUS like "Com_%"; 全局级别:show global status;    以下几个参数

  • iOS汇编入门教程之ARM64汇编基础教程

    前言 对于应用层开发人员而言,仅仅掌握Objective-C和系统框架即可较好的完成开发,但在涉及到应用加固.逆向分析等内容时仅有应用层开发技能就会显得非常的无力,因此掌握汇编对于突破iOS开发水平的瓶颈十分有效. 一个例子 以反调试为例,我们知道,通过调用ptrace函数可以阻止调试器依附. ptrace(31, 0, 0, 0) 这种方式能够被函数hook轻易破解,例如使用facebook的fishhook.为了防止函数被hook,我们可以将函数调用转为通过汇编发起系统调用,即使用下面的代码

  • java JDBC系列教程之JDBC类的简析与JDBC的基础操作

    什么是JDBC? 概念:JAVA Database Connectivity Javas数据库连接,Java语言操作数据库接口,然后由各个数据库厂商去实现这个接口,提供数据库驱动java包,我们可以使用这套接口,真正执行的是jar驱动包中的实习类 使用一张图让大家更为直观的理解: coder就是写这套接口的程序员 JDBC的使用步骤 1.导入驱动jar包 2.注册驱动 3.获取数据库连接对象 4.定义sql执行语句 5.获取sql语句执行对象 6.执行sql语句返回结果 7.处理结果 8.释放结

  • 分区表场景下的 SQL 优化

    导读 有个表做了分区,每天一个分区. 该表上有个查询,经常只查询表中某一天数据,但每次都几乎要扫描整个分区的所有数据,有什么办法进行优化吗? 待优化场景 有一个大表,每天产生的数据量约100万,所以就采用表分区方案,每天一个分区. 下面是该表的DDL: CREATE TABLE `t1` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `date` date NOT NULL, `kid` int(11) DEFAULT '0', `uid` int(11)

  • Python GUI教程之在PyQt5中使用数据库的方法

    目录 PyQt5的SQL数据库支持 在PyQt5中简单使用数据库 创建一个UI界面 连接一个数据库 在UI界面查看和修改数据 添加和删除数据 在桌面图像化界面编程中,我们通常需要将一些数据或配置信息存储在本地.在本地进行数据的存储,我们可以直接使用文本文件,比如ini文件.csv文件.json文件等,或者是使用文件型的数据库(比如sqlit3)进行存储. PyQt5的SQL数据库支持 Qt平台对SQL编程有着良好的支持,PyQt5也一并继承了过来.在PyQt5中,QtSql子模块提供对SQL数据

随机推荐