从大表中有效地选择不同的(a,b)

Posted

技术标签:

【中文标题】从大表中有效地选择不同的(a,b)【英文标题】:Efficiently selecting distinct (a, b) from big table 【发布时间】:2019-10-04 00:27:09 【问题描述】:

我在 Postgres 9.6 DB 中有一个包含大约 5400 万行的表,并且希望找到所有不同的两列对(大约有 400 万个这样的值)。我对感兴趣的两列有一个索引:

create index ab_index on tbl (a, b)

获得此类配对的最有效方法是什么?我试过了:

select a,b
from tbl
where a>$previouslargesta
group by a,b
order by a,b
limit 1000

还有:

select distinct(a,b)
from tbl
where a>previouslargesta
order by a,b
limit 1000

还有这个递归查询:

with recursive t AS (
  select min(a) AS a from tbl
  union all
  select (select min(a) from tickets where a > t.a)
  FROM t)
select a FROM t

但一切都很慢。

有没有更快的方法来获取这些信息?

【问题讨论】:

LIMIT 1000 是用于测试还是只需要 1000 个结果行?哪些行重要吗? (ORDER BY a,b 也很重要?)显示您的确切索引定义。平均行大小(以字节为单位)?你能显示你尝试过的递归查询吗? 嗨@erwinbrandstetter。感谢您对我的问题感兴趣。我最终需要得到 a 和 b 的所有组合。我使用的页面大小为 1000。 【参考方案1】:

您的表有 5400 万行并且...

大约有 400 万个这样的值

所有行的 7.4% 是一个很高的百分比,索引主要只能通过提供预排序的数据来提供帮助,最好是在 index-only scan 中。对于较小的结果集有更复杂的技术(见下文),并且有 much 更快的分页方法,一次返回更少的行(见下文),但对于一般情况,一个普通的@987654328 @ 可能是最快的:

SELECT DISTINCT a, b  -- *no* parentheses
FROM   tbl;
-- ORDER  BY a, b      -- ORDER BY wasn't not mentioned as requirement ...

不要将它与需要括号的DISTINCT ON 混淆。见:

Select first row in each GROUP BY group?

(a, b) 上的 B-tree 索引 ab_index 已经是最好的索引。不过,必须对其进行全面扫描。挑战在于有足够的 work_mem 来处理 RAM 中的所有内容。使用标准设置,它在磁盘上至少占用 1831 MB,通常会更多,而且有些膨胀。如果您负担得起,请在会话中使用 2 GB(或更多)的 work_mem 设置运行查询。见:

Configuration parameter work_mem in PostgreSQL on Linux
SET work_mem = '2 GB';
SELECT DISTINCT a, b ...
RESET work_mem;

只读表有帮助。否则,您需要足够积极的VACUUM 设置以允许仅索引扫描。然而,更多的 RAM 将有助于(通过适当的设置)保持索引兑现。

还升级到 Postgres 的最新版本(撰写时为 11.3)。大数据有很多改进。

分页

如果您要按照示例查询的指示添加分页,请紧急考虑ROW 值比较。见:

Optimize query with OFFSET on large table SQL syntax term for 'WHERE (col1, col2) < (val1, val2)'
SELECT DISTINCT a, b
FROM   tbl
WHERE  (a, b) > ($previous_a, $previous_b)   -- !!!
ORDER  BY a, b
LIMIT  1000;

递归 CTE

对于一般的大查询,这也可能更快,也可能不会更快。对于小子集,它变得更加更具吸引力:

WITH RECURSIVE cte AS (
   (  -- parentheses required du to LIMIT 1
   SELECT a, b
   FROM   tbl
   WHERE  (a, b) > ($previous_a, $previous_b)   -- !!!
   ORDER  BY a, b
   LIMIT  1
   )
   UNION ALL
   SELECT x.a, x.b
   FROM   cte c
   CROSS  JOIN LATERAL (
      SELECT t.a, t.b
      FROM   tbl t
      WHERE  (t.a, t.b) > (c.a, c.b) -- lateral reference
      ORDER  BY t.a, t.b
      LIMIT  1
      ) x
   )
TABLE cte
LIMIT 1000;

这可以充分利用您的索引,并且应该尽可能快

进一步阅读:

Optimize GROUP BY query to retrieve latest row per user

对于重复使用并且表上没有或很少写入负载,请考虑基于上述查询之一的MATERIALIZED VIEW - 以获得更快的读取性能。

【讨论】:

【参考方案2】:

我不能保证 Postgres 的性能,但这是我在类似情况下在 sql server 上使用过的一种技术,并且被证明比其他技术更快:

将不同的 A 变成一个临时的 a

将不同的 B 转换为临时 b

将 a 和 b 时间跨过笛卡尔坐标系到一个 temp abALL

对 abALL 进行排名(可选)

创建一个视图 myview 作为 select top 1 a,b from tbl (your_main_table)

将 temp abALL 与 myview 加入 temp abCLEAN

如果您的排名没有超过,请在此处排名 abCLEAN

【讨论】:

以上是关于从大表中有效地选择不同的(a,b)的主要内容,如果未能解决你的问题,请参考以下文章

优化从大表中选择

MySQL nodejs 在从大表中选择数据时崩溃

MySQL - 从大表中选择随机行

使用多个 WHERE 有效地连接两个表

使用python从大表中删除大量记录的有效方法

如何优化限制查询以更快地从大表中访问数据?