聊聊order by rand()
总结写在前面:
1. 不建议直接使用order by rand(),原因是执行代价比较大
2. 介绍了内存临时表,对于内存临时表,由于回表不需要访问磁盘,所以往往是用rowid排序,可以减少参与排序字段
3. 介绍了磁盘临时表,当临时表大小超过了 tmp_table_size的时候,就会使用磁盘存储。
4. 介绍了优先队列排序算法,该算法内使用了最大堆的思想,当排序时需要维护的堆的大小比sort_buffer_size小的时候(维护的堆大小往往跟需要取出的行数和排序字段相关),会使用该算法,否则会使用归并排序算法,借助临时文件排序。
现有一个需求:从一个单词表中随机选出三个单词。
先创建一张单词表,并且插入10000条数据
mysql> CREATE TABLE `words` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`word` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB; delimiter ;;
create procedure idata()
begin
declare i int;
set i=0;
while i<10000 do
insert into words(word) values(concat(char(97+(i div 1000)), char(97+(i % 1000 div 100)), char(97+(i % 100 div 10)), char(97+(i % 10))));
set i=i+1;
end while;
end;;
delimiter ; call idata();
接下来,我们一起探讨下要随机选择 3 个单词,有什么方法实现,存在什么问题以及如何改进。
内存临时表
首先,我们用 order by rand() 来实现这个逻辑。
mysql> select word from words order by rand() limit 3;
意思很直白,随机排序取前 3 个。这个 SQL 语句写法很简单,但执行流程却有点复杂的。
用 explain 命令来看看这个语句的执行情况。
Extra 字段显示 Using temporary,表示的是需要使用临时表;Using filesort,表示的是需要执行排序操作。
意思就是,需要临时表,并且需要在临时表上排序。
那么对于临时内存表的排序来说,它会选择哪一种算法呢?
在 orderby工作机制 里说过对于 InnoDB 表来说,执行全字段排序会减少磁盘访问,因此会被优先选择。
这里强调“InnoDB 表”,但对于内存表,回表过程只是简单地根据数据行的位置,直接访问内存得到数据,根本不会导致多访问磁盘。
优化器没有了这一层顾虑,那么它会优先考虑的,就是用于排序的行越小越好了,所以,MySQL 这时就会选择 rowid 排序。
接着看看语句的执行流程,尝试分析一下语句的扫描行数:
1. 创建一个临时表。这个临时表使用的是 memory 引擎,表里有两个字段,第一个字段是 double 类型,为了后面描述方便,记为字段 R,第二个字段是 varchar(64) 类型,记为字段 W。并且,这个表没有建索引。
2. 从 words 表中,按主键顺序取出所有的 word 值。对于每一个 word 值,调用 rand() 函数生成一个大于 0 小于 1 的随机小数,并把这个随机小数和 word 分别存入临时表的 R 和 W 字段中,到此,扫描行数是 10000。
3. 现在临时表有 10000 行数据了,接下来要在这个没有索引的内存临时表上,按照字段 R 排序。
4. 初始化 sort_buffer。sort_buffer 中有两个字段,一个是 double 类型,另一个是整型。
5. 从内存临时表中一行一行地取出 R 值和位置信息(后面会解释这里为什么是“位置信息”),分别存入 sort_buffer 中的两个字段里。这个过程要对内存临时表做全表扫描,此时扫描行数增加 10000,变成了 20000。
6. 在 sort_buffer 中根据 R 的值进行排序。注意,这个过程没有涉及到表操作,所以不会增加扫描行数。
7. 排序完成后,取出前三个结果的位置信息,依次到内存临时表中取出 word 值,返回给客户端。这个过程中,访问了表的三行数据,总扫描行数变成了 20003。
通过慢查询日志(slow log)可以验证我们分析得到的扫描行数是否正确。
# Query_time: 0.900376 Lock_time: 0.000347 Rows_sent: 3 Rows_examined: 20003
SET timestamp=1541402277;
select word from words order by rand() limit 3;
其中,Rows_examined:20003 就表示这个语句执行过程中扫描了 20003 行,也就验证了我们分析得出的结论。
下面解释下“位置信息”。
先提个问题 : MySQL 的表是用什么方法来定位“一行数据”的? 主键?如果把主键删了呢?
简单解答如下:
如果创建的表没有主键,或者把一个表的主键删掉了,那么 InnoDB 会自己生成一个长度为 6 字节的 rowid 来作为主键。
这也是排序模式里面,rowid 名字的来历。实际上它表示的是:每个引擎用来唯一标识数据行的信息。
- 对于有主键的 InnoDB 表来说,这个 rowid 就是主键 ID;
- 对于没有主键的 InnoDB 表来说,这个 rowid 就是由系统生成的;
- MEMORY 引擎不是索引组织表。在这个例子里面,你可以认为它就是一个数组。因此,这个 rowid 其实就是数组的下标。
即有主键,位置信息就是主键;没主键,位置信息就是rowid。
磁盘临时表
不是所有的临时表都是内存表。tmp_table_size 这个配置限制了内存临时表的大小,默认值是 16M。
如果临时表大小超过了 tmp_table_size,那么内存临时表就会转成磁盘临时表。
磁盘临时表使用的引擎默认是 InnoDB,是由参数 internal_tmp_disk_storage_engine 控制的。
当使用磁盘临时表的时候,对应的就是一个没有显式索引的 InnoDB 表的排序过程。
为了复现这个过程,把 tmp_table_size 设置成 1024,把 sort_buffer_size 设置成 32768, 把 max_length_for_sort_data 设置成 16。
set tmp_table_size=1024;
set sort_buffer_size=32768;
set max_length_for_sort_data=16;
/* 打开 optimizer_trace,只对本线程有效 */
SET optimizer_trace='enabled=on'; /* 执行语句 */
select word from words order by rand() limit 3; /* 查看 OPTIMIZER_TRACE 输出 */
SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`\G
然后,我们来看一下这次 OPTIMIZER_TRACE 的结果。
sort_mode 里面显示的是 rowid 排序,这个是符合预期的,因为将 max_length_for_sort_data 设置成 16,小于 word 字段的长度定义,参与排序的是随机值 R 字段和 rowid 字段组成的行。
但是number_of_tmp_files 的值居然是 0,难道不需要用临时文件吗?R 字段存放的随机值就 8 个字节,rowid 是 6 个字节(至于为什么是 6 字节,就留给你课后思考吧),数据总行数是 10000,这样算出来就有 140000 字节,超过了 sort_buffer_size 定义的 32768 字节了。感觉不太对。
但实际上这个 SQL 语句的排序确实没有用到临时文件,采用是 MySQL 5.6 版本引入的一个新的排序算法,即:优先队列排序算法。接下来,我们就看看为什么没有使用临时文件的算法,也就是归并排序算法,而是采用了优先队列排序算法。
我们现在的 SQL 语句,其实只需要取 R 值最小的 3 个 rowid。使用归并排序算法的话,虽然最终也能得到前 3 个值,但其实已经将 10000 行数据都排好序了。浪费了非常多的计算量。
而优先队列算法,就可以精确地只得到三个最小值,执行流程如下:
1. 对于这 10000 个准备排序的 (R,rowid),先取前三行,构造成一个堆;(对数据结构印象模糊的同学,可以先设想成这是一个由三个元素组成的数组)
2. 取下一个行 (R’,rowid’),跟当前堆里面最大的 R 比较,如果 R’小于 R,把这个 (R,rowid) 从堆中去掉,换成 (R’,rowid’);
3. 重复第 2 步,直到第 10000 个 (R’,rowid’) 完成比较。
整个排序过程中,为了最快地拿到当前堆的最大值,总是保持最大值在堆顶,因此这是一个最大堆【这也保证了新加入的(R’,rowid’)会跟堆里另外两个排序,最终选出最小的三个】。
OPTIMIZER_TRACE 结果中,filesort_priority_queue_optimization 这个部分的 chosen=true,就表示使用了优先队列排序算法,这个过程不需要临时文件,因此对应的 number_of_tmp_files 是 0。
这个流程结束后,我们构造的堆里面,就是这个 10000 行里面 R 值最小的三行。然后,依次把它们的 rowid 取出来,去临时表里面拿到 word 字段。
我们再看一下 orderby工作机制 文章的 SQL 查询语句:
select city,name,age from t where city='杭州' order by name limit 1000 ;
这里也用到了 limit,为什么没用优先队列排序算法呢?
原因是,这条 SQL 语句是 limit 1000,如果使用优先队列算法的话,需要维护的堆的大小就是 1000 行的 (name,rowid),超过了设置的 sort_buffer_size 大小,所以只能使用归并排序算法。
可以看到,不论是使用哪种类型的临时表,order by rand() 这种写法都会让计算过程非常复杂,需要大量的扫描行数,因此排序过程的资源消耗也会很大。
聊聊order by rand()的更多相关文章
- MySQL的Order By Rand()的效率问题
MySQL很多时候需要获取随机数据,举个例子,要从tablename表中随机提取一条记录,大家一般的写法就是: 但是,后来我查了一下MYSQL的官方手册,里面针对RAND()的提示大概意思就是,在OR ...
- MYSQL随机抽取查询 MySQL Order By Rand()效率问题
MYSQL随机抽取查询:MySQL Order By Rand()效率问题一直是开发人员的常见问题,俺们不是DBA,没有那么牛B,所只能慢慢研究咯,最近由于项目问题,需要大概研究了一下MYSQL的随机 ...
- MySQL Order By Rand()效率
最近由于需要大概研究了一下MYSQL的随机抽取实现方法.举个例子,要从tablename表中随机提取一条记录,大家一般的写法就是:SELECT * FROM tablename ORDER BY RA ...
- MySQL 中随机抽样:order by rand limit 的替代方案
最近由于需要大概研究了一下MYSQL的随机抽取实现方法.举个例子,要从tablename表中随机提取一条记录,大家一般的写法就是:SELECT * FROM tablename ORDER BY RA ...
- 【MySQL性能优化】改进MySQL Order By Rand()的低效率
<a href="http://click.aliyun.com/m/9153/">点击查看原文</a> 正 文: 最近由于需要研究了一下MYSQL的随 ...
- ORDER BY RAND()
大概是因为需要研究了一下MYSQL随机样本实现.例如:离tablename表随机抽取了创纪录,我们一般的写法是:SELECT * FROM tablename ORDER BY RAND() LIMI ...
- [mysql] MySQL Order By Rand()效率【转载】
最近由于需要大概研究了一下MYSQL的随机抽取实现方法.举个例子,要从tablename表中随机提取一条记录,大家一般的写法就是:SELECT * FROM tablename ORDER BY RA ...
- MySQL Order By Rand()效率分析
最近研究了一下MYSQL的随机抽取实现方法.举个例子,要从tablename表中随机提取一条记录,大家一般的写法就是:SELECT * FROM tablename ORDER BY RAND() L ...
- MySQL中的RAND()函数使用详解(order by rand() 随机查询取前几条记录)
MySQL RAND()函数调用可以在0和1之间产生一个随机数: mysql> SELECT RAND( ), RAND( ), RAND( ); +------------------+--- ...
随机推荐
- SHELL用法八(Grep语句)
1.SHELL编程Grep语句案例实战 1)Find是Linux操作系统文件和目录的路径检索.查询工具,而Grep是Linux系统下文本文件内容检索或者匹配工具,Grep称为全局正则表达式检索工具,在 ...
- springboot学习笔记:1.前言
眼下,随着微服务敏捷开发的势头日益增长,前几年刚刚萌芽的话题,到了现在已经炽手可热.在java圈子里,springboot作为spring生态圈的顶级项目,更是火的不得了.作为java程序员,不了解和 ...
- Time Machine 备份mac系统
USB外接移动硬盘的方式来熟悉Time Machine,体会它带给我们的便利.O(∩_∩)O~~通过USB连接移动硬盘到MacBook Pro,如图所示 2 将移动硬盘连接到 Mac 时,系统有时会询 ...
- 对Vue为什么不支持IE8的解释之一
在JavaScript对象中有一个Object.defineProperties(obj, props)方法 该方法主要用来给指定对象添加自定义属性 可以接收两个参数: 第一个参数 要定义或者修改属性 ...
- JS计算日期加天数后的日期(起始日期+有效天数=截至日期)
/** * 优惠券有效期 * startDate:起始日期 * valueTime:有效天数 */ function transferCouponValueTime(startDate,valueTi ...
- 「CometOJ」Contest #11
Link Aeon 显然字典序最大就是把最小的字母放在最后 Business [动态规划] 简单dp dp[i][j]dp[i][j]dp[i][j]表示到第iii天,当前有jjj块钱,最后返还的钱最 ...
- 云服务器之——Linux下配置JDK环境
在Linux下jdk的安装已经操作了很多次,每次发现自己还是会忘记之前的操作,所以今天就简单的来做个记录. 第一步:下载jdk安装包 登录oracle官网:https://www.oracle.com ...
- 一致性哈希算法(consistent hashing)PHP实现
一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简单哈希 ...
- kafka相关问题总结
一直在使用kafka,遇到过很多问题,总结一下 很多人对比kafka和AMQP的时候,都会强调kafka会丢数据,感觉好像只要用kafka就会丢数据一样,从而排斥使用kafka,亦或者在使用的过程中, ...
- springboot利用swagger构建api文档
前言 Swagger 是一款RESTFUL接口的文档在线自动生成+功能测试功能软件.本文简单介绍了在项目中集成swagger的方法和一些常见问题.如果想深入分析项目源码,了解更多内容,见参考资料. S ...