有个表结构:
CREATE TABLE `words` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `word` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

表里面插入了 10000 行记录,要从中随机选择 3 个单词。

最简单的方法

mysql> select word from words order by rand() limit 3;

虽然这个 SQL 语句写法很简单,但执行流程却有点复杂的。

Extra 字段显示 Using temporary,Using filesort,表示的是需要临时表,并且需要在临时表上排序。对于 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

注:步骤5中的“位置信息”是个什么概念:MEMORY 引擎不是索引组织表。在这个例子里面,你可以认为它就是一个数组。因此,这个 rowid 其实就是数组的下标。

通过慢查询日志(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;

order by rand() 使用了内存临时表,内存临时表排序的时候使用了 rowid 排序方法。

tmp_table_size 这个配置限制了内存临时表的大小,默认值是 16M。如果临时表大小超过了 tmp_table_size,那么内存临时表就会转成磁盘临时表。磁盘临时表使用的引擎默认是 InnoDB,是由参数 internal_tmp_disk_storage_engine 控制的。

当使用磁盘临时表的时候,上面的例子对应的就是一个没有显式索引的 InnoDB 表的排序过程。

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

sort_mode 里面显示的是 rowid 排序,参与排序的是随机值 R 字段和 rowid 字段组成的行。

R 字段存放的随机值就 8 个字节,rowid 是 6 个字节,数据总行数是 10000,这样算出来就有 140000 字节,超过了 sort_buffer_size 定义的 32768 字节了。但是,number_of_tmp_files 的值居然是 0。因为这个 SQL 语句的排序采用是 MySQL 5.6 版本引入的一个新的排序算法,即:优先队列排序算法。从OPTIMIZER_TRACE 结果中,filesort_priority_queue_optimization 这个部分的 chosen=true也能看出。

其实,我们现在的 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’) 完成比较。

上面一篇文章的 SQL 查询语句,也是 limit 1000,如果使用优先队列算法的话,需要维护的堆的大小就是 1000 行的 (name,rowid),超过了我设置的 sort_buffer_size 大小,所以只能使用归并排序算法。

总之,不论是使用哪种类型的临时表,order by rand() 这种写法都会让计算过程非常复杂,需要大量的扫描行数,因此排序过程的资源消耗也会很大。

正确地随机排序

先把问题简化一下,如果只随机选择 1 个 word 值:

  1. 取得这个表的主键 id 的最大值 M 和最小值 N;
  2. 用随机函数生成一个最大值到最小值之间的数 X = (M-N)*rand() + N;
  3. 取不小于 X 的第一个 ID 的行。

暂时称作随机算法 1,看一下执行语句的序列:

mysql> select max(id),min(id) into @M,@N from t ;
set @X= floor((@M-@N+1)*rand() + @N);
select * from t where id >= @X limit 1;

这个方法效率很高,因为取 max(id) 和 min(id) 都是不需要扫描索引的,而第三步的 select 也可以用索引快速定位,可以认为就只扫描了 3 行。但实际上,这个算法本身并不严格满足题目的随机要求,因为 ID 中间可能有空洞,因此选择不同行的概率不一样,不是真正的随机。

为了得到严格随机的结果,你可以用下面这个流程:

  1. 取得整个表的行数,并记为 C。
  2. 取得 Y = floor(C * rand())。 floor 函数在这里的作用,就是取整数部分。
  3. 再用 limit Y,1 取得一行。

这个是随机算法 2,解决了算法 1 里面明显的概率不均匀问题。MySQL 处理 limit Y,1 的做法就是按顺序一个一个地读出来,丢掉前 Y 个,然后把下一个记录作为返回结果,因此这一步需要扫描 Y+1 行。再加上,第一步扫描的 C 行,总共需要扫描 C+Y+1 行,执行代价比随机算法 1 的代价要高。

如果按照这个表有 10000 行来计算的话,C=10000,要是随机到比较大的 Y 值,那扫描行数也跟 20000 差不多了,接近 order by rand() 的扫描行数,但是依然比order by rand() 执行代价小很多。因为随机算法2进行limit获取数据的时候是根据主键排序获取的,主键天然索引排序,这里省去了这个过程。

如果我们按照随机算法 2 的思路,要随机取 3 个 word 值呢:

  1. 取得整个表的行数,记为 C;
  2. 根据相同的随机方法得到 Y1、Y2、Y3;
  3. 再执行三个 limit Y, 1 语句得到三行数据。

这个随机算法  的总扫描行数是 C+(Y1+1)+(Y2+1)+(Y3+1),实际上它还是可以继续优化,来进一步减少扫描行数的:

  1. 在随机出Y1、Y2、Y3后,算出Ymax、Ymin;
  2. 再用 select id from t limit Ymin,(Ymax - Ymin + 1);
  3. 得到id集后算出Y1、Y2、Y3对应的三个id;
  4. 最后 select * from t where id in (id1, id2, id3)。

这样扫描的行数应该是C+Ymax+3。

内容来源: 林晓斌《MySQL实战45讲》

