0%

MySQL如何正确地显示随机消息?

先说结论

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

在实际应用的过程中,比较规范的用法是:尽量将业务逻辑写在业务代码中,让数据库只做”读写数据“的事情。

正确的方法

下面这个流程:

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

下面这段代码,就是上面流程的执行语句的序列。

1
2
3
4
5
6
7

mysql> select count(*) into @C from t;
set @Y = floor(@C * rand());
set @sql = concat("select * from t limit ", @Y, ",1");
prepare stmt from @sql;
execute stmt;
DEALLOCATE prepare stmt;

由于limit后面的参数不能直接跟变量,所以在上面的代码中使用prepare+execute的方法。你也可以把拼接SQL语句的方法写在应用程序中,会更简单些(其实我觉得也更规范,业务逻辑写在业务代码中)。

MySQL处理limit Y, 1的做法就是按顺序一个一个地读出来,丢掉前Y个,然后把下一个记录作为返回结果,因此这一步需要扫描Y+1行。再加上,第一步扫面的C行,总共需要扫描C+Y+1行。

跟直接order by rand()比起来,执行代价还是小很多的。

如果按照这种思路,随机取3个word的值呢?可以这么做:

  1. 取得整个表的行数,即为C
  2. 按照相同的随机方法得到Y1、Y2、Y3
  3. 再执行三个limit Y, 1语句得到三行数据。
  4. 可以优化成,假设Y1,Y2,Y3是由小到大的三个数,则可以优化成这样,这样扫描行数为Y3 id1 = select * from t limit @Y1,1; id2= select * from t where id > id1 limit @Y2@Y1,1; select * from t where id > id2 limit @Y3 - @Y2,1;

参考

丁奇老师的MySQL实战45讲