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
鉴于您的规格(以及评论中的其他信息),
下面的查询不需要对大表进行顺序扫描,只需要进行索引扫描.
首先,获取主要查询的估计值:
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
).
加入id
s到大桌子.在索引到位时,这应该非常快.
最后修剪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)
特别是如果你不确定差距和估计.
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空间中我们仍然需要相对较少的间隙,或者在达到限制之前递归可能会干涸 - 或者我们必须从足够大的缓冲区开始,这违背了优化性能的目的.
UNION
rCTE中的副本被消除.
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
......但这超出了这个问题的范围.看到:
如果您的要求允许重复呼叫的相同集合(我们正在讨论重复呼叫),我会考虑物化视图.执行上面的查询一次并将结果写入表.用户以闪电般的速度进行准随机选择.按照您选择的间隔或事件刷新随机选择.
TABLESAMPLE SYSTEM (n)
n
百分比在哪里.手册:
的
BERNOULLI
和SYSTEM
采样方法的每一个接受单个参数,它是该表采样的分数,表示为 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的回答.
但那仍然不是随机的.
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
select your_columns from your_table ORDER BY random()
Run Code Online (Sandbox Code Playgroud)
select * from
(select distinct your_columns from your_table) table_alias
ORDER BY random()
Run Code Online (Sandbox Code Playgroud)
select your_columns from your_table ORDER BY random() limit 1
Run Code Online (Sandbox Code Playgroud)
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
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听起来就像是一个挑战.
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)
Eva*_*oll 11
select * from table order by random() limit 1000;
Run Code Online (Sandbox Code Playgroud)
如果你知道你想要多少行,请查看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)
如果只需要一行,则可以使用计算offset
得出的行count
.
select * from table_name limit 1
offset floor(random() * (select count(*) from table_name));
Run Code Online (Sandbox Code Playgroud)
我认为 postgreSQL 中最好、最简单的方法是:
SELECT * FROM tableName ORDER BY random() LIMIT 1
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
189303 次 |
最近记录: |