『MySQL 实战 45 讲』17 - 如何正确地显示随机消息?(随机抽取 3 个词)

news/2024/12/4 17:17:48/

如何正确地显示随机消息?(随机抽取 3 个词)

  1. 需求:从用户的英语单词表中,随机选择三个单词,创表和插入数据如下:
# 建表
CREATE TABLE `words` (`id` INT(11) NOT NULL AUTO_INCREMENT,`word` VARCHAR(64) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=INNODB;
# 插入数据
DELIMITER ;;
CREATE PROCEDURE idata()
BEGINDECLARE i INT;SET i=0;WHILE i<10000 DOINSERT 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))));SET i=i+1;END WHILE;
END;;
DELIMITER ;CALL idata();

内存临时表

  1. 实现的逻辑可以这样
# 每行随机一个数字之后,排序
select word from words order by rand() limit 3;
  1. 通过 explain 查看语句的执行情况
  • Using temporary:表示用了临时表,临时表分内存临时表和磁盘临时表,看实际数据量才知道有没有变为磁盘临时表
  • Using filesort:表示的是需要执行排序操作
    在这里插入图片描述
  1. 对于内存临时表(使用 Memory 引擎,这时候的数据量能够存在内存临时表下,tmp_table_size 默认 16M,超过了才会转为磁盘临时表),回表过程只是简单地根据数据行的位置,直接访问内存得到数据,根本不会导致多访问磁盘,MySQL 就会选择 rowid 排序
    在这里插入图片描述
  • 创建一个临时表。这个临时表使用的是 memory 引擎,表里有两个字段,第一个字段是 double 类型,记为字段 R,第二个字段是 varchar(64) 类型,记为字段 W。并且,这个表没有建索引
  • 从 words 表中,按主键顺序取出所有的 word 值。对于每一个 word 值,调用 rand() 函数生成一个大于 0 小于 1 的随机小数,并把这个随机小数和 word 分别存入临时表的 R 和 W 字段中,到此,扫描行数是 10000
  • 现在临时表有 10000 行数据了,接下来在这个没有索引的内存临时表上,按照字段 R 排序
  • 初始化 sort_buffer。sort_buffer 中有两个字段,一个是 double 类型,另一个是整型
  • 从内存临时表中一行一行地取出 R 值和 pos 位置信息(MEMORY 引擎不是索引组织表,这个 pos 即 rowid,其实是数组下标)
  • 分别存入 sort_buffer 中的两个字段里。这个过程要对内存临时表做全表扫描,此时扫描行数增加 10000,变成了 20000
  • 在 sort_buffer 中根据 R 的值进行排序。注意,这个过程没有涉及到表操作,所以不会增加扫描行数
  • 排序完成后,取出前三个结果的位置信息,依次到内存临时表中取出 word 值,返回给客户端。这个过程中,访问了表的三行数据,总扫描行数变成了 20003
  1. 使用 slow.log 验证扫描到的行数
# 查看慢日志存储位置
show variables like '%slow%'; 
# 开启慢查询
set global slow_query_log=ON;
# 记录了包含所有执行时间超过参数 long_query_time 的 sql
set long_query_time=0;select word from words order by rand() limit 3;

在这里插入图片描述
5. 小结:order by rand() 使用了内存临时表,内存临时表排序的时候使用了 rowid 排序方法

磁盘临时表

  1. tmp_table_size 限制了内存临时表的大小,默认值是 16M。如果临时表大小超过了 tmp_table_size,那么内存临时表就会转成磁盘临时表
  2. 磁盘临时表默认为 InnoDB 存储引擎,由 internal_tmp_disk_storage_engine 控制
  3. 复现场景,使用下面的参数
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

在这里插入图片描述
4. 分析

  • 这里的 sort_mode 现实 rowid 排序
  • num_examined_rows 总行数 1w,R 字段 8 字节,rowid 字段 6 字节,合计 14w 字节,应该超过 sort_buffer_size 的 32768 字节,但是 num_initial_chunks_spilled_to_disk 却为 0?
  1. 原因
  • 使用了优先队列排序算法,其实只要取 R 的最小的 3 个 rowid
    在这里插入图片描述

  • 对于这 10000 个准备排序的 (R,rowid),先取前三行,构造成一个堆

  • 取下一个行 (R’,rowid’),跟当前堆里面最大的 R 比较,如果 R’小于 R,把这个 (R,rowid) 从堆中去掉,换成 (R’,rowid’)

  • 重复第 2 步,直到第 10000 个 (R’,rowid’) 完成比较

