MySQL 17 如何正确地显示随机消息?
假设有一个场景,一个英语学习APP首页有一个随机显示单词的功能,用户每次访问首页的时候,都会随机滚动显示三个单词。
已知表里有10000条记录,来看看随机选择3个单词有什么方法,又存在什么问题。
建表语句:
mysql> CREATE TABLE `words` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`word` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
内存临时表
首先,可以使用order by rand()来实现:
select word from words order by rand() limit 3;
该语句的执行情况:

Extra字段显示Using temporary,表示需要使用临时表;Using filesort,表示需要执行排序操作。
为了更好地分析,这里引用上一篇文章中全字段排序和rowid排序的流程图:


那么对于临时内存表的排序来说,它会选择哪一种算法呢?
对于内存表,回表过程只是简单根据数据行的位置,直接访问内存得到数据,不会导致多访问磁盘。这种情况下,优化器会考虑用于排序的行的大小,所以MySQL会选择rowid排序方法。
因此该语句的执行流程为:
创建一个临时表,临时表用的是MEMORY引擎,表里有两个字段,第一个是double类型,记为字段R,第二个是varchar(64)类型,记为字段W,临时表没有建索引;
从words表按主键顺序取出所有的word值,对于每一个word,调用rand()生成一个大于0小于1的随机数,并把这个随机数和word分别存入临时表的R和W字段中,该步骤扫描行数10000行;
初始化sort_buffer,里面有两个字段,一个是double类型,另一个是整型;
从内存临时表中逐行取出R值和“位置信息”(后面解释),分别存入sort_buffer中的两个字段里,这个过程要对内存临时表做全表扫描,该步骤扫描行数10000行;
在sort_buffer中根据R值进行排序;
排序完成后,取出前三个结果的位置信息,依次到内存临时表取出word值,返回给客户端。该步骤访问三行,因此总扫描行数变为20003。
完整的排序执行流程图:

位置信息本质是数据库引擎用来快速定位“一行数据”的唯一标识,一般称为rowid,在不同引擎中其具体形式不同:
对于有主键的InnoDB表,rowid就是主键ID;
对于没有主键的InnoDB表,rowid是由系统生成的6字节的主键;
对于MEMORY引擎,由于其不是索引组织表,可以认为是一个数组,因此rowid其实是数组的下标。
因此,可以总结:order by rand()使用了内存临时表,内存临时表排序时候使用了rowid排序方法。
磁盘临时表
并不是所有的临时表都是内存表,参数tmp_table_size配置限制了内存临时表的大小,默认是16M,如果临时表大小超过了配置值,内存临时表会转成磁盘临时表。
磁盘临时表使用的引擎默认是InnoDB,是由参数internal_tmp_disk_storage_engine控制。
当使用磁盘临时表,对应是一个没有显式索引的InnoDB表的排序过程。这里把tmp_table_size设为1024,sort_buffer_size设为32768,max_length_for_sort_data设为16,查看OPTIMIZER_TRACE,得到部分结果如下:

