选择随机行PostgreSQL的最佳方法

nan*_*nue 311 sql random postgresql performance

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

select * from table where random() < 0.01;
Run Code Online (Sandbox Code Playgroud)

但其他一些人推荐这个:

select * from table order by random() limit 1000;
Run Code Online (Sandbox Code Playgroud)

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

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

Erw*_*ter 215

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

  • 您有一个数字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;
Run Code Online (Sandbox Code Playgroud)

唯一可能很昂贵的部分是count(*)(对于巨大的桌子).鉴于上述规格,您不需要它.估计会很好,几乎没有任何费用(详细说明):

SELECT reltuples AS ct FROM pg_class WHERE oid = 'schema_name.big'::regclass;
Run Code Online (Sandbox Code Playgroud)

只要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
Run Code Online (Sandbox Code Playgroud)
  • id空间中生成随机数.你有"几个空白",所以添加10%(足以轻松覆盖空白)到要检索的行数.

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

  • 加入ids到大桌子.在索引到位时,这应该非常快.

  • 最后修剪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;
Run Code Online (Sandbox Code Playgroud)

使用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
Run Code Online (Sandbox Code Playgroud)

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

UNIONrCTE中的副本被消除.

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;
Run Code Online (Sandbox Code Playgroud)

呼叫:

SELECT * FROM f_random_sample();
SELECT * FROM f_random_sample(500, 1.05);
Run Code Online (Sandbox Code Playgroud)

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

可能的选择

如果您的要求允许重复呼叫的相同集合(我们正在讨论重复呼叫),我会考虑物化视图.执行上面的查询一次并将结果写入表.用户以闪电般的速度进行准随机选择.按照您选择的间隔或事件刷新随机选择.

Postgres 9.5介绍 TABLESAMPLE SYSTEM (n)

n百分比在哪里.手册:

BERNOULLISYSTEM采样方法的每一个接受单个参数,它是该表采样的分数,表示为 0到100之间的百分比.该参数可以是任何real值的表达式.

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

当指定小的采样百分比时,该SYSTEM方法明显快于该BERNOULLI方法,但是由于聚类效应,它可能返回表的较不随机的样本.

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

SELECT * FROM big TABLESAMPLE SYSTEM ((1000 * 100) / 5100000.0);
Run Code Online (Sandbox Code Playgroud)

有关:

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

SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);
Run Code Online (Sandbox Code Playgroud)

有关详细信息,请参阅Evan的回答.

但那仍然不是随机的.

  • @Awesome-o:目标是检索 1000 行,我从额外的 10% 开始,以补偿一些间隙或(不太可能但可能)重复的随机数......解释在我的答案中。 (2认同)

A.H*_*.H. 92

您可以使用检查和比较两者的执行计划

EXPLAIN select * from table where random() < 0.01;
EXPLAIN select * from table order by random() limit 1000;
Run Code Online (Sandbox Code Playgroud)

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

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

第三个提案是

select * from table where random() < 0.01 limit 1000;
Run Code Online (Sandbox Code Playgroud)

一旦找到1000行就会停止表扫描,因此会更快返回.当然,这会使随机性稍微降低,但在你的情况下,这可能已经足够了.

编辑:除了这些考虑因素,您可以查看已经提出的问题.使用该查询[postgresql] random返回相当多的点击.

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


1 "大",如"完整的表格不适合记忆".


Eri*_*ski 73

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

select your_columns from your_table ORDER BY random()
Run Code Online (Sandbox Code Playgroud)

postgresql以random()顺序排列:

select * from 
  (select distinct your_columns from your_table) table_alias
ORDER BY random()
Run Code Online (Sandbox Code Playgroud)

postgresql命令随机限制一行:

select your_columns from your_table ORDER BY random() limit 1
Run Code Online (Sandbox Code Playgroud)

  • `select your_columns from your_table ORDER BY random() limit 1` 需要大约 2 分钟才能在 4500 万行上执行 (7认同)
  • 有没有办法加快速度? (2认同)

Mic*_*lif 38

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

SELECT * FROM mytable TABLESAMPLE SYSTEM (5);
Run Code Online (Sandbox Code Playgroud)

这个例子将为您提供5%的元素mytable.

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

  • 您可以使用“ TABLESAMPLE SYSTEM_ROWS(400)”来获取400个随机行的样本。您需要启用[内置`tsm_system_rows`扩展名](https://www.postgresql.org/docs/current/static/tsm-system-rows.html)才能使用此语句。 (4认同)
  • 来自文档的一个重要说明:"SYSTEM方法进行块级采样,每个块具有指定的选择机会;返回每个选定块中的所有行.当采样百分比较小时,SYSTEM方法明显快于BERNOULLI方法是指定的,但由于聚类效应,它可能会返回表中较不随机的样本." (3认同)
  • 有没有办法指定行数而不是百分比? (2认同)

Don*_*ner 27

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是你将获得可变数量的输出记录.


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

  • 没有理由你不能在第一个例子的末尾添加限制1.唯一的问题是你有可能没有记录回来,所以你必须在你的代码中考虑这一点. (2认同)

Bog*_*rai 16

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

SELECT 
  field_1, 
  field_2, 
  field_2, 
  random() as ordering
FROM 
  big_table
WHERE 
  some_conditions
ORDER BY
  ordering 
LIMIT 1000;
Run Code Online (Sandbox Code Playgroud)

  • 我认为这种解决方案可以作为“ ORDER BY random()”工作,但可以正常工作,但是在处理大型表时可能效率不高。 (2认同)

Eva*_*oll 11

select * from table order by random() limit 1000;
Run Code Online (Sandbox Code Playgroud)

如果你知道你想要多少行,请查看tsm_system_rows.

tsm_system_rows

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

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

首先安装扩展

CREATE EXTENSION tsm_system_rows;
Run Code Online (Sandbox Code Playgroud)

然后你的查询,

SELECT *
FROM table
TABLESAMPLE SYSTEM_ROWS(1000);
Run Code Online (Sandbox Code Playgroud)

  • 我添加了指向您添加的答案的链接,这是对内置`SYSTEM`方法的显着改进。 (2认同)

Nel*_*nim 7

如果只需要一行,则可以使用计算offset得出的行count.

select * from table_name limit 1
offset floor(random() * (select count(*) from table_name));
Run Code Online (Sandbox Code Playgroud)


Sau*_*wal 6

我认为 postgreSQL 中最好、最简单的方法是:

SELECT * FROM tableName ORDER BY random() LIMIT 1
Run Code Online (Sandbox Code Playgroud)