选择随机行PostgreSQL的最佳方式

问题描述 投票:282回答:12

我想在PostgreSQL的行随机选择,我尝试这样做:

select * from table where random() < 0.01;

但其他一些建议如下:

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

我有500万行一个非常大的表,我想它要快。

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

sql performance postgresql random
12个回答
207
投票

鉴于您的要求(加在评论中附加信息)

  • 你有一个数字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)。
  • 加入ids到大表。这应该是非常快到位的指标。
  • 最后修剪还没有被吃掉的愚弄和差距盈余ids。每一行必须要挑一个完全平等的机会。

Short version

您可以简化这个查询。该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 ......但是,这超出了这个问题的范围。看到:

Possible alternative

如果您的要求允许同一集重复调用(和我们谈论重复调用),我会考虑一个物化视图。执行上面的查询一次,并将结果写入到一个表。用户可以再一次闪电般的速度准随机选择。刷新间隔或您所选择的活动你随便挑。

Postgres的9.5引入TABLESAMPLE SYSTEM (n)

n是一个百分比。 The manual:

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

大胆重点煤矿。它的速度非常快,但结果是不完全随机的。再次手动:

SYSTEM方法比当指定小采样百分比BERNOULLI方法显著更快,但它可能会返回表中的一个随机样品量少作为聚类效应的结果。

行数返回变化很大。在本例中,得到大约1000行:

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

有关:

或安装额外的模块tsm_system_rows得到确切请求的行数(如果有足够的),并允许更方便的语法:

SELECT * FROM big TABLESAMPLE SYSTEM_ROWS(1000);

Evan's answer了解详情。

但是,这仍然不是完全随机的。


0
投票

添加一个名为rserial列。指数r

假设我们有200,000行,我们将产生一个随机数n,其中0 <n <= 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再次执行该语句>的行数或需要选择多行。


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

0
投票

从我的经验教训之一:

offset floor(random() * N) limit 1并不比order by random() limit 1快。

我以为offset的做法是更快,因为它要保存在Postgres的整理时间。原来事实并非如此。


89
投票

您可以检查并通过比较两者的执行计划

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返回了不少点击率查询。

和depez的链接文章,概述几个方法:


1“大”,如“完整的表格将不适合到内存”。


71
投票

的PostgreSQL为了通过以随机顺序随机(),选择的行:

select your_columns from your_table ORDER BY random()

PostgreSQL的顺序由随机()与不同的:

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

37
投票

从PostgreSQL 9.5开始,有致力于让从表中随机元素的新语法:

SELECT * FROM mytable TABLESAMPLE SYSTEM (5);

这个例子将给你从mytable元素的5%。

看到这个博客帖子更多的解释:http://www.postgresql.org/docs/current/static/sql-select.html


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之一是,你会得到一个可变数量的输出记录。


请注意,有一个洗牌的一组数据比随机排序更好的办法:The Fisher-Yates Shuffle,它运行在O(N)。在执行SQL洗牌听起来相当的挑战,虽然。


15
投票

下面是对我工作的决定。我想这是很容易理解和执行。

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

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

如果你知道你想要多少行,退房tsm_system_rows

tsm_system_rows

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

此表采样方法接受一个整数参数也就是读取的行的最大数量。所得样品总是包含准确的行数,除非表不包含足够的行,在这种情况下选择了整个表。像内置系统抽样方法,SYSTEM_ROWS执行块级采样,使得样品没有完全随机的,而是可能会受到影响聚类,特别是如果只要求的行一个小数目。

首先安装扩展

CREATE EXTENSION tsm_system_rows;

然后你的查询,

SELECT *
FROM table
TABLESAMPLE SYSTEM_ROWS(1000);

7
投票

如果你只想要一个行,你可以使用从offset得出的计算count

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

2
投票

物化视图“可能的替代” outlined by Erwin Brandstetter的变型是可能的。

说,例如,你不希望在返回的随机值重复。所以,你需要设置包含您的值(非随机)设定主表的布尔值。

假设这是输入表:

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

根据需要填充ID_VALUES表。然后,如由欧文描述,创建一个物化视图随机化ID_VALUES表一次:

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

需要注意的是物化视图不包含使用列,因为这将很快成为了过期。也不认为需要包含其它列,其可以是在id_values表。

为了获得(和“消费”)的随机值,使用UPDATE-返程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 - 如果你一次只需要一个随机值,改变LIMIT1

随着id_values适当的指标,我相信UPDATE返流应该很少加载速度非常快速执行。它返回一个数据库往返随机值。按要求“合格”行的条件可以是复杂的。新行可以在任何时间被添加到id_values表,并作为物化视图刷新他们将尽快成为该应用访问(可以可能在非高峰时间运行)。物化视图的创建和更新将是缓慢的,但它只需要在新的ID添加到id_values表执行。

© www.soinside.com 2019 - 2024. All rights reserved.