MySQL随机查询性能分析

作者: falm | 来源:发表于2017-01-15 19:03 被阅读571次

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

    总结

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

    相关文章

      网友评论

      • hhuua:Inner join这种方法 在删除数据之后会出现问题的
      • Bger:发现在带上查询条件之后. Inner join这种方法的性能损耗特别明显... 直接GG掉了.
        falm:@Bger 不清楚会有这种情况,能否贴一下你的SQL ,还有带条件的随机查询记录,使用inner join 这个方法查不出可靠的结果。

      本文标题:MySQL随机查询性能分析

      本文链接:https://www.haomeiwen.com/subject/tjfubttx.html