假如我们有一个单词表,用户每次访问首页时,都会随机滚动显示三个单词,发现随着单词表变大,选单词这个逻辑变得越来越慢,甚至影响到了首页得打开速度。
那么使用了什么sql语句呢,为什么越来越慢呢?
我们先来创建表,表名为words,字段只有主键id和word,没有二级索引,语句如下:
CREATE TABLE `words` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`word` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
数据Sql语句,插入一万条数据,语句如下:
INSERT INTO words (word)
SELECT SUBSTRING(MD5(RAND()), 1, 10)
FROM (
SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL
SELECT 9 UNION ALL SELECT 10
) AS numbers
CROSS JOIN
(
SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL
SELECT 9 UNION ALL SELECT 10
) AS numbers2
CROSS JOIN
(
SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL
SELECT 9 UNION ALL SELECT 10
) AS numbers3
CROSS JOIN
(
SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL
SELECT 9 UNION ALL SELECT 10
) AS numbers4
WHERE (
SELECT COUNT(*) FROM words
) < 10000;
然后从这个表的数据里随机查询3条数据,使用了rand函数,
select word from words order by rand() limit 3;
我们来看看执行计划这个 语句做了什么?
explain select word from words order by rand() limit 3;
我们看虽然是简单查询,但是其实执行过程还是蛮复杂的,看Extra字段,语句使用了临时表Using temporary:代表了使用了临时表,Using filesort:需要执行排序操作,说明我们即使用了临时表还需要在临时表上排序(我的rows为2万行是我在测试时又添加了数据不用觉得好奇哈!)
排序的方式这里选择了rowid排序形式,因为这里使用了内存临时表(用的引擎是memory),回临时表里取数据不涉及随机读,也不涉及扫描行,效率较高,所以配合临时表时,排序会使用rowid。
这条语句的执行流程时这样的:
我们可以通过慢查询日志看一下扫描行数是否正确,
首先查看自己是否开启慢日志
show VARIABLES like 'slow_query_log'
开启了会返回ON(我的已经开启了),没有就是OFF
如果你没有开启,可以通过下面语句开启
set GLOBAL slow_query_log='ON'
查看慢日志文件配置在哪里了,也可以配置文件路径
show VARIABLES like 'slow_query_log_file'
set GLOBAL slow_query_log_file='E:/slow_sql.log';
语句执行多少秒才算慢查询或者说触发记录慢查询日志,时间的化因为我测试发现每次执行消耗是0.02以后,所以设定的0.02秒
# 查询慢查询时间
show VARIABLES like 'long_query_time'
# 设置慢查询时间,需要重新启动navicat
set GLOBAL long_query_time=0.02;
测试慢查询日志发现没记录,配置了如下,查询了下没有索引的是否记录,是OFF,需要开启
# 不使用索引的查询是否开启
show VARIABLES like 'log_queries_not_using_indexes'
# 开启
set GLOBAL log_queries_not_using_indexes=on;
然后执行sql,执行完去对应的文件看下慢日志内容,当时我的表里有10000多一点的数据,所以多了一倍的扫描就是20000多条。
图示:这个执行流程
也就是说,order by rand()使用了内存临时表,内存临时表排序的时候使用了rowid排序方法。
当然,内存临时表是有大小的,超过内存临时表就使用磁盘临时表,我们可以设置下配置
# 查询临时表大小默认16M
show VARIABLES like 'tmp_table_size'
# 当前会话内设置临时表大小,超过这个大小就用磁盘临时表
set tmp_table_size=1024;
再来设置关于排序处理的配置,然后开启优化器跟踪,目的执行sql语句时可以查询优化的结果。
set sort_buffer_size=32768
# 排序过程中最大数据长度
set max_length_for_sort_data=16
# 开启优化器跟踪
set optimizer_trace='enabled=on';
查询sql,并查看optimizer_trace 结果
# 查看 optimizer_trace 输出
select word from words order by rand() limit 3;
select * from information_schema.OPTIMIZER_TRACE;
数据沾到在线json里我们看下,
我测试时filesort_summary.sort_mode使用了additional_fields排序的方式,而作者使用了rowid的方式,我这个是代表用了额外的字段来进行排序。
filesort_summary.number_of_tmp_files这个等于0,相当于没用临时文件,但是按道理sort_buffer_size设置的很小,因为不够所以正常是需要临时文件,但这里看到确实没有用到,因为它用了一个排序算法,叫优先队列排序算法,我们看filesort_priority_queue_optimization.chosen是true,代表了采用了优先队列算法,为什么没有采用临时文件的算法(归并排序),而是哟个了优先队列算法呢?
其实就像limit一样(limit一般使用优先队列,但是limit数据过多,超过了sort_buffer_size的大小则就归并排序了),我们只要部分的数据,如此处只要最小的3个rowid,如果使用归并排序,则是要把所有的数据都排序一遍,但我们不需要所有的都排序,浪费了很多的计算量,使用优先队列,就可以精确的只得到最小值,执行流程如下:
图片来源-极客时间
总之,不论使用哪种临时表,order by rand()这种写法让计算过程非常都咋,需要大量的扫描行数,我们最好可以采用业务代码处理,去进行随机排序。
如:
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; //在应用代码里面取Y1、Y2、Y3值,拼出SQL后执行 select * from t limit @Y2,1;
select * from t limit @Y3,1;