随机排序选择方法

  1. 方案一
  • 假设随机选择 1 个值,后面重复 3 次
  • 取得这个表的主键 id 的最大值 M 和最小值 N
  • 用随机函数生成一个最大值到最小值之间的数 X = (M-N)*rand() + N
  • 取不小于 X 的第一个 ID 的行
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;

在这里插入图片描述

  • 缺点
    • 对于 id 数据不均匀,例如 1、2、40000、40001,那么取到 40000 的几率就很大
  1. 方案二
  • 取得整个表的行数,并记为 C
  • 取得 Y = floor(C * rand())。 floor 函数在这里的作用,就是取整数部分
  • 再用 limit Y,1 取得一行
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 行,执行代价比随机算法一的代价要高

http://www.ppmy.cn/news/81446.html

相关文章

LogicalDOC安装

安装7.x的版本的时候&#xff0c;页面新建文件夹&#xff0c;如果不支持中文&#xff0c;总是乱码&#xff0c;调试了很多&#xff0c;查了很多&#xff0c;发现他需要下载单独中文支持&#xff0c;而且还支持的不是特别好&#xff0c;索性用8&#xff0c;搭配jdk11,测试几个后…

Flutter学习之旅 -网格布局

文章目录 GridView列表三种形式常用属性 小案例 GridView列表三种形式 可以通过GridView.count实现网格布局 /* 格式: GridView.count(crossAxisCount: 一行显示数量,children: [component(),...],) */ class MyHomePage extends StatelessWidget {const MyHomePage({Key? k…

Java+vue生成报纸排版新闻页面

您可以按照以下步骤使用JavaVue实现生成报纸排版的新闻页面&#xff1a; 准备后端环境&#xff1a;Java Servlet或Spring Boot框架&#xff1b;根据需要定义Sql数据库表、字段、实体类等相关信息&#xff1b;使用Mybatis或Hibernate框架实现数据库访问操作&#xff1b;定义前端…

Redis缓存穿透、击穿、雪崩问题及其解决方法

Redis缓存穿透、击穿、雪崩问题及其解决方法 1 缓存穿透1.1 概念及其解决思路1.2 编码解决商品查询的缓存穿透问题&#xff1a; 2 缓存雪崩问题及解决思路3 缓存击穿问题及解决思路3.1 利用互斥锁解决缓存击穿问题3.2 利用逻辑过期解决缓存击穿问题 1 缓存穿透 1.1 概念及其解…

Hadoop集群常见错误

&#xff08;一&#xff09;启动hadoop集群时易出现的错误&#xff1a; 1. 错误现象&#xff1a;java.net.NoRouteToHostException: No route to host. 原因&#xff1a;master服务器上的防火墙没有关闭。 解决方法: 在master上关闭防火墙: chkconfig iptables off. 2. …

Python学习笔记——《吴恩达Machine Learning》线性回归例程

文章目录 案例背景线性回归&#xff08;Loss Regression&#xff09;梯度下降法&#xff08;批量梯度下降算法——batch gradient descent&#xff09;计算成本函数和梯度下降使用线性回归拟合训练数据模型预测 梯度下降效果可视化完整版demo 案例背景 详情参照吴恩达机器学习…

小程序设计与用户体验(上)

本章主要介绍小程序的设计、交互和用户体验。学习者将会学到&#xff1a; 小程序设计的基本原则设计小程序的用户交互、动画和视觉效果提高小程序的用户体验 学习本章内容可使学生对小程序的设计理念有更深入的掌握&#xff0c;并具备制定用户体验方案的能力。 小程序设计的…

代码随想录算法训练营15期 Day 2 | 977.有序数组的平方 、209.长度最小的子数组 、59.螺旋矩阵II 、总结

977.有序数组的平方 题目建议&#xff1a; 本题关键在于理解双指针思想 题目链接&#xff1a;力扣 思路一&#xff1a;暴力解算&#xff0c;直接将所有元素变成一个平方&#xff0c;然后进行排序。 class Solution { public:vector<int> sortedSquares(vector<int&g…