如何随机获取表中几行记录

内存临时表

用 order by rand() 来实现这个逻辑。

explain SELECT NAME FROM t_user ORDER BY rand() LIMIT 2;

这个语句的意思很直白,随机排序取前 3 个。虽然这个 SQL 语句写法很简单,但执行流程却有点复杂的。
在这里插入图片描述
可以看到Extra字段中显示Using temporary 表示使用了临时表,Using filesort,表示的是需要执行排序操作。 使用了排序操作。

内存临时表上进行了排序操作。

整个操作流程:

  1. 创建一个临时表, 表里有两个字段,第一个字段是 double 类型,为了后面描述方便,记为字段 R,第二个字段是 varchar(64) 类型,记为字段 W。并且,这个表没有建索引。
  2. 从t_user表中取出所有的name字段放入临时表中, 并每条记录使用rand()函数生成一个随机数, 并把随机数赋值给R字段
  3. 把临时表中的R和W字段的值放入sort_buffer中根据R字段进行排序,
  4. 排序完成后, 取出全面三个记录, 把W字段的值返回给客户端,

可以看到上面的整个流程 会把全表扫描两次,第一次是把所有记录扫描出来放入临时表, 第二次把临时把的数据扫描出来放入到sort_buffer中。 代价还是很高的。

磁盘临时表

tmp_table_size 值的设置可以限制内存临时表的大小, 默认16M。 如果内存临时表的大小超出了这个限制就会使用磁盘临时表。

上面的语句中 获取到的只是前面的2个记录, 如果sort_buffer不够用了,会使用临时文件,使用临时文件了就会使用归并排序算法 ,但是只是去前面2行记录,使用归并排序并不必要, mysql会进行优化 使用了优先队列排序算法。

优先队列排序算法其实就是选择排序中的堆排序, 但是它不会构建整个堆, 只会构建包含2个节点的堆, 然后把后面的数据以此和这个堆进行比较,如果小于对堆中节点的值就替换掉这个节点,这样比较完成后这个堆就是符合要求的最小的两行记录了。 使用的内存也会很小。

但是如果我们的limit后面的值比较大, sort_buffer装不下所有的排序行,还是会使用归并排序的。 使用优先排序的前提是sort_buffer够用。

如何高效的获取随机行数

方法一

  1. 先取得最大id值Max和最小id值Min .
  2. 使用应用程序获取Max和Min中间的一个随机数 R
  3. 使用 select * from table where id >= R limit 3

当然我这个流程使用到了应用程序,可能会出现问题, 如果在取随机数R的时候表的最大最小id改变了,后面查询的时候可能会查出0条。
解决办法就是三步操作都放到一个事务中, 由于RR的隔离级别会没有问题的, 也可以三步操作都使用sql语句来完成, sql也是一个很强大的编程语言的。

由于表记录中间有空洞, 比如表中三条记录的id值为 1, 2, 10000000. 使用上面的方法一, 明显的Id为10000000的概率大多了。

方法二

  1. 取得表的总行数C
  2. 生成随机数Y, 这个Y的范围从 [ 0, C )
  3. 在使用 select * from table limit Y, 3 获取随机的三行

这个方法二解决了概率不均衡的问题。 但是这个方法第一步会扫描表的总行数, 第三步会扫描Y+1行, 总计扫描了C+Y+1行。 代价比方法一要搞很多。 但是代价还是比使用Order By Rand() 的代价低多了。

方法一和二的问题

方法一在数据的id连续的情况下是没有问题的, 并且执行效率很高的, 这个需要根据你的业务数据的id情形来判断是否使用了。 如果id已经无法修改了,它本身就是不均匀的, 这个使用我们可以专门创建一个排序字段, 自己设置里面的值是连续的, 在使用这个排序字段来进行方法一操作。

方法二的代价比较大一点,但是为通用解决方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值