我正在努力针对两个非常大的表优化一个简单的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
两个表都非常大:vca
和gt
分别具有900万(2 GB)和13亿行(346 GB)。
我创建vca
(MATERIALIZED 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:
为了澄清,我在带有16个内核和32 GB内存的CentOS 7.3上使用Postgres 10.5。查询现在已经运行24小时以上,没有任何结果。
检查状态,看来wait_event_type
是IO
。这是否意味着查询正在暂存/写入暂存空间?这可以解释缓慢吗?
+------------------+---------------+---------------+---------------+---------------+-----------------+--------------+--------+-------------+--------------+
| 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
我猜想腾出更多的内存可能有帮助?有什么方法可以优化需要更多内存的查询?
答案 0 :(得分:1)
这篇文章的评论:
您的查询正在使用
genotype_pos_ind
并在aliquot_barcode
上进行过滤。尝试(暂时)删除genotype_pos_ind
,如果这样不起作用,请搜索如何强制使用索引。
您的查询应改为使用genotype_pk
。
根据您所说的,可能有很多记录,其中aliquot_barcode
,chrom
,start
和end
具有相同的值,因此RDBMS将采用需要很长时间来过滤每个aliquot_barcode
。
如果对您来说太长了,您可以尝试我的较旧答案,我将保留该答案以供进一步参考:
不幸的是,我无法优化您的查询:要考虑的东西太多了。生成具有13个字段的900万条记录的结果可能会太多:交换可能会发生,您的操作系统将不允许进行如此多的内存分配,同时还会进行(写在真正的答案...)JOIN
等。
我曾经优化过一些查询,这些查询由大约1000万条记录的15个表组成。如此大小的SELECT
在合理的时间内(少于10小时)将永远无法使用。
我没有任何RDBMS可以测试我的意思。另外,半年我没有做任何SQL:p 找到为什么要花费这么多时间(如您所问)将花费太多时间,因此这是原始问题的另一种解决方案。
我采用的解决方案是制作临时表:
tmp_analysis
相同的字段以及一些实用程序字段创建临时表SELECT
:一个ID字段(tmp_ID
,一个大整数),一个布尔值,用于检查记录是否已更新(tmp_updated),以及时间戳记,以检查记录是否已更新(tmp_update_time
)。
当然,来自原始SELECT
(来自vca
和gt
的所有具有相同数据类型的字段
vca
插入所有记录:暂时对null
中的字段使用gt
(或其他默认值,如果不能的话)。将tmp_updated
设置为false。使用简单的count()
作为主键。
使用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这是一对一的关系,但是我不确定。无论如何,索引
SELECT
您的tmp_analysis
表可以获取记录!一些注意事项:
例如,使用tmp_ID
字段将更新次数限制为10000,并检查第三个查询(UPDATE
)的执行计划:您应该对临时表表进行全面扫描并在gt
上(在genotype_pk
上进行索引扫描)。如果不是,请检查索引并搜索如何强制PGSL使用索引。您应该使用WHERE tmp_ID < 10000
而不是LIMIT 10000
。 IIRC,LIMIT
将执行整个查询,并只向您提供部分结果。
使用tmp_ID
对查询进行细分,并(如您所说)在UPDATE
上使用loop statement一次查询10万条或更少的记录(再次使用{{1} }。再次检查执行计划:在索引扫描之前,完整扫描应受where tmp_ID < x AND tmp_ID > y
限制。不要伪造在此fild上添加索引(如果它还不是主键)。
使用tmp_id
封装所有查询,并使用BEGIN/END TRANSACTION
上的TEMPORARY TABLE
选项,这样您在执行查询后就不必清理tmp_analysis。
在循环内使用事务,如果循环再次冻结,则将其停止。然后,您可以稍后以较小的循环大小还原它。
您可以使用CREATE TABLE tmp_analysis
在一个查询中执行步骤1和2,但是我不记得如何为INSERT .. AS .. SELECT
中的字段设置数据类型,因为它们将被设置为null。通常,这总体上应该快一点。
没有循环的查询仍然需要10多个小时,将其停止并检查tmp_update_time以查看执行时间是如何变化的,也许它将为您提供一个线索,说明原始查询为何不起作用。 PGSQL上有多个配置选项以限制RAM使用,磁盘使用和线程。您的操作系统可能会设置自己的限制,并检查磁盘交换,CPU缓存使用情况等。(我认为您已经完成了一些操作,但我没有检查)