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

英语学习 App性能问题。另一种排序需求。首页随机显示三个单词(根据用户级别)。单词表变大,选单词越来越慢

简化:去掉用户级别对应单词表,随机选。

insert  into words(word) values(concat(char(97+(i div 1000)), char(97+(i % 1000 div

100)), char(97+(i % 100 div 10)), char(97+(i % 10))));

插入了 10000 行记录。

一、内存临时表

select  word from words order by rand() limit 3;

随机排序取前 3 个。执行流程复杂。

图 1 使用 explain 命令查看语句的执行情况  

Using temporary需临时表;Using filesort需要执行排序。Extra :在临时表上排序

图 2 全字段排序
图 3 rowid 排序

对于 InnoDB 表,全字段排序会减少磁盘访问,优先。

内存表,回表过程只是简单地根据数据行的位置,直接访问内存得到数据,根本不会导致多访问磁盘。优化器没有这顾虑,、排序行越小越好,选择 rowid 排序。执行流程:

1.  创建临时表。memory 引擎,字段R(double),W varchar(64) 。没有索引。

2.  按主键顺序取出所有word。调用 rand() 生成0 到1 随机数存入临时表 R 和 W 字段中,扫描行数10000。

3.  没有索引内存临时表上,按 R 排序

4.  初始化 sort_buffer

5.  一行行地取出 R 值和位置信息,存入 sort_buffer 。对内存临时表做全表扫描,扫描行数 10000变20000。

6.  sort_buffer 中R 排序。没有涉及到表,不会增加扫描行数。

7.  排序完成后,取前三位置信息,到临时表中取出 word 值返回给客户端。访问了表三行数据,总扫描行数变20003。

慢查询日志(slow log)验证

# Query_time:  0.900376  Lock_time: 0.000347  Rows_sent: 3 Rows_examined: 20003

SET  timestamp=1541402277;  select word from  words order by rand() limit 3;

图 4 随机排序完整流程图 1

pos位置信息:定位一行数据。rowid:每个引擎用来唯一标识数据行的信息。

有主键rowid 就是主键ID;没有rowid 系统生成(6 字节);

MEMORY 引擎不是索引组织表。 rowid 其实就是数组的下标。

order by rand()用内存临时表(rowid 排序)

二、磁盘临时表

tmp_table_size默认16M限制内存临时表大小,超过tmp_table_size,内存临时表转成磁盘临时表(默认InnoDB,internal_tmp_disk_storage_engine控制)。

磁盘临时表时,没显式索引InnoDB 排序。为了复现

set  tmp_table_size=1024;

set  sort_buffer_size=32768;

set  max_length_for_sort_data=16;

/* 打开 optimizer_trace,只对本线程有效*/SET  optimizer_trace='enabled=on';

/* 执行语句*/   select word from  words order by rand() limit 3;

/* 查看 OPTIMIZER_TRACE 输出*/   SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`\G

图 5 OPTIMIZER_TRACE 部分结果

max_length_for_sort_data  16,小于 word 字段长度定义 rowid 排序,符合预期,参与排序的是随机值 R 字段和 rowid 字段组成的行。

R  8 字节,rowid 6 字节,总行数 10000,算出140000 字节,sort_buffer_size的 32768 字节了。number_of_tmp_files  0,没用临时文件,filesort_priority_queue_optimization  chosen=true,表示优先队列排序

取 R 值最小3 个 rowid。归并排10000 行数据。浪费计算量。优先队列算法:

1.  10000  (R,rowid),取前三行,构造堆;

2.  取下个行 (R’,rowid’),跟当前堆最大的 R 比较,R’小于 R, (R,rowid) 换成 (R’,rowid’);

3.  重复第 2 步,10000 个。

图 6 优先队列排序算法示例  

如果limit 1000,超过sort_buffer_size 大小,用归并排序算法。

不论哪种临时表,order by rand() 计算复杂,要大量扫描行数,资源消耗大。

三、随机排序方法

随机算法 1,随机选择 1 个 word 值:

1.  取得主键 id 的最大值 M 和最小值N;

2.  随机函数生成(最大到最小值之间数)X =(M-N)*rand() + N

3.  取不小于 X 第一个 ID 行

mysql> select  max(id),min(id) into @M,@N from t ;

set @X=  floor((@M-@N+1)*rand() + @N);

select * from t  where id >= @X limit 1;

效率高,max(id) 和 min(id) 不需扫描索引,select 索引快速定位,只扫描了 3 行。不严格满足题目的随机要求,ID 中可能有空洞,不是真正随机

id是 1、2、4、5,取到 id=4 概率是其他两倍。

id 是 1、2、40000、40001,基本 bug 

随机算法 2,严格随机结果流程:

1.  取整表行数,记为 C。

2.  取得 Y = floor(C * rand())。 floor 取整数部分

3.  limit Y,1 取一行。

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;

解决1概率不均匀问题,limit 后参数不能直接跟变量,用prepare+execute 方法。把拼接 SQL 写程序中,更简单。

处理 limit Y,1丢掉前 Y 个,扫描 Y+1 行。加上 C 行,总C+Y+1 行,代价高。

跟直接 order by rand() 比代价小。

C=10000,随机大Y 值,跟 20000 差不多,接近 order by rand()?

随机算法 3,2 思路,随机取 3 个:

1.  整表行,记为 C;

2.  随机方法得 Y1、Y2、Y3;

3.  执行三个 limit Y, 1 得三行

小结

借随机排序需求,MySQL 对临时表排序执行过程。

直接用 order by rand(),需Using temporary 和 Using filesort,查询代价大。设计时避开。

今天例子,不仅数据库内部解决问题,还配合拼接 SQL 语句。实际规范用法:业务逻辑写业务代码中,数据库只“读写”。

思考题

随机算法 3 ,总扫描行数:C+(Y1+1)+(Y2+1)+(Y3+1),怎么减少扫描行?

(1)Y1、Y2 和 Y3 最大为M,最小为 N,执行:mysql> select  * from t limit N, M-N+1;

总 C+M+1 行。

(2)先取 id 值,确定以后,再执行三次 where id=X 

(3)装jdk或redis缓存:量不多,随机访问数组好。单词10字节,10*10000,1M装下。

(4)表里存无空洞自增值,用数据库,方案1好,单词库不变,把空洞处理调。原在A表,新建B表 ,insert into B(word) select word from A. B的id自增,生成连续主键。量大禁用,RR隔离级别会锁A表 

(5)rowid 方法:增加主键字段,记录每行rowid,连续一万随机,回表查行记录

(6)id 迭代往下,主键索引有序性按Y排序,第一取完,拿到对应id,where id大于xxx,limit y2-y1,1

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容