对于结果:
sort_mode里是rowid,这个符合预期,因为max_length_for_sort_data设为16,小于word字段的长度定义,因此使用rowid算法,参与排序是随机值R和6字节的主键;
number_of_tmp_files=0,没有用到临时文件是因为这个语句的排序用的是MySQL 5.6版本引入的优先队列排序算法。
对于取R值最小的3个rowid的目标,如果使用归并排序,在算法结束后已经将10000行数据都排好序了,其实浪费了比较多的计算量,而使用优先队列算法就可以精确只得到三个最小值,其执行流程为:
对于10000个准备排序的(R,rowid),取前三行构造大根堆;
取下一行(R',rowid'),与堆顶R比较,如果R'小于R,将堆顶弹出,放入(R',rowid');
重复上一步,直到第10000个数据完成比较;
取出堆中的rowid,去临时表里拿到word字段。
在OPTIMIZER_TRACE结果中,filesort_priority_queue_optimization里的chosen=true,就表示使用了优先队列排序算法。
那为什么上篇文章的查询语句(见下)没有使用优先队列呢?
select city,name,age from t where city='杭州' order by name limit 1000 ;
是因为如果使用优先队列,需要维护的堆的大小是1000行的(name,rowid),超过了设置的sort_buffer_size大小,所以只能使用归并排序算法。
不过,不论是使用内存临时表还是磁盘临时表,order by rand()这种方法都会让计算过程非常复杂,需要大量的扫描行数。
随机排序方法
把问题简化一下,如果只随机选择一个word值,那么思路为:
取表的主键id的最大值M和最小值N;
用随机函数生成一个最大值与最小值之间的数\(X=(M-N)*rand()+N\);
取不小于X的第一个ID的行。
我们把这个算法称为算法1,其执行语句为:
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;
算法1的效率很高,因为取最值都不需要扫描索引,而第三步的查询也能利用索引快速定位,可以认为总共就扫描了3行。但这个算法并不严格满足要求,因为ID中间可能有空洞,那么选择不同行的概率不一样,并不是真正的随机。
比如4个id,分别是1、2、4、5,如果按照上面的方法,那么取到id=4的概率是取得其他行概率的两倍。
为了做到严格随机,可以用下面流程:
取得整个表的行数C;
取得\(Y=floor(C*rand())\);
用
limit Y,1取一行。
我们把这个算法称为算法2,其执行语句为:
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;
MySQL处理limit Y,1时会按顺序一个一个读出来,丢掉前Y个后将下一个记录返回,因此需要扫描Y+1行,算上计算行数扫描的C行,总共扫描C+Y+1行,执行代价高于算法1,但小于order by rand()。
那么按照算法2的思路,随机取3个word值的做法为:
取得整个表的行数C;
根据相同的随机方法取得Y1,Y2,Y3;
执行三个
limit Y,1取得三行数据。
其执行语句为:
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;
最后总结:对于随机排序,尽量避免使用order by rand()。
MySQL 17 如何正确地显示随机消息?的更多相关文章
- MySQL 笔记整理(17) --如何正确地显示随机消息?
笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> (本篇内图片均来自丁奇老师的讲解,如有侵权,请联系我删除) 17) --如何正确地显示随机消息? 如果有这么一个英语单词表,需要每次 ...
- Mysql LIMIT如何正确对其进行优化
Mysql LIMIT如何正确对其进行优化 2010-05-17 17:09 佚名 博客园 字号:T | T 我们今天主要和大家分享的是Mysql LIMIT简单介绍以及如何进行优化的相关内容的描述, ...
- MySQL触发器如何正确使用
MySQL触发器如何正确使用 2010-05-18 15:58 佚名 博客园 字号:T | T 我们今天主要向大家介绍的是MySQL触发器进行正确使用,其中包括对MySQL触发器发器的语句创建,触发时 ...
- dmesg 显示内核消息
显示内核消息 dmesg [options] dmesg 可以用来显示存储在内核环缓冲区中的消息 系统启动时,内核会用硬件和模块初始化的相关消息填充其环缓冲区.内核环缓冲区中的消息常常用于诊断系统问题 ...
- SpringMVC——类型转换和格式化、数据校验、客户端显示错误消息
在介绍类型转换和格式化之前,我首先来介绍 <mvc:annotation-driven />. 需要导入的 schema: xmlns:mvc="http://www.sprin ...
- 卸载了mysql之后,mysql服务仍在,显示读取描述失败,错误代码2
卸载了mysql之后,mysql服务仍在,显示读取描述失败,错误代码2 用360软件管家,卸载mysql5.5,卸载了mysql之后,再依次删除 mysql的安装目录.c盘下的隐藏文件夹Program ...
- 利用PHP实现登录与注册功能以及使用PHP读取mysql数据库——以表格形式显示数据
登录界面 <body><form action="login1.php" method="post"><div>用户名:&l ...
- win10 64位 mysql安装过程出现status显示failed
mysql安装过程出现status显示failed,如下图: 由于我的电脑是64位系统,这里需要升级一个插件,即32位 visual C++ 2013 and visual C++ redistri ...
- 如何把mysql的列修改成行显示数据简单实现
如何把mysql的列修改成行显示数据简单实现 创建测试表: 1: DROP TABLE IF EXISTS `test`; 2: CREATE TABLE `test` ( 3: `year` int ...
- mysql_connect() 不支持 请检查 mysql 模块是否正确加载
php的扩展 没有配置好! 打开php.ini文件: 搜索pdo_mysql和curl ;extension=php_curl.dll ;extension=pdo_mysql.dll 然后把2者前面 ...
随机推荐
- MCP云托管最优解,揭秘国内最大MCP中文社区背后的运行时
作者:封崇 近期,中国第一AI开源社区魔搭(ModelScope)推出全新MCP广场,上架千余款热门的 MCP 服务.从当下火热的高德地图.网页抓取再到独家的支付宝,开发者/机构可以查看近1500种M ...
- .net6 中间件
参照资料: ASP.NET Core 中间件 | Microsoft Learn ASP.NET Core端点路由 作用原理 - 知乎 (zhihu.com) 一.概念 中间件是一种装配到应用管道以处 ...
- async/await Task.Delay 和Thread.Sleep的理解
async/await Task.Delay 和Thread.Sleep的理解 相关学习资料: 第十七节:从状态机的角度async和await的实现原理(新) - Yaopengfei - 博客园 ( ...
- 用DevEco Studio增量补丁修复功能,让鸿蒙应用的调试效率大增
在鸿蒙应用开发的快节奏赛道上,每一秒的开发效率提升都至关重要.如何更快地看到代码更改后的效果?如何尽可能缩短开发.调试和验证的周期?如何做到在某大厂180万行+项目中将代码修改即时生效?这些问题在De ...
- Numpy 的广播机制
广播机制在numpy中居于非常重要的位置,也是numpy高效计算的秘密武器,有必要进行深入彻底的理解,简而言之,它的规则如下: 规则1:如果两个数组在维度上不一样,那么维度低的数组用1(1个或者多个) ...
- Django-debug-toolbar配置流程及主要事项
配置流程 大概的配置流程官网上已经很清楚了,主要注意的有2点:(1)'JQUERY_URL'的配置(建议)(2)debug=True模式下的template必须包含closing的(必须).下面简要介 ...
- 万字长文: 仅花7天,利用AI编程神器Cursor 从0到1开发上线个人网站,保姆级教程!
大家好,我是狂师. 今天我们来分享一下,如何利用AI编程帮助我们开发一款个人定制网站,保姆级教程,篇符较长,建议先保存收藏. 这篇文章,将从0到1,讲解如何利用AI编程开发并上线一款个人网站产品,包括 ...
- 洛谷 P3386 【模板】二分图最大匹配
匈牙利算法博大精深,这里只记录步骤. 当然,不知道这些基础图论的童鞋请看这里(虽然也是草草概括一下谔谔谔) 主要步骤 \(main\) 主函数里面一个枚举现在正在匹配的左点 对于每个准备匹配的左点,进 ...
- MAC系统13.2,安装最新版logi options+,打开一直转圈
我联系官网客服,按照他给的步骤成功的安装了options+,你试试 请抽出时间按照下面列出的故障排除步骤尝试解决问题. 卸载我们所有的软件 删除剩余文件 步骤 1:打开 Finder,在菜单栏中选择& ...
- WSL学习笔记
WSL学习笔记 适用于 Linux 的 Windows 子系统 (WSL) 是 Windows 的一项功能,可用于在 Windows 计算机上运行 Linux 环境,而无需单独的虚拟机或双引导. WS ...