MySQL随机排序的正确姿势相关推荐

  1. php mysql 随机排序函数_php+mysql实现数据库随机重排实例

    本文实例实现了php+mysql数据库随机重排的方法,可将表中的所有数据随机读出来一次之后再进行随机保存到另一个表,从而达到了记录随机的功能. 主要实现代码如下: //数据库连接就不写在这里面了 $s ...

  2. MySql 你知道如何正确的取随机数据吗 ?

    志在巅峰的攀登者,不会陶醉在沿途的某个脚印之中,在码农的世界里,优美的应用体验,来源于程序员对细节的处理以及自我要求的境界,年轻人也是忙忙碌碌的码农中一员,每天.每周,都会留下一些脚印,就是这些创作的 ...

  3. mysql实战17 | 如何正确地显示随机消息?

    我在上一篇文章,为你讲解完 order by 语句的几种执行模式后,就想到了之前一个做英语学习 App 的朋友碰到过的一个性能问题.今天这篇文章,我就从这个性能问题说起,和你说说 MySQL 中的另外 ...

  4. eclipse连接mysql_专题一、flask构建mysql数据库正确姿势

    每周壹总结,一起共同充电第121篇 应用程序最核心的就是数据,每天我们写程序其实也是在处理数据的过程,那么很有必要系统性的讲讲和梳理python的flask框架是如何进行数据交互操作的. 趁这3天假期 ...

  5. 使用R语言的正确姿势,R包干货奉献

    生物信息学习的正确姿势 NGS系列文章包括NGS基础.在线绘图.转录组分析 (Nature重磅综述|关于RNA-seq你想知道的全在这).ChIP-seq分析 (ChIP-seq基本分析流程).单细胞 ...

  6. 如何成为一名合格的Apache项目Committer,参与Apache开源贡献的正确姿势

    近日,孙金城老师在 "Open Source Promotion Plan - Summer 2020" 开源软件供应链点亮计划做了<如何成为一名合格的Apache项目Com ...

  7. 开发方向校招准备的正确姿势,机会留给有准备的人

    一.背景 马上就快到校招的时间了. 网上有很多分享面经的地方,也有一些博文分享作者的面试经历,尤其是大公司的面试经历. 大多数是分享具体的问题,而没有系统的总结出方法论.导致大家只不过是在刷题!仅此而 ...

  8. 开发函数计算的正确姿势 —— 爬虫

    2019独角兽企业重金招聘Python工程师标准>>> 在 <函数计算本地运行与调试 - Fun Local 基本用法> 中,我们介绍了利用 Fun Local 本地运行 ...

  9. Navicat使用Instant Client创建连接到Oracle数据库的正确姿势

    太长不看版: 1.你什么操作系统,Instant Client就选什么操作系统 2.你的navicat是多少位(32.64),Instant Client就选多少位 3.你的Oracle是哪个版本,I ...

  10. AI 玩微信跳一跳的正确姿势:跳一跳 Auto-Jump 算法详解

    作者丨安捷 & 肖泰洪 学校丨北京大学硕士生 研究方向丨计算机视觉 本文经授权转载自知乎专栏「学术兴趣小组」. 最近,微信小游戏跳一跳可以说是火遍了全国,从小孩子到大孩子仿佛每一个人都在刷跳一 ...

最新文章

  1. 一个完整的Core Data应用
  2. 复杂问题需要系统思维
  3. 基本拖拽效果,使用 mousedown , mousemove , mouseup实现
  4. P1433 吃奶酪 回溯法 优化
  5. KMP 算法并非字符串查找的优化 [转]
  6. MATLAB在通信系统仿真中的注意
  7. android 多行 对齐方式,android – 按钮与多行文字下沉对齐线,如何解决?
  8. 理解Javascritp中的引用
  9. matlab meshgrid
  10. mysql 数据库引擎介绍_MYSQL 数据库引擎介绍
  11. 快手有佳人|2020快手女性人群价值报告
  12. 【今日CV 计算机视觉论文速览】Mon, 28 Jan 2019
  13. 黄轩成为QQ阅读新代言人 变身“队长”号召网友 “组队读书”
  14. mysql 二进制日志大小_mysql二进制日志。
  15. 【CLR】解析AppDomain
  16. java 数组减除值_java – 删除数组中空值的最有效方法是什么.
  17. python画航线图_数据可视化:python调用pyecharts库绘制航线专题图
  18. Linux:文件系统和数据资料
  19. 映客卖身、花椒获资、抖音崛起——直播和短视频现状分析
  20. Type-C边充电边OTG芯片LDR6028A

热门文章

  1. 【斜对称矩阵】向量的斜对称矩阵表示
  2. 盒子科技刘恒:聚合支付系统演讲
  3. NS3:FlowMonitor设计讲解
  4. Ubuntu系统安装分区
  5. What is Dymola?---Dymola的特点和架构
  6. opencv配置VS2019环境
  7. 印刷文字的字体与字号规定
  8. 极化码的巴氏参数构造算法
  9. java把date转化成yyyymmdd_jquery 将当前时间转换成yyyymmdd格式的实现方法
  10. AD15的PCB设计流程及基本设置