从 big table 中高效地选择 distinct (a, b)

Efficiently selecting distinct (a, b) from big table

我有一个 table 在 Postgres 9.6 数据库中有大约 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

但都是懒散的。

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

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

there are around 4 million such values

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

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 的磁盘空间,通常会因膨胀而占用更多空间。如果您负担得起,运行 在您的会话中 work_mem 设置为 2 GB(或更多)的查询。参见:

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

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

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

分页

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

SELECT DISTINCT a, b
FROM   tbl
<b>WHERE  (a, b) > ($previous_a, $previous_b)   -- !!!</b>
ORDER  BY a, b
LIMIT  1000;

递归 CTE

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

WITH RECURSIVE cte AS (
   (  -- parentheses required du to LIMIT 1
   SELECT a, b
   FROM   tbl
   <b>WHERE  (a, b) > ($previous_a, $previous_b)</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

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

我不能保证 Postgres 的性能,但这是我在 sql 服务器上用过的类似案例中的一项技术,并且证明比其他技术更快:

将不同的 A 转换为临时 a

将不同的 B 变成临时 b

将笛卡尔的 a 和 b 临时值交叉为临时值 abALL

对 abALL 进行排名(可选)

创建视图 myview 作为 select 来自 tbl (your_main_table)

的前 1 a,b

将 temp abALL 与 myview 合并为 temp abCLEAN

如果您的排名没有超过

,请在此处排名 abCLEAN