Postgres,排序

时间:2016-09-09 07:18:06

标签: postgresql query-optimization

有一个包含这种结构的表:

    Table "public.all_emails"
│ Column | Type | Modifiers
│ ----------- + -------- + -----------
│ email | text |
│ frequency | bigint |
│Indexes:
│ "all_emails_email_idx" UNIQUE, btree (email)

我希望通过对它们执行更多操作,将此表中的所有记录移动到另一个数据库中。为了加快速度,我编写了一个多进程应用程序,它需要几次特定的表。为了知道以下哪个过程开始,我按如下方式对表进行排序:

Select email from all_emails order by email limit # {PULL_SIZE} offset # {offset}

如果表中有大量记录,此操作非常昂贵且不是最佳的。我怎样才能让它变得更好?

2 个答案:

答案 0 :(得分:1)

为此,你可以CLUSTER你的桌子:

CLUSTER all_emails USING all_emails_email_idx;
ANALYZE all_emails;

群集根据指定的索引对表中的行进行物理重新排序。因此,电子邮件地址是根据电子邮件地址排序的,然后查询 - 与任何其他查询一样的过程 - 将在有限数量的磁盘页面上找到所请求子集中的所有行,因此I / O也会减少任何排序(因为查询计划程序识别该表是在特定索引上聚集的)。 ANALYZE命令在群集后更新表统计信息,以帮助查询计划程序做出最佳选择。

这实际上只适用于只读或不经常更新或插入新行的表,因为不维护群集:它是一次性过程。群集也是一个相当“昂贵”的过程,因为整个表被重写并且需要一个独占的表锁。您可以使用缩写形式CLUSTER all_emails的相同索引定期对表重新聚类。

答案 1 :(得分:1)

没有什么比单个顺序扫描读取整个表更快,至少在引入并行顺序扫描的PostgreSQL 9.6之前。

将表格拆分为ctid(表格中元组的物理位置)是很诱人的,但PostgreSQL不会优化ctid对不同于=的运算符的访问权限:

test=> EXPLAIN SELECT * FROM large WHERE ctid BETWEEN '(390, 0)' AND '(400,0)';
┌───────────────────────────────────────────────────────────────────┐
│                            QUERY PLAN                             │
├───────────────────────────────────────────────────────────────────┤
│ Seq Scan on large  (cost=0.00..1943.00 rows=500 width=8)          │
│   Filter: ((ctid >= '(390,0)'::tid) AND (ctid <= '(400,0)'::tid)) │
└───────────────────────────────────────────────────────────────────┘
(2 rows)

同样适用于插入:不能显示数字,我很确定一个进程INSERTCOPY进入一个表的速度不会慢于几个进程所有加载数据进入同一张桌子。

由于瓶颈似乎是处理原点SELECT与目的地INSERT之间行的处理,我建议如下:

  1. 让一个线程执行单个SELECT * FROM all_emails

  2. 创建一些可以并行执行昂贵处理的线程。

  3. 第一个线程以循环方式将结果行分发给并行工作者。

  4. 另一个线程收集并行工作程序的结果,并将它们组合成它执行的COPY tablename FROM STDIN语句的输入。