选择随机行PostgreSQL的最佳方法

时间:2011-12-29 23:30:11

标签: sql performance postgresql random

我想在PostgreSQL中随机选择一行,我试过这个:

select * from table where random() < 0.01;

但其他一些人建议:

select * from table order by random() limit 1000;

我有一个非常大的表,有5亿行,我希望它快。

哪种方法更好?有什么区别?选择随机行的最佳方法是什么?

12 个答案:

答案 0 :(得分:188)

根据您的规格(以及评论中的其他信息),

  • 您有一个数字ID列(整数),只有很少(或中等数量)间隙。
  • 显然没有或几乎没有写操作。
  • 您的ID列必须编入索引!主键很好用。

下面的查询不需要对大表进行顺序扫描,只需要进行索引扫描。

首先,获取主要查询的估计值:

SELECT count(*) AS ct              -- optional
     , min(id)  AS min_id
     , max(id)  AS max_id
     , max(id) - min(id) AS id_span
FROM   big;

唯一可能很昂贵的部分是count(*)(对于巨大的表格)。鉴于上述规格,您不需要它。估算会很好,几乎免费提供(detailed explanation here):

SELECT reltuples AS ct FROM pg_class WHERE oid = 'schema_name.big'::regclass;

只要ct 小于id_span,查询就会胜过其他方法。

WITH params AS (
    SELECT 1       AS min_id           -- minimum id <= current min id
         , 5100000 AS id_span          -- rounded up. (max_id - min_id + buffer)
    )
SELECT *
FROM  (
    SELECT p.min_id + trunc(random() * p.id_span)::integer AS id
    FROM   params p
          ,generate_series(1, 1100) g  -- 1000 + buffer
    GROUP  BY 1                        -- trim duplicates
    ) r
JOIN   big USING (id)
LIMIT  1000;                           -- trim surplus
  • id空间中生成随机数。你有“几个空白”,所以要添加10%(足以轻松覆盖空白)到要检索的行数。

  • 每个id都可以偶然挑选多次(虽然很可能是一个很大的ID空间),所以将生成的数字分组(或使用DISTINCT)。

    < / LI>
  • id加入大表。在索引到位时,这应该非常快。

  • 最后修剪没有被欺骗和间隙吃掉的剩余id。每一行都有一个完全相同的机会

短版

您可以简化此查询。上述查询中的CTE仅用于教育目的:

SELECT *
FROM  (
    SELECT DISTINCT 1 + trunc(random() * 5100000)::integer AS id
    FROM   generate_series(1, 1100) g
    ) r
JOIN   big USING (id)
LIMIT  1000;

使用rCTE

进行优化

特别是如果您对差距和估计不太确定。

WITH RECURSIVE random_pick AS (
   SELECT *
   FROM  (
      SELECT 1 + trunc(random() * 5100000)::int AS id
      FROM   generate_series(1, 1030)  -- 1000 + few percent - adapt to your needs
      LIMIT  1030                      -- hint for query planner
      ) r
   JOIN   big b USING (id)             -- eliminate miss

   UNION                               -- eliminate dupe
   SELECT b.*
   FROM  (
      SELECT 1 + trunc(random() * 5100000)::int AS id
      FROM   random_pick r             -- plus 3 percent - adapt to your needs
      LIMIT  999                       -- less than 1000, hint for query planner
      ) r
   JOIN   big b USING (id)             -- eliminate miss
   )
SELECT *
FROM   random_pick
LIMIT  1000;  -- actual limit

我们可以在基本查询中使用 更小的剩余 。如果存在太多间隙,因此我们在第一次迭代中找不到足够的行,则rCTE继续使用递归项进行迭代。我们仍然需要ID空间中相对间隙,或者在达到限制之前递归可能会干涸 - 或者我们必须从足够大的缓冲区开始,这无法达到优化性能的目的。

rCTE中的UNION消除了重复项。

外部LIMIT会在我们有足够的行时立即停止CTE。

仔细草拟此查询以使用可用索引,生成实际随机行并且在我们满足限制之前不会停止(除非递归运行干燥)。如果要重写它,可能存在许多陷阱。

包装成功能

对于不同参数的重复使用:

CREATE OR REPLACE FUNCTION f_random_sample(_limit int = 1000, _gaps real = 1.03)
  RETURNS SETOF big AS
$func$
DECLARE
   _surplus  int := _limit * _gaps;
   _estimate int := (           -- get current estimate from system
      SELECT c.reltuples * _gaps
      FROM   pg_class c
      WHERE  c.oid = 'big'::regclass);
BEGIN

   RETURN QUERY
   WITH RECURSIVE random_pick AS (
      SELECT *
      FROM  (
         SELECT 1 + trunc(random() * _estimate)::int
         FROM   generate_series(1, _surplus) g
         LIMIT  _surplus           -- hint for query planner
         ) r (id)
      JOIN   big USING (id)        -- eliminate misses

      UNION                        -- eliminate dupes
      SELECT *
      FROM  (
         SELECT 1 + trunc(random() * _estimate)::int
         FROM   random_pick        -- just to make it recursive
         LIMIT  _limit             -- hint for query planner
         ) r (id)
      JOIN   big USING (id)        -- eliminate misses
   )
   SELECT *
   FROM   random_pick
   LIMIT  _limit;
END
$func$  LANGUAGE plpgsql VOLATILE ROWS 1000;

呼叫:

SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);

你甚至可以使这个泛型适用于任何表:将PK列和表的名称作为多态类型并使用EXECUTE ......但这超出了这个问题的范围。参见:

可能的替代

如果您的要求允许重复来电的相同的设置(我们正在谈论重复的通话),我会考虑具体化视图。执行上面的查询一次并将结果写入表。用户以闪电般的速度进行准随机选择。按照您选择的间隔或事件刷新随机选择。

Postgres 9.5介绍TABLESAMPLE SYSTEM (n)

n 的百分比。 The manual:

  

BERNOULLISYSTEM抽样方法均接受单一   参数,即要采样的表的分数,表示为a    0到100之间的百分比。该参数可以是任何real - 值表达式。

大胆强调我的。它非常快,但结果是不完全随机。再次手册:

  

SYSTEM方法明显快于BERNOULLI方法   当指定小的采样百分比时,它可能返回a   由于聚类效应而导致表的随机样本较少。

返回的行数可能会有很大差异。对于我们的示例,要获得大致 1000行:

SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);

相关:

安装附加模块tsm_system_rows以准确获取所请求的行数(如果有足够数量)并允许更方便的语法:

SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);

有关详细信息,请参阅Evan's answer

但这仍然不是随机的。

答案 1 :(得分:87)

您可以使用

检查和比较两者的执行计划
EXPLAIN select * from table where random() < 0.01;
EXPLAIN select * from table order by random() limit 1000;

对大表 1 的快速测试表明,ORDER BY首先对整个表进行排序,然后选择前1000个项目。对大表进行排序不仅可以读取该表,还可以读取和写入临时文件。 where random() < 0.1仅扫描整个表格一次。

对于大型表,这可能不是您想要的,因为即使一次完整的表扫描也可能需要很长时间。

第三个提案是

select * from table where random() < 0.01 limit 1000;

只要找到1000行就会停止表扫描,因此会更快返回。当然,这会使随机性稍微降低,但在你的情况下这或许就足够了。

编辑:除了这些注意事项,您可以查看已经提出的问题。使用查询[postgresql] random会返回相当多的点击次数。

还有一篇链接文章,其中列出了几种方法:


1 “大”,如“完整的表格不适合记忆”。

答案 2 :(得分:67)

postgresql order by random(),按随机顺序选择行:

select your_columns from your_table ORDER BY random()

postgresql以random()顺序排列:

select * from 
  (select distinct your_columns from your_table) table_alias
ORDER BY random()

postgresql命令随机限制一行:

select your_columns from your_table ORDER BY random() limit 1

答案 3 :(得分:33)

从PostgreSQL 9.5开始,有一种新的语法专用于从表中获取随机元素:

SELECT * FROM mytable TABLESAMPLE SYSTEM (5);

此示例将为您提供mytable中5%的元素。

在此博客文章中查看更多解释:http://www.postgresql.org/docs/current/static/sql-select.html

答案 4 :(得分:25)

ORDER BY的那个将会变慢。

select * from table where random() < 0.01;按记录记录,并决定是否随机过滤。这将是O(N),因为它只需要检查一次记录。

select * from table order by random() limit 1000;将对整个表格进行排序,然后选择前1000个。除了幕后任何伏都教魔法之外,顺序为O(N * log N)

random() < 0.01的缺点是你会获得可变数量的输出记录。


注意,有一种更好的方法来混洗一组数据,而不是随机排序:The Fisher-Yates Shuffle,它在O(N)中运行。然而,在SQL中实现shuffle听起来就像是一个挑战。

答案 5 :(得分:13)

这是一个适合我的决定。我想这很容易理解和执行。

