Mysql调优-随机消息使用临时表的优化

假如我们有一个单词表,用户每次访问首页时,都会随机滚动显示三个单词,发现随着单词表变大,选单词这个逻辑变得越来越慢,甚至影响到了首页得打开速度。

那么使用了什么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;

1.内存临时表

然后从这个表的数据里随机查询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。

这条语句的执行流程时这样的:

  1. 创建一个临时表,表里有两个字段,第一个字段是double类型,为了方便描述,记为R,第二个字段是varchar(64)类型,记为字段W,并且,这个表没有建索引。
  2. 从wards表中,按主键顺序取出所有的ward值,对每一个ward值调用rand函数生成一个大于0小于1的随机数,并把这个随机小数和ward分别存入临时表的R和W字段中,到此,扫描行数是10000.
  3. 临时表里有了10000条数据了,接下来要在没有索引的内存临时表,按照R字段排序。
  4. 初始化sort_buffer,sort_buffer中有两个字段,一个是double类型,一个是整型。
  5. 从内存临时表中一行一行取出R值和位置信息(memory不是索引组织可以看成数组,通过下标找位置),分别存入sort_buffer的两个字段里。这个过程要对内存临时表进行全表扫描,此时扫描行数增加10000,变成了20000.
  6. 在sort_buffer根据R值进行排序,没有涉及到表操作,不会增加扫描行数,排序完成,取出前三个结果位置信息,依次到内存临时表取出ward值,返回给客户端,这时候访问了表得三行数据,总扫描行数就是20003。

我们可以通过慢查询日志看一下扫描行数是否正确,

2 慢日志查询配置

首先查看自己是否开启慢日志

show VARIABLES like 'slow_query_log'

开启了会返回ON(我的已经开启了),没有就是OFF

Mysql调优-随机消息使用临时表的优化_第1张图片

如果你没有开启,可以通过下面语句开启 

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多条。

 图示:这个执行流程

Mysql调优-随机消息使用临时表的优化_第2张图片

 

也就是说,order by rand()使用了内存临时表,内存临时表排序的时候使用了rowid排序方法。

3.磁盘临时表

当然,内存临时表是有大小的,超过内存临时表就使用磁盘临时表,我们可以设置下配置

# 查询临时表大小默认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里我们看下, 

Mysql调优-随机消息使用临时表的优化_第3张图片

 我测试时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,如果使用归并排序,则是要把所有的数据都排序一遍,但我们不需要所有的都排序,浪费了很多的计算量,使用优先队列,就可以精确的只得到最小值,执行流程如下:

  1. 对于10000条数据,先取前三行,构成一个堆,
  2. 取下一行的(R',rowid),跟当前堆里面最大的R比较,如果R'小于R,把这个(R,rowid)从堆里去掉,换成最新的。
  3. 重复第二步,直到都比较完成。
  4. 流程结束后,最小的三行依次取出rowid,去临时表里面拿word字段。

                                         图片来源-极客时间

Mysql调优-随机消息使用临时表的优化_第4张图片

 

总之,不论使用哪种临时表,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;

你可能感兴趣的:(#,MySQL,mysql,数据库)