Mysql学习笔记--如何正确地显示随机消息

180 阅读4分钟


思考

内存临时表

 select word from words order by rand() limit 3

虽然这条语句看起来很简单,但是执行流程复杂。

从图中可以看出,使用了临时表

其中我们会用哪种算法进行排序呢?

全字段:对于Innodb来说执行全字段排序会减少磁盘访问操作,所以会被优先考虑

rowid:但是对于内存临时表来说,数据已经在内存中,所以不用回表操作了

这条语句的执行流程是这样的:创建一个临时表。这个临时表使用的是 memory 引擎,表里有两个字段,第一个字段是 double 类型,为了后面描述方便,记为字段 R,第二个字段是 varchar(64) 类型,记为字段 W。并且,这个表没有建索引。

从 words 表中,按主键顺序取出所有的 word 值。对于每一个 word 值,调用 rand() 函数生成一个大于 0 小于 1 的随机小数,并把这个随机小数和 word 分别存入临时表的 R 和 W 字段中,到此,扫描行数是 10000。

现在临时表有 10000 行数据了,接下来你要在这个没有索引的内存临时表上,按照字段 R 排序。

初始化 sort_buffer。sort_buffer 中有两个字段,一个是 double 类型,另一个是整型。

从内存临时表中一行一行地取出 R 值和位置信息(我后面会和你解释这里为什么是“位置信息”),分别存入 sort_buffer 中的两个字段里。这个过程要对内存临时表做全表扫描,此时扫描行数增加 10000,变成了 20000。

在 sort_buffer 中根据 R 的值进行排序。注意,这个过程没有涉及到表操作,所以不会增加扫描行数。

排序完成后,取出前三个结果的位置信息,依次到内存临时表中取出 word 值,返回给客户端。这个过程中,访问了表的三行数据,总扫描行数变成了 20003。

Innodb如果删除了主键,系统会自动根据row_id创建主键,row_id是每个引擎用来标识数据行的信息。

在内存临时表中row_id作为数组的下标出现。

磁盘临时表

tmp_table_size作为内存表与磁盘表转换的边界(当临时表超过这个值则用磁盘临时表)

磁盘临时表使用的引擎默认是 InnoDB,是由参数 internal_tmp_disk_storage_engine 控制的。

Mysql5.6引入新的排序算法--优先队列排序,为什么不使用归并算法,因为归并算法的最后会将所有数据都排序好,这种分治的方法的弊端,会造成大量不必要的计算量

其实就是首相将3组数据放进堆中,进来一个淘汰一个最大的,以此类推最后剩下就是我们需要的三个值。

OPTIMIZER_TRACE 结果中,filesort_priority_queue_optimization 这个部分的 chosen=true,就表示使用了优先队列排序算法,这个过程不需要临时文件,因此对应的 number_of_tmp_files 是 0。

当堆的大小大于sort_buffer_size的时候,就只能用归并算法了。

随机算法

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

随机算法1:取得最大值与最小值,随机生成最大值与最小值之间的数

每次的执行结果不一样

这种算法的弊端:选择的概率不一样,随机值落到空洞区间,会取到同一个值。空洞区间越大,空洞越多,越容易得到同一个值

随机算法2:

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 @Y11//在应用代码里面取Y1、Y2、Y3值,拼出SQL后执行
select * from t limit @Y21select * from t limit @Y31

去整个表的行数C,再取floor(C*rand())取整数部分,再用limit Y,1取得一行

随机算法3:

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 @Y11//在应用代码里面取Y1、Y2、Y3值,拼出SQL后执行
select * from t limit @Y21select * from t limit @Y31

结论

order by rand(),这个语句需要 Using temporary 和 Using filesort,查询的执行代价往往是比较大的。所以,在设计的时候你要尽量避开这种写法。