MySQL随机查询性能分析

这周遇到一个需求,是要根据若干条件,随机的选取一些数据,本来这个问题还是比较简单的,可以使用编程语言自带的随机函数,对查询出来的数据集再进行,随机选取,但是大家都知道,如果在数据表的数据量上来了后,这种方法显然就是非常不靠谱的,本文我就来聊一聊通过MySQL数据库查询随机行数据。

方法

首先让我们来列举一下,可能使用的若干方法。

Order by Rand()

这个方法,几个方案中最慢的一个,但是我们还是把它列举了出来,没有比较就没有伤害,所以一定要有一个速度慢的衬托才行。它之所以慢,是因为 在order by 子句后的 rand() 函数会先为每一行数据生成一个 1~0之间的随机数,然后在根据这个数字,进行排序再选出最小的N行数据(N取决于limit N)。

mysql> explain select * from users order by RAND() limit 1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: users
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 8
        Extra: Using temporary; Using filesort
1 row in set (0.00 sec)

优点: 简单易记,可以任意选取若干数据行,可以使用条件限定结果集。

缺点: 该语句的执行速度取决于,数据量的多少,一般来说超过万行的数据就不推荐使用这种方式了。

Rand() 改进方法1

上面使用 order by rand() 方法,我们说了它的性能非常差,这个方法就是对它的改进,同样是使用rand() 函数不过这次我们把,它用在 where条件中。

SELECT id FROM users, (SELECT ((1/COUNT(*))*100) as n FROM users) as x WHERE RAND()<=x.n LIMIT 1;

上面的方法,首先使用了一个子查询,计算出你想要随机出的记录所在总记录的百分比,然后再乘上100(防止比例过小)再使用这个小数,去和随机数比较,取出小于或等于这个小数的记录。举个例子 你想从一百万条记录中随机取10条记录,那么算式就是 10/1_000_000 * 100 = 0.001 查询语句就是:

SELECT id FROM users WHERE RAND()<=0.001 LIMIT 10;
mysql> explain SELECT id FROM users, (SELECT ((1/COUNT(*))*100) as n FROM users) as x WHERE RAND()<=x.n LIMIT 1\G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: <derived2>
         type: system
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: PRIMARY
        table: users
         type: index
possible_keys: NULL
          key: index_users_on_user_key
      key_len: 767
          ref: NULL
         rows: 210220
        Extra: Using where; Using index
*************************** 3. row ***************************
           id: 2
  select_type: DERIVED
        table: users
         type: index
possible_keys: NULL
          key: index_users_on_user_key
      key_len: 767
          ref: NULL
         rows: 210220
        Extra: Using index

优点: 速度尚可,可以用于主键非连续的表中,可以容易的使用Limit和Where语句限定随机结果集的大小和条件。

缺点: 需要子查询统计总记录数(数据量大可能比较耗时),随机性不好 末尾的记录的随机比例远低于其他记录,对于随机分布要求比较高的场景,就不太适合了。

Rand() 改进方法2

改进方法1中达到了快速数据的目的,但是它的随机性不好,那么改进方法2就是使用一定的性能去换取随机分布率。

SELECT id FROM users, (SELECT ((1/COUNT(*))*100) as n FROM users) as x WHERE RAND()<=x.n ORDER BY RAND() LIMIT 1;

只需要再主查询语句中加入 order by rand()就可以达到随机分布率的提升。

优点:改进方法1的有点一样,并且随机分布更好。

缺点: 因为使用了order by rand() 所以该语句的执行速度取决于,数据量的多少。

Inner join

SELECT * FROM users as u JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM users)) AS id ) AS u2 WHERE u.id >= u2.id ORDER BY u.id DESC LIMIT 1;

该方法巧妙的使用了自增长的ID主键,取其最大值,然后再乘上随机函数 的到一个 随机的ID,这样你就可以根据想要得到的随机记录数,决定使用 >= 或是 = 运算符去筛选结果了( = 仅用于随机一条记录的情况)。

优点: 速度非常快。

缺点: 查询语句稍微有些复杂,被查询的表必须是连续自增的主键表,例如(1,2,3....N) 不能是 (1,3,8,22) 因为根据最大ID随机出来的不确定ID可能不存在对应的记录,并且无法使用条件去筛选随机结果集。

性能比较

我们使用下面的表结构,去分别创建(10K,25K,50K,100K,250K,500K,1000K)的数据,然后分别使用上面的几种方案进行查询,看看他们的性能如何。

+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| nickname   | varchar(191) | YES  |     | NULL    |                |
| avatar_url | varchar(255) | YES  |     | NULL    |                |
| uid        | int(11)      | YES  |     | NULL    |                |
| user_key   | varchar(36)  | YES  | UNI | NULL    |                |
| channel_id | varchar(36)  | NO   | UNI | NULL    |                |
| created_at | datetime     | NO   |     | NULL    |                |
| updated_at | datetime     | NO   |     | NULL    |                |
| user_type  | smallint(6)  | YES  |     | 1       |                |
| cellphone  | varchar(50)  | YES  | MUL | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
benchmark

可以从上图看出第一种方法和其他三种有非常显著的性能差别,方法3和方法2也在一百万的数据量上开始拉开距离了,inner join 方法即使在一百万的数据量上,也是非常快速的。

总结

综上所述,除了第一种方法外,其他的三种方式都可以根据你,具体的使用场景,包括数据量,想要获取的记录数是否有限定条件,去决定使用哪种方式。

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

推荐阅读更多精彩内容