【问题标题】:quick selection of a random row from a large table in mysql从mysql中的大表中快速选择随机行
【发布时间】:2010-09-17 16:49:46
【问题描述】:

从大型mysql表中选择随机行的快速方法是什么?

我正在使用 php,但我对任何解决方案都感兴趣,即使它是另一种语言的。

【问题讨论】:

标签: mysql sql select random


【解决方案1】:

获取所有 id,从中随机选择一个,然后检索整行。

如果您知道 id 是连续的,没有孔,您可以获取最大值并计算随机 id。

如果这里和那里有漏洞但大部分是顺序值,并且您不关心稍微偏斜的随机性,请获取最大值,计算一个 id,然后选择 id 等于或高于那个的第一行你计算过。偏斜的原因是 id 跟随这样的洞比跟随另一个 id 的洞更有可能被选中。

如果您随机订购,您将面临糟糕的表格扫描,而 quick 这个词不适用于这样的解决方案。

不要这样做,也不应该按 GUID 订购,它有同样的问题。

【讨论】:

    【解决方案2】:

    我知道必须有一种方法可以在单个查询中快速完成。这里是:

    不涉及外部代码的快速方法,赞

    http://jan.kneschke.de/projects/mysql/order-by-rand/

    SELECT name
      FROM random AS r1 JOIN
           (SELECT (RAND() *
                         (SELECT MAX(id)
                            FROM random)) AS id)
            AS r2
     WHERE r1.id >= r2.id
     ORDER BY r1.id ASC
     LIMIT 1;
    

    【讨论】:

    • 请注意这里的权衡,为了确保在第一次尝试时获得结果,任何前面有空格的键都更有可能被选中。例如,给定两个键为 1 和 10 的记录,以 10 作为键的记录将在 90% 的时间内被选中。
    • 是的,如果键没有间隙并避免 WHERE 和 ORDER BY 子句,您可以获得更好的分布。检查文章,那里解释得很好。我不想偷走所有这些,因此没有提出其他查询,每个查询的优缺点。
    • 当您指定一些额外的参数(例如 WHERE r1.id >= r2.id AND r1.some_field=1 而 some_field 包含 data=1 时,此查询有时不会返回数据。关于如何解决这个问题的任何想法?
    【解决方案3】:

    MediaWiki 使用了一个有趣的技巧(用于 Wikipedia 的 Special:Random 功能):包含文章的表格有一个带有随机数的额外列(在创建文章时生成)。要获得一篇随机文章,请生成一个随机数,并在随机数列中获取具有下一个更大或更小(不记得是哪个)值的文章。使用索引,这可以非常快。 (而且 MediaWiki 是用 PHP 编写并为 MySQL 开发的。)

    如果结果数字分布不均,这种方法可能会导致问题; IIRC,这已在 MediaWiki 上修复,因此如果您决定这样做,您应该查看代码以了解它当前是如何完成的(可能他们会定期重新生成随机数列)。

    【讨论】:

    • 这是个好主意。是否有文章或其他资源对此进行了详细说明?
    • 这是个不错的主意,但我猜想 N 个期望的结果可能行不通。因为您可能得到的结果更少,或者顺序可能相同。
    • 这是个好主意。但是在查询中,我们仍然必须按随机列排序,对吧?假设随机列是random_number,那么查询就像:“SELECT * FROM mytable WHERE random_number>$rand ORDER BY random_number LIMIT 1”。是不是比 ORDER BY RAND() 快很多?
    • 您需要对当前条目数的最大随机数设置一定程度的限制。然后随着表的增长,将这个限制与表中的行数有一定程度的相关性。例如,当条目不多时。假设你有 3 个。对随机数没有限制,你可以说 2 个非常小的数字和一个大的数字。当最小值、本身和中间数之间的差距很小时,几乎不会调用 3 个中最小的那个。如果 min=0, max=100 有 3 个条目并且分配的 rand # 是 49, 50, 51?
    • 我不明白这个。这与仅在 1 和 max(id) 之间随机化一个数字并选择具有该 ID 的条目有何不同?为什么需要额外的列?
    【解决方案4】:

    这是一个运行相当快的解决方案,它获得了更好的随机分布,而不依赖于连续或从 1 开始的 id 值。

    SET @r := (SELECT ROUND(RAND() * (SELECT COUNT(*) FROM mytable)));
    SET @sql := CONCAT('SELECT * FROM mytable LIMIT ', @r, ', 1');
    PREPARE stmt1 FROM @sql;
    EXECUTE stmt1;
    

    【讨论】:

    • 如何使用 PHP 获取此 SQL 查询返回的行?将$query 设置为等于上述值,然后执行通常的mysql_query($query) 不会返回任何结果。谢谢。
    • 这是 1.5 次表扫描——COUNT(*) 的 1 次(假设 InnoDB),比 OFFSET @r 的完整扫描少。但它非常擅长随机而不依赖于 id 的属性。
    • @RickJames,对。另一种解决方案是用一个用序列整数填充的新列来枚举行。然后可以用 MAX() 而不是 COUNT() 得到最大的,然后通过索引选择它,而不用处理间隙。尽管该解决方案需要在行进出时重新编号。
    【解决方案5】:

    也许你可以这样做:

    SELECT * FROM table 
      WHERE id=
        (FLOOR(RAND() * 
               (SELECT COUNT(*) FROM table)
              )
        );
    

    这是假设你的身份证号码都是连续的,没有间隔。

    【讨论】:

    • 实际上你可能需要 CEIL 而不是 FLOOR,这取决于你的 ID 是从 0 开始还是从 1 开始
    • 假设表达式被缓存并且不会为每一行重新计算。
    • 主键有空格,因为有些行被删除了。
    【解决方案6】:

    为每一行添加一个包含计算出的随机值的列,并在排序子句中使用它,在选择时限制为一个结果。这比ORDER BY RANDOM() 引起的表扫描要快。

    更新:当然,在检索时发出SELECT 语句之前,您仍然需要计算一些随机值,例如

    SELECT * FROM `foo` WHERE `foo_rand` >= {some random value} LIMIT 1
    

    【讨论】:

    • 我想过。添加一个新的索引列并在创建行时为其分配一个随机整数。但问题是我存储了不必要的数据,你仍然需要做其他事情才能真正从中获取随机行,因为随机列数据是静态的。
    • 为什么这是-2,而Cesar B的却是+17?在我看来,它们几乎相同。
    • 应该是“SELECT * FROM foo WHERE foo_rand >= {一些随机值} ORDER BY foo_rand LIMIT 1”?
    • 如果您的 {some random value} 大于表中预先生成的最高随机数怎么办。您将返回一个空记录集。
    【解决方案7】:

    还有另一种方法来生成随机行,只使用一个查询而不用 rand() 排序。 它涉及用户定义的变量。 见how to produce random rows from a table

    【讨论】:

      【解决方案8】:

      为了从表中查找随机行,不要使用 ORDER BY RAND(),因为它会强制 MySQL 进行完整的文件排序,然后才检索所需的限制行数。为了避免这种完整的文件排序,请仅在 where 子句中使用 RAND() 函数。一旦达到所需的行数,它将立即停止。 看 http://www.rndblog.com/how-to-select-random-rows-in-mysql/

      【讨论】:

        【解决方案9】:

        如果不删除此表中的行,最有效的方法是:

        (如果你知道最小 id 就跳过它)

        SELECT MIN(id) AS minId, MAX(id) AS maxId FROM table WHERE 1
        
        $randId=mt_rand((int)$row['minId'], (int)$row['maxId']);
        
        SELECT id,name,... FROM table WHERE id=$randId LIMIT 1
        

        【讨论】:

          【解决方案10】:

          我在这里看到了很多解决方案。一两个似乎还可以,但其他解决方案有一些限制。但以下解决方案适用于所有情况

          select a.* from random_data a, (select max(id)*rand() randid  from random_data) b
               where a.id >= b.randid limit 1;
          

          这里,id,不需要是连续的。它可以是任何主键/唯一/自动增量列。请看以下Fastest way to select a random row from a big MySQL table

          谢谢 齐鲁尔 - www.techinfobest.com

          【讨论】:

            【解决方案11】:

            为了从给定表中选择多个随机行(比如“单词”),我们的团队想出了这个美:

            SELECT * FROM
            `words` AS r1 JOIN 
            (SELECT  MAX(`WordID`) as wid_c FROM `words`) as tmp1
            WHERE r1.WordID >= (SELECT (RAND() * tmp1.wid_c) AS id) LIMIT n
            

            【讨论】:

              【解决方案12】:

              经典的“SELECT id FROM table ORDER BY RAND() LIMIT 1”其实没问题。

              请参阅 MySQL 手册的以下摘录:

              如果将 LIMIT row_count 与 ORDER BY 结合使用,MySQL 会在找到排序结果的前 row_count 行后立即结束排序,而不是对整个结果进行排序。

              【讨论】:

              • 但它仍然必须为每条记录分配一个随机数,不是吗?我问是因为这种解释对我来说没有多大意义:如果整个结果集没有排序,它将如何返回前 N 个排序的行:S
              • @igelkott,还是有性能问题,估计不行
              【解决方案13】:

              使用命令你会做一个完整的扫描表。 最好是执行 select count(*) 并稍后在 0 和最后一个注册表之间获得一个随机 row=rownum

              【讨论】:

                【解决方案14】:

                一种简单但缓慢的方法是(适用于小桌子)

                SELECT * from TABLE order by RAND() LIMIT 1
                

                【讨论】:

                • 这将为表中的所有行产生一个随机值,排序,然后抓取一行。这并不快。
                • 是的。不过,它的开发时间很快。 (并在回答时间:-))。我会把它留给可能需要它的非大表用户
                • "smallish" 可能非常小(我在虚拟主机上遇到了 20k 条目表的问题),跟踪此类问题可能是一个royal 背部疼痛。帮自己一个忙,从一开始就使用适当的算法。
                • 这将导致大型表的性能大幅下降。检查这个类似的问题stackoverflow.com/questions/1244555/…
                【解决方案15】:

                在伪代码中:

                sql "select id from table"
                store result in list
                n = random(size of list)
                sql "select * from table where id=" + list[n]
                

                这假定id 是唯一的(主)键。

                【讨论】:

                • 如果 ID 不经常更改,您甚至可以将 ID 列表保存在内存中以加快速度。
                • 如果有十亿行怎么办?这意味着您的列表变量很大。
                【解决方案16】:

                看看 Jan Kneschke 的 this linkthis SO answer,因为他们都在讨论同一个问题。 SO的答案也涵盖了各种选项,并根据您的需要提供了一些很好的建议。 Jan 回顾了所有不同的选项以及每个选项的性能特征。他最终得出了以下在 MySQL 选择中执行此操作的最优化方法:

                SELECT name
                  FROM random AS r1 JOIN
                       (SELECT (RAND() *
                                     (SELECT MAX(id)
                                        FROM random)) AS id)
                        AS r2
                 WHERE r1.id >= r2.id
                 ORDER BY r1.id ASC
                 LIMIT 1;
                

                HTH,

                -地平

                【讨论】:

                  【解决方案17】:

                  我对 SQL 有点陌生,但是如何在 PHP 中生成一个随机数并使用

                  SELECT * FROM the_table WHERE primary_key >= $randNr
                  

                  这并不能解决桌子上有洞的问题。

                  但这里对 lassevks 的建议有所不同:

                  SELECT primary_key FROM the_table
                  

                  在PHP中使用mysql_num_rows()根据上面的结果创建一个随机数:

                  SELECT * FROM the_table WHERE primary_key = rand_number
                  

                  顺便说一句,SELECT * FROM the_table 有多慢:
                  根据mysql_num_rows() 创建一个随机数,然后将数据指针移动到该点mysql_data_seek()。在拥有一百万行的大表上,这会有多慢?

                  【讨论】:

                    【解决方案18】:

                    我遇到了我的 ID 不连续的问题。我想出了这个。

                    SELECT * FROM products WHERE RAND()<=(5/(SELECT COUNT(*) FROM products)) LIMIT 1
                    

                    返回的行数约为 5,但我将其限制为 1。

                    如果您想添加另一个 WHERE 子句,它会变得更有趣。假设您要搜索打折产品。

                    SELECT * FROM products WHERE RAND()<=(100/(SELECT COUNT(*) FROM pt_products)) AND discount<.2 LIMIT 1
                    

                    您需要做的是确保返回足够的结果,这就是我将其设置为 100 的原因。在子查询中使用 WHERE discount<.2>

                    【讨论】:

                      【解决方案19】:

                      使用以下查询获取随机行

                      SELECT user_firstname ,
                      COUNT(DISTINCT usr_fk_id) cnt
                      FROM userdetails 
                      GROUP BY usr_fk_id 
                      ORDER BY cnt ASC  
                      LIMIT 1
                      

                      【讨论】:

                        【解决方案20】:

                        在我的情况下,我的表有一个 id 作为主键,自动递增,没有间隙,所以我可以使用 COUNT(*)MAX(id) 来获取行数。

                        我做了这个脚本来测试最快的操作:

                        logTime();
                        query("SELECT COUNT(id) FROM tbl");
                        logTime();
                        query("SELECT MAX(id) FROM tbl");
                        logTime();
                        query("SELECT id FROM tbl ORDER BY id DESC LIMIT 1");
                        logTime();
                        

                        结果是:

                        • 计数:36.8418693542479 ms
                        • 最大:0.241041183472 ms
                        • 订购0.216960906982 ms

                        用下单方式回答:

                        SELECT FLOOR(RAND() * (
                            SELECT id FROM tbl ORDER BY id DESC LIMIT 1
                        )) n FROM tbl LIMIT 1
                        
                        ...
                        SELECT * FROM tbl WHERE id = $result;
                        

                        【讨论】:

                          【解决方案21】:

                          我已经使用了这个并且工作完成了 来自here的参考

                          SELECT * FROM myTable WHERE RAND()<(SELECT ((30/COUNT(*))*10) FROM myTable) ORDER BY RAND() LIMIT 30;
                          

                          【讨论】:

                            【解决方案22】:

                            创建一个函数来做这个很可能是最好的答案和最快的答案!

                            优点 - 即使有间隙也能工作,而且速度极快。

                            <?
                            
                            $sqlConnect = mysqli_connect('localhost','username','password','database');
                            
                            function rando($data,$find,$max = '0'){
                               global $sqlConnect; // Set as mysqli connection variable, fetches variable outside of function set as GLOBAL
                               if($data == 's1'){
                                 $query = mysqli_query($sqlConnect, "SELECT * FROM `yourtable` ORDER BY `id` DESC LIMIT {$find},1");
                            
                                 $fetched_data = mysqli_fetch_assoc($query);
                                  if(mysqli_num_rows($fetched_data>0){
                                   return $fetch_$data;
                                  }else{
                                   rando('','',$max); // Start Over the results returned nothing
                                  }
                               }else{
                                 if($max != '0'){
                                    $irand = rand(0,$max); 
                                    rando('s1',$irand,$max); // Start rando with new random ID to fetch
                                 }else{
                            
                                    $query = mysqli_query($sqlConnect, "SELECT `id` FROM `yourtable` ORDER BY `id` DESC LIMIT 0,1");
                                    $fetched_data = mysqli_fetch_assoc($query);
                                    $max = $fetched_data['id'];
                                    $irand = rand(1,$max);
                                    rando('s1',$irand,$max); // Runs rando against the random ID we have selected if data exist will return
                                 }
                               }
                             }
                            
                             $your_data = rando(); // Returns listing data for a random entry as a ASSOC ARRAY
                            ?>
                            

                            请记住,此代码未经测试,但它是一个可行的概念,即使有间隙也会返回随机条目。只要间隙不会大到导致加载时间问题。

                            【讨论】:

                              【解决方案23】:

                              又快又脏的方法:

                              SET @COUNTER=SELECT COUNT(*) FROM your_table;
                              
                              SELECT PrimaryKey
                              FROM your_table
                              LIMIT 1 OFFSET (RAND() * @COUNTER);
                              

                              对于 MyISAM 表,第一个查询的复杂度是 O(1)。

                              第二个查询伴随着表全扫描。复杂度 = O(n)

                              又脏又快的方法:

                              保留一个单独的表格仅用于此目的。每当插入原始表时,您还应该向该表插入相同的行。假设:没有删除。

                              CREATE TABLE Aux(
                                MyPK INT AUTO_INCREMENT,
                                PrimaryKey INT
                              );
                              
                              SET @MaxPK = (SELECT MAX(MyPK) FROM Aux);
                              SET @RandPK = CAST(RANDOM() * @MaxPK, INT)
                              SET @PrimaryKey = (SELECT PrimaryKey FROM Aux WHERE MyPK = @RandPK);
                              

                              如果允许删除,

                              SET @delta = CAST(@RandPK/10, INT);
                              
                              SET @PrimaryKey = (SELECT PrimaryKey
                                                 FROM Aux
                                                 WHERE MyPK BETWEEN @RandPK - @delta AND @RandPK + @delta
                                                 LIMIT 1);
                              

                              总体复杂度为 O(1)。

                              【讨论】:

                                【解决方案24】:

                                SELECT DISTINCT * FROM yourTable WHERE 4 = 4 LIMIT 1;

                                【讨论】:

                                  猜你喜欢
                                  • 2011-05-18
                                  • 1970-01-01
                                  • 2011-07-14
                                  • 2016-04-27
                                  • 1970-01-01
                                  相关资源
                                  最近更新 更多