暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

[译文] 在 PostgreSQL 中更快地获得随机行

原创 Magnus Hagander 2021-08-10
1765

在 PostgreSQL 中经常需要从表中获取单个随机行或几行以获取代表性数据,执行此操作的最常见方法是使用ORDER BY random()类似:

SELECT id FROM data ORDER BY random() LIMIT 1
复制

但是当在大表上运行时,这可能会非常慢,因为它必须扫描整个表才能找到行。Jonathan Katz在 Twitter 上提到了一种不同的方法,这提醒我人们不断想出不同的(有时是非常复杂的)方法来尝试解决这个问题。

虽然乔纳森的方法(他有超级简单的示例代码,结果在一个要点上)仍然比ORDER BY random()我的测试(使用他的数据)快两倍,但它带来了一些问题。例如,它需要一组连续的id值,这些值必须是整数。在我的机器上运行他的 500 万行样本仍然需要大约一秒钟的时间——并且随着表的增长会越来越慢。

事实证明,如果你不需要你的行完全随机,只是大部分随机,并且可以处理一些警告,PostgreSQL 有内置功能,它比 Jonathan 的版本快 20,000 倍,快 40,000 倍比ORDER BY random()。输入TABLESAMPLE。

TABLESAMPLE是一种旨在返回表的样本部分的功能,例如10%. 但是使用插件tsm_system_rows(默认包含在 PostgreSQL 中,但未激活),我们可以返回样本行数。扫描的性能并不完全独立于表的大小,而是几乎,即使在非常大的表上,如果您只需要一行,它也会运行得非常快。

那么它是怎样工作的?

CREATE EXTENSION tsm_system_rows ; SELECT id FROM data TABLESAMPLE system_rows(1);
复制

在我的机器上,使用 Jonathans 的 500 万个 bigint 样本,我的运行时间是:

  • ORDER BY random() ~2 秒
  • 乔纳森的方法~1秒
  • TABLESAMPLE ~0.05 毫秒

随着表格的增加,差异只会变得更大。例如,再向表中添加 1000 万行,现在我得到:

  • ORDER BY random() ~ 4-4.5 秒
  • 乔纳森的方法 - ~ 2.7-3 秒
  • TABLESAMPLE ~0.05 毫秒

(TABLESAMPLE在我的测试中,的执行时间很短,由于分辨率的原因,它们变化很大,但从未超过 0.1 毫秒)。

那么这个“魔法”是如何运作的呢?

该函数的 system_rows版本TABLESAMPLE将在表中随机选择一个磁盘块,然后从那里依次获取行。只需查看表的大小即可选择随机块,因此速度非常快。

只要你只得到一行就可以了。但是如果你得到不止一行,如果你想要随机性,你可能会得到不好的结果:

postgres=# SELECT id FROM data ORDER BY random() LIMIT 3; id ---------- 4545431 772665 12743060 (3 rows) postgres=# SELECT id FROM data TABLESAMPLE system_rows(3); id --------- 4815157 4815158 4815159 (3 rows)
复制

注意第一行是如何随机选取的,但接下来的行是连续的。

如果您获取的行比该页面上的多,PostgreSQL 将选择另一个随机块。所以你会得到一组连续的行,但这些集合本身是随机的。这些集合的大小将取决于表中行的宽度,因为它控制着每页上的行数。

PostgreSQL 还提供了许多其他方法来对您的表进行采样(作为 PostgreSQL,您当然可以编写自己的,但这是获取该行的快速方法。

那么有什么注意事项呢?肯定不会有那么神奇吧?

需要注意的是,TABLESAMPLE仅适用于表的实际扫描,不能直接与 WHERE 子句结合使用。例如,假设我想从表中随机获取偶数行。使用常规方法很容易:

SELECT id FROM data WHERE id % 2 = 0 ORDER BY random()
复制

但是,如果我跑

SELECT id FROM data TABLESAMPLE system_rows(1) WHERE id % 2 = 0;
复制

一半的时间我会得到零行。这是因为 theTABLESAMPLE将首先返回一行,然后如果该行不是,则该WHERE子句将把它扔掉。

我们可以使用以下类似的方法来降低可能性,它仍然在 0.1 毫秒内运行。

WITH t AS ( SELECT id FROM data TABLESAMPLE system_rows(10) ) SELECT id FROM t WHERE id % 2 = 0 LIMIT 1
复制

如果在内部扫描中选择的额外行数足够大,这将“几乎一直”成功。但是,根据数据的分布,在某些运行中您仍然可能会得到零行。

因此,如果您在过滤器之后需要确切数量的行,这不是一个足够好的解决方案,除非您的应用程序愿意重新运行它的查询。但是随着数万倍的加速,根据用例,实际上可能值得重新运行几次查询,直到一行回来。

TABLESAMPLE以及tsm_system_rows在所有支持的 PostgreSQL 版本中都可用。该system和bernoulli获得表的一定比例的方法在标准SQL,该system_rows方法是PostgreSQL扩展。

原文地址:https://blog.hagander.net/getting-random-rows-faster-very-much-faster-249/

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论