SELECT 
  field_1, 
  field_2, 
  field_2, 
  random() as ordering
FROM 
  big_table
WHERE 
  some_conditions
ORDER BY
  ordering 
LIMIT 1000;

答案 6 :(得分:9)

select * from table order by random() limit 1000;

如果您知道自己想要多少行,请查看tsm_system_rows

tsm_system_rows

  

模块提供表采样方法SYSTEM_ROWS,可以在SELECT命令的TABLESAMPLE子句中使用。

     

此表抽样方法接受一个整数参数,该参数是要读取的最大行数。生成的样本将始终包含那么多行,除非表中没有足够的行,在这种情况下,将选择整个表。 与内置的SYSTEM采样方法类似,SYSTEM_ROWS执行块级采样,因此样本不是完全随机的,但可能会受到聚类效应的影响,特别是如果只请求少量行。

首先安装扩展程序

CREATE EXTENSION tsm_system_rows;

然后你的查询,

SELECT *
FROM table
TABLESAMPLE SYSTEM_ROWS(1000);

答案 7 :(得分:6)

如果您只想要一行,则可以使用从offset派生的计算count

select * from table_name limit 1
offset floor(random() * (select count(*) from table_name));

答案 8 :(得分:2)

物化视图的变体&#34;可能的替代品&#34; outlined by Erwin Brandstetter是可能的。

例如,假设您不希望在返回的随机值中出现重复项。因此,您需要在包含(非随机)值集的主表上设置一个布尔值。

假设这是输入表:

id_values  id  |   used
           ----+--------
           1   |   FALSE
           2   |   FALSE
           3   |   FALSE
           4   |   FALSE
           5   |   FALSE
           ...

根据需要填充ID_VALUES表。然后,如Erwin所述,创建一个物化视图,将ID_VALUES表随机化一次:

CREATE MATERIALIZED VIEW id_values_randomized AS
  SELECT id
  FROM id_values
  ORDER BY random();

请注意,实体化视图不包含已使用的列,因为它很快就会过时。视图也不需要包含可能在id_values表中的其他列。

为了获得(和&#34;消费&#34;)随机值,请在id_values上使用更新返回,从id_values选择id_values_randomized并加入,并且应用所需标准以仅获得相关的可能性。例如:

UPDATE id_values
SET used = TRUE
WHERE id_values.id IN 
  (SELECT i.id
    FROM id_values_randomized r INNER JOIN id_values i ON i.id = r.id
    WHERE (NOT i.used)
    LIMIT 5)
RETURNING id;

根据需要更改LIMIT - 如果您一次只需要一个随机值,请将LIMIT更改为1

使用id_values上的正确索引,我相信UPDATE-RETURNING应该在很少负载的情况下快速执行。它返回一个数据库往返的随机值。符合条件的标准&#34;行可以根据需要复杂。可以随时将新行添加到id_values表中,只要刷新物化视图(可能在非高峰时间运行),它们就可以被应用程序访问。物化视图的创建和刷新将很慢,但只有在将新的ID添加到id_values表时才需要执行。

答案 9 :(得分:0)

添加名为r的列serial。索引r

假设我们有200,000行,我们将生成一个随机数n,其中0 <0} n&lt; = 200,000。

选择r > n行,对其进行排序ASC并选择最小的行。

代码:

select * from YOUR_TABLE 
where r > (
    select (
        select reltuples::bigint AS estimate
        from   pg_class
        where  oid = 'public.YOUR_TABLE'::regclass) * random()
    )
order by r asc limit(1);

代码不言自明。中间的子查询用于从https://stackoverflow.com/a/7945274/1271094快速估计表行计数。

在应用程序级别,如果n&gt;您需要再次执行该语句行数或需要选择多行。

答案 10 :(得分:0)

我知道我参加聚会的时间有点晚了,但我刚刚找到了这个名为pg_sample的精彩工具:

  

pg_sample - 从较大的PostgreSQL数据库中提取一个小的样本数据集,同时保持参照完整性。

我尝试使用350M行数据库并且它非常快,不了解随机性

./pg_sample --limit="small_table = *" --limit="large_table = 100000" -U postgres source_db | psql -U postgres target_db

答案 11 :(得分:0)

根据我的经验教训:

$radioG1 = $('[name="Group1"]') $cbxG1 = $('#Group1_notRequired') $cbxG1.change(function() { $radioG1.prop("required", !this.checked) }) 并不比offset floor(random() * N) limit 1快。

我认为order by random() limit 1方法会更快,因为它可以节省在Postgres中进行排序的时间。事实并非如此。