在1000个单词中随机选择 3 个单词,有什么方法实现?
内存临时表:
mysql> select word from words order by rand() limit 3;
随机排序,取前3个,这个语句的执行过程是:
Extra 字段显示 Using temporary,表示的是需要使用临时表;Using filesort,表示的是需要执行排序操作。因此这个 Extra 的意思就是,需要临时表,并且需要在临时表上排序。
order by rand() 使用了内存临时表,内存临时表排序的时候使用了 rowid 排序方法。
1)创建一个临时表。这个临时表使用的是 memory 引擎,并且,这个表没有建索引。
2)从 源表中,按主键顺序取出所有的 值。对于每一个 word 值,调用 rand() 函数生成一个大于 0 小于 1 的随机小数,并把这个随机小数和 word 分别存入临时表的 R 和 W 字段中。临时表的数量就是源表的行数
3)接下来在这个没有索引的内存临时表上,按照字段 R 排序。
4)初始化 sort_buffer。sort_buffer 中有两个字段,一个是 double 类型,另一个是整型。从内存临时表中一行一行地取出 R 值和位置信息(R就是rowid),分别存入 sort_buffer 中的两个字段里。这个过程要对内存临时表做全表扫描,此时扫描行数翻一倍。
5)在 sort_buffer 中根据 R 的值进行排序。注意,这个过程没有涉及到表操作,所以不会增加扫描行数。
6)排序完成后,取出前三个结果的位置信息,依次到内存临时表中取出 word 值,返回给客户端。访问了表的三行数据,总扫描行数又增加了3。
磁盘临时表
tmp_table_size 这个配置限制了内存临时表的大小,默认值是 16M。如果临时表大小超过了 tmp_table_size,那么内存临时表就会转成磁盘临时表。磁盘临时表使用的引擎默认是 InnoDB,是由参数 internal_tmp_disk_storage_engine 控制的。
MySQL 5.6 版本引入的一个新的排序算法,即:优先队列排序算法。
之前使用归并排序算法的话,虽然最终也能得到前 3 个值,其实已经将 所有 行数据都排好序了。但我们并不需要这些数据都是有序的,这浪费了非常多的计算量。而优先队列算法,就可以精确地只得到三个最小值,执行流程如下:
1)对于这 10000 个准备排序的 (R,rowid),先取前三行,构造成一个堆;(对数据结构印象模糊的同学,可以先设想成这是一个由三个元素组成的数组)
2)取下一个行 (R’,rowid’),跟当前堆里面最大的 R 比较,如果 R’小于 R,把这个 (R,rowid) 从堆中去掉,换成 (R’,rowid’);
3)重复第 2 步,直到第 10000 个 (R’,rowid’) 完成比较。
为了最快地拿到当前堆的最大值,总是保持最大值在堆顶,因此这是一个最大堆。
OPTIMIZER_TRACE 结果中,filesort_priority_queue_optimization 这个部分的 chosen=true,就表示使用了优先队列排序算法,这个过程不需要临时文件,因此对应的 number_of_tmp_files 是 0。
随机排序方法
算法1:
1)取得这个表的主键 id 的最大值 M 和最小值 N;
2)用随机函数生成一个最大值到最小值之间的数 X = (M-N)*rand() + N;
3)取不小于 X 的第一个 ID 的行。
优点:快速、简单
缺点:对于稀疏数据效果不好
算法2:
1)取得整个表的行数,并记为 C。
2)取得 Y = floor(C * rand())。floor 函数在这里的作用,就是取整数部分。
3)再用 limit Y,1 取得一行。
mysql> select count(*) into @C from t;
set @Y1 = floor(@C * rand());
set @Y2 = floor(@C * rand());
set @Y3 = floor(@C * rand());
select * from t limit @Y1,1;
select * from t limit @Y2,1;
select * from t limit @Y3,1;
//在应用代码里面取Y1、Y2、Y3值,拼出SQL后执行




