繁体   English   中英

postgres非常慢(12小时以上)的大桌子加入

[英]Very slow (12+ hours) large table joins in postgres

我正在努力优化一个简单的LEFT JOIN对抗两个非常大的表,到目前为止已经花费了大约12个小时来完成和持续。

这是执行计划:

Gather  (cost=1001.26..11864143.06 rows=8972234 width=133)
  Workers Planned: 7
  ->  Nested Loop Left Join  (cost=1.26..10773657.51 rows=1281748 width=133)
        ->  Parallel Index Scan using var_case_aliquot_aliquot_ind on var_case_aliquot vca  (cost=0.56..464070.21 rows=1281748 width=103)
        ->  Index Scan using genotype_pos_ind on snv_genotypes gt  (cost=0.70..8.01 rows=1 width=65)
              Index Cond: ((vca.chrom = chrom) AND (vca.start = start) AND (vca.end = end) AND ((vca.alt)::text = (alt)::text))
              Filter: (vca.aliquot_barcode = aliquot_barcode)

这是查询:

SELECT vca.aliquot_barcode,
    vca.case_barcode,
    vca.gene_symbol,
    vca.variant_classification,
    vca.variant_type,
    vca.chrom,
    int4range(vca.start::integer, vca."end"::integer, '[]'::text) AS pos,
    vca.alt,
    gt.called AS mutect2_call,
    gt.ref_count,
    gt.alt_count,
    gt.read_depth,
    gt.called OR
        CASE
            WHEN (gt.alt_count + gt.ref_count) > 0 THEN (gt.alt_count::numeric / (gt.alt_count + gt.ref_count)::numeric) > 0.20
            ELSE false
        END AS vaf_corrected_call
   FROM analysis.var_case_aliquot vca
     LEFT JOIN analysis.snv_genotypes gt ON vca.aliquot_barcode = gt.aliquot_barcode AND vca.chrom = gt.chrom AND vca.start = gt.start AND vca."end" = gt."end" AND vca.alt::text = gt.alt::text

两个表都非常大: vcagt有900万(2 GB)和13亿行(346 GB)。

我创建了vcaMATERIALIZED VIEW ),仅用于执行此连接。 本质上,它是一个连接表,只有1:1匹配左连接所需的字段,然后是一些额外的元数据。 正如您在查询计划中看到的那样,所有正在加入的字段都已正确编入索引。

查询本身很简单,有什么我想念的东西可以加快它吗? 我不认为有一些方法可以使用WHERE吗?

我可以在postgres设置中调整一些可能有帮助的东西吗? 目前我有以下内容:

shared_buffers = 4096MB
effective_cache_size = 20GB
work_mem = 64MB
maintenance_work_mem = 4096MB
max_wal_size = 4GB
min_wal_size = 128MB
checkpoint_completion_target = 0.9
max_worker_processes = 16
max_parallel_workers_per_gather = 8
max_parallel_workers = 16

更新12/12:

表DDL:

CREATE TABLE analysis.snv_genotypes (
    aliquot_barcode character(30) NOT NULL,
    chrom character(2) NOT NULL,
    start bigint NOT NULL,
    "end" bigint NOT NULL,
    alt character varying(510) NOT NULL,
    genotype character(3),
    read_depth integer,
    ref_count integer,
    alt_count integer,
    called boolean
);

ALTER TABLE ONLY analysis.snv_genotypes
    ADD CONSTRAINT genotype_pk PRIMARY KEY (aliquot_barcode, chrom, start, "end", alt);
CREATE INDEX called_ind ON analysis.snv_genotypes USING btree (called);
CREATE INDEX genotype_pos_ind ON analysis.snv_genotypes USING btree (chrom, start, "end", alt);

CREATE MATERIALIZED VIEW analysis.var_case_aliquot AS
 SELECT var_case_aliquot.aliquot_barcode,
    var_case_aliquot.case_barcode,
    var_case_aliquot.chrom,
    var_case_aliquot.start,
    var_case_aliquot."end",
    var_case_aliquot.alt,
    var_case_aliquot.gene_symbol,
    var_case_aliquot.variant_classification,
    var_case_aliquot.variant_type,
    var_case_aliquot.hgvs_p,
    var_case_aliquot.polyphen,
    var_case_aliquot.sift
   FROM var_case_aliquot
  WITH NO DATA;

CREATE INDEX var_case_aliquot_aliquot_ind ON analysis.var_case_aliquot USING btree (aliquot_barcode);
CREATE INDEX var_case_aliquot_pos_ind ON analysis.var_case_aliquot USING btree (chrom, start, "end", alt);

更广泛的DDL: https//rextester.com/JRJH43442

更新12/13:

为了澄清,我在CentOS 7.3上使用Postgres 10.5 w / 16内核和32 GB内存。 查询现在已经运行了24小时而没有任何结果。

检查状态似乎是wait_event_typeIO 这是否意味着查询正在刮擦/写入临时空间? 这可以解释缓慢吗?

+------------------+---------------+---------------+---------------+---------------+-----------------+--------------+--------+-------------+--------------+
| application_name | backend_start | xact_start    | query_start   | state_change  | wait_event_type | wait_event   | state  | backend_xid | backend_xmin |
+------------------+---------------+---------------+---------------+---------------+-----------------+--------------+--------+-------------+--------------+
| psql             | 12/12/18 8:42 | 12/12/18 8:42 | 12/12/18 8:42 | 12/12/18 8:42 | IO              | DataFileRead | active | 22135       | 22135        |
+------------------+---------------+---------------+---------------+---------------+-----------------+--------------+--------+-------------+--------------+

