一、SQL语句执行流程分析
在进行业务开发时,某些需求需要展示一个或者多个随机数据,一种比较常规的写法如下:
SELECT word FROM words ORDER BY rand() limit 3;
这条语句是在words表中随机取3个word数据。下面对流程进行简要分析
-
读取数据到临时内存表中
在读取数据时,会创建临时内存表,将word字段读取到临时内存表中,在该表新增一个随机数字段R并生成一个随机值。
-
使用rowid排序的方式将数据读入sort buffer并排序
由于临时内存表是在内存中,因此采用rowid方式进行排序。即将随机数字段R和行号信息pos传入sort buffer中,在根据R字段进行排序。
-
读取数据
此时sort buffer内部是排序好的R字段和位置信息pos字段的表集合。获取前3条数据的位置信息,在临时内存表中找到对应行的数据并返回。
图如下:
疑点解答:为什么不能直接使用临时内存表进行R字段排序取得结果?
首先MySQL的排序操作都是在sort buffer中进行的,临时内存表只会进行数据暂存,不会进行排序操作。上图可能看起来sort buffer的排序有些多余,直接在临时内存表中进行即可,sort buffer的排序只是为了获得一个R和pos的映射关系。 此时需要考虑一个问题,如果需要获取的字段是50个,而不是一个word字段,此时临时内存表内存使用量会很大,采用rowid的方式排序只需要排序字段即可,可以在排序阶段极大减少内存的开销,然后在回读数据即可。order by排序参考
二、推荐方式
从上述可以看出,一个常规的order by rand()内部的流程一点不少,其中需要进行2次全表扫描,第一次是读取数据到临时内存表,第二次是读取到sort buffer。那么有没有推荐的获取随机消息的方式呢?
-
使用自增主键进行条件查询
既然MySQL需要这么复杂的操作才能获取一个随机消息。那么在外部控制如何?直接定义一个获取随机id的算法,比如id = Math.floor((maxId - minId) * rand())。然后通过条件查询的方式获取这个id的值。这种方式就避免了内部的复杂算法,直接通过主键索引快速获取数据。但这种方式不稳定,因为主键不是一定自增的,且可能存在空档,即1,10,50,1000的情况,算出来的id的概率不是完全随机的。
-
获取某随机行的数据
这种模式和前面一种思想类似,只不过是把计算id变成了计算第几行。比如当前存在10000条数据,计算出一个随机行8500,此时只需要通过以下SQL:
SELECT word from words LIMIT 8500,1这种方式虽然对于表的扫描行数和计算出第几行的随机数有一定关系,但最坏的情况也只会进行一次全表扫描,综合效率还是比较可观的。