有一个包含这种结构的表:
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}
如果表中有大量记录,此操作非常昂贵且不是最佳的。我怎样才能让它变得更好?
答案 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)
同样适用于插入:不能显示数字,我很确定一个进程INSERT
或COPY
进入一个表的速度不会慢于几个进程所有加载数据进入同一张桌子。
由于瓶颈似乎是处理原点SELECT
与目的地INSERT
之间行的处理,我建议如下:
让一个线程执行单个SELECT * FROM all_emails
。
创建一些可以并行执行昂贵处理的线程。
第一个线程以循环方式将结果行分发给并行工作者。
另一个线程收集并行工作程序的结果,并将它们组合成它执行的COPY tablename FROM STDIN
语句的输入。