我有很多可用的资源:

$ free -h
              total        used        free      shared  buff/cache   available
Mem:            31G        722M        210M        5.0G         30G         25G
Swap:          3.7G        626M        3.1G

我想提供更多内存可能会有所帮助吗? 有没有办法优化需要更多内存的查询?

从这篇文章的评论:

您的查询使用genotype_pos_ind并对aliquot_barcode过滤。 尝试删除(临时) genotype_pos_ind ,如果这不起作用,请搜索如何强制使用索引。

您的查询应该使用genotype_pk

根据你所说的,可能有很多记录具有相同的aliquot_barcodechromstartend ,因此RDBMS将花费很长时间来过滤每个aliquot_barcode

如果它对你来说仍然太长,你可以尝试我的旧答案,我将继续提供进一步的参考:



不幸的是,我无法优化您的查询:有太多事情需要考虑。 用9百万条记录的13个字段构建一个结果可能太多了:可能会发生交换,你的操作系统不会允许这么多的内存分配,同时也会产生JOIN等等。 (在真正的答案之前写的......)

我曾经优化过一些由大约1000万条记录的十五个表组成的查询。 在合理的时间内(不到10小时),这种尺寸的SELECT永远不可行。

我没有任何RDBMS来测试我在说什么。 此外,我半年没有做过任何SQL:p找到为什么花了这么多时间(正如你的要求)将耗费太多时间,所以这是原始问题的另一种解决方案。


我采用的解决方案是制作临时表:

  1. 创建临时表: tmp_analysis ,其中包含与SELECT +一些实用程序字段相同的字段:

一个ID字段( tmp_ID ,一个大的int),一个用于检查记录是否已更新的布尔值(tmp_updated),以及用于检查更新时间的时间戳( tmp_update_time )。 当然,所有具有相同数据类型的字段都来自原始SELECT (来自vcagt

  1. vca插入所有记录:

目前,对于gt字段,请使用null (或任何其他默认值,如果不能)。 tmp_updated设置为false。 对主键使用简单的count()

  1. 使用gt中的字段更新所有这些记录。

使用WHERE而不是JOIN

UPDATE tmp_analysis as tmp -- I don't think you need to use a schema to call tmp_analysis
    SET tmp_update = true,
    tmp_update_time = clock_timestamp(),
    tmp.mutect2_call = gt.called
    gt.ref_count,
    gt.alt_count,
    gt.read_depth,
    gt.called = -- ... (your CASE/WHEN/ELSE/END should work here)
FROM 
    analysis.snv_genotypes gt
WHERE --JOIN should work too
    tmp.aliquot_barcode = gt.aliquot_barcode AND 
    tmp.chrom = gt.chrom AND 
    vca.start = gt.start AND 
    tmp."end" = gt."end" AND 
    tmp.alt::text = gt.alt::text

我说你应该出于性能原因使用EXISTS ,但是我错了,因为我认为你不能从EXISTS条件中检索字段。 可能有一种方法可以告诉Postgresql它是一对一的关系,但我不确定。 无论如何,索引

  1. 显然, SELECT你的tmp_analysis表来获取你的记录!

一些注意事项:

  1. 如果花了太多时间:

例如,使用tmp_ID字段将更新次数限制为10 000,并检查第3次查询( UPDATE )的执行计划:您应该对临时表表进行完整扫描,并在gt (在genotype_pk )进行索引扫描。 如果没有,请检查索引并搜索如何强制PGSL使用索引。 您应该使用WHERE tmp_ID < 10000而不是LIMIT 10000 IIRC, LIMIT将执行整个查询,只给你一部分结果。

  1. 如果仍然花费太多时间:

使用tmp_ID分段,并且(正如您所说)在UPDATE上使用循环语句一次查询100 000或更少的记录(再次使用where tmp_ID < x AND tmp_ID > y )。 再次检查执行计划:完整扫描应该在索引扫描之前由tmp_id限制。 不要伪造在这个fild上添加一个索引(如果它还不是主键)。

  1. 如果您以后需要再次呼叫:

使用BEGIN/END TRANSACTION来封装所有查询,并在CREATE TABLE tmp_analysis上使用TEMPORARY TABLE选项,这样您就不必在执行查询后清理tmp_analysis。

  1. 如果你仍然有循环问题:

在循环内使用事务,如果再次冻结则停止它。 然后,您可以稍后使用较小的循环大小恢复它。

  1. 如果你想减少一点执行时间:

您可以使用INSERT .. AS .. SELECT在一个查询中执行步骤1和2,但我不记得如何为gt字段设置数据类型,因为它们将设置为null。 通常情况下,这应该会更快一些。

  1. 如果你很好奇:

没有循环的查询仍然需要超过10个小时,停止它并检查tmp_update_time以查看执行时间如何演变,也许它会给你一个关于原始查询无效的原因的线索。 PGSQL上有多个配置选项来限制RAM使用率,磁盘使用率,线程。 您的操作系统可能会设置自己的限制,并检查磁盘交换,CPU缓存使用情况等。(我想您已经完成了一些但我没有检查)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM