有什么办法可以加快这个 Postgres 位图堆扫描的速度吗?
Is there any way to speed up this Postgres bitmap heap scan?
这里是数据库新手。这是我的查询,我使用的是 Postgres 9.3.5:
=# explain analyse SELECT SUM(actual_cost) as cost, SUM(total_items)
as num_items, processing_date FROM frontend_items
WHERE chemical_id='0501013B0' GROUP BY processing_date;
这是查询计划:
HashAggregate (cost=1648624.91..1648624.92 rows=1 width=16) (actual time=12591.844..12591.848 rows=17 loops=1)
-> Bitmap Heap Scan on frontend_items (cost=14520.24..1643821.35 rows=640474 width=16) (actual time=254.841..12317.746 rows=724242 loops=1)
Recheck Cond: ((chemical_id)::text = '0501013B0'::text)
-> Bitmap Index Scan on frontend_items_chemical_id_varchar_pattern_ops_idx (cost=0.00..14360.12 rows=640474 width=0) (actual time=209.538..209.538 rows=724242 loops=1)
Index Cond: ((chemical_id)::text = '0501013B0'::text)
Total runtime: 12592.499 ms
如您所见,Bitmap Heap Scan
占用了大部分时间。有什么办法可以加快速度吗?
如果需要,我可以创建更多索引:我的数据几乎是只读的(它每月更新一次)。
鉴于我想要多个属性,我想我无能为力,除了支付足够的 RAM 以将整个数据库保存在内存中之外,但非常感谢您的建议。
我可以一次只查找其中一个属性,如果这样可以加快速度的话。
注意:我 运行 在配备 16GB RAM 和 SSD 的 Macbook 上进行此操作。我已将 shared_buffers
设置为 4GB,将 work_mem
设置为 40MB。我最终将使用具有 32GB RAM 和 SSD 的服务器。
更新:table 架构如下:
Column | Type | Modifiers
-------------------+-------------------------+--------------------------------------------------------------------
id | integer | not null default nextval('frontend_items_id_seq'::regclass)
presentation_code | character varying(15) | not null
presentation_name | character varying(1000) | not null
total_items | integer | not null
net_cost | double precision | not null
actual_cost | double precision | not null
quantity | double precision | not null
processing_date | date | not null
price_per_unit | double precision | not null
chemical_id | character varying(9) | not null
pct_id | character varying(3) | not null
practice_id | character varying(6) | not null
sha_id | character varying(3) | not null
Indexes:
"frontend_items_pkey" PRIMARY KEY, btree (id)
"frontend_items_45fff4c7" btree (sha_id)
"frontend_items_4e2e609b" btree (pct_id)
"frontend_items_528f368c" btree (processing_date)
"frontend_items_6ea07fe3" btree (practice_id)
"frontend_items_a69d813a" btree (chemical_id)
"frontend_items_b9b2c7ab" btree (presentation_code)
"frontend_items_chemical_id_varchar_pattern_ops_idx" btree (chemical_id varchar_pattern_ops)
"frontend_items_pct_code_id_488a8bbfb2bddc6d_like" btree (pct_id varchar_pattern_ops)
"frontend_items_practice_id_bbbafffdb2c2bf1_like" btree (practice_id varchar_pattern_ops)
"frontend_items_presentation_code_69403ee04fda6522_like" btree (presentation_code varchar_pattern_ops)
"frontend_items_presentation_code_varchar_pattern_ops_idx" btree (presentation_code varchar_pattern_ops)
Foreign-key constraints:
"front_chemical_id_4619f68f65c49a8_fk_frontend_chemical_bnf_code" FOREIGN KEY (chemical_id) REFERENCES frontend_chemical(bnf_code) DEFERRABLE INITIALLY DEFERRED
"frontend__practice_id_bbbafffdb2c2bf1_fk_frontend_practice_code" FOREIGN KEY (practice_id) REFERENCES frontend_practice(code) DEFERRABLE INITIALLY DEFERRED
"frontend_items_pct_id_30c06df242c3d1ba_fk_frontend_pct_code" FOREIGN KEY (pct_id) REFERENCES frontend_pct(code) DEFERRABLE INITIALLY DEFERRED
"frontend_items_sha_id_4fa0ca3c3b9b67f_fk_frontend_sha_code" FOREIGN KEY (sha_id) REFERENCES frontend_sha(code) DEFERRABLE INITIALLY DEFERRED
这里是详细解释的输出:
# explain (verbose, buffers, analyse) SELECT SUM(actual_cost) as cost, SUM(total_items) as num_items, processing_date FROM frontend_items WHERE chemical_id='0501012G0' GROUP BY processing_date;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=1415349.73..1415349.74 rows=1 width=16) (actual time=3048.551..3048.556 rows=17 loops=1)
Output: sum(actual_cost), sum(total_items), processing_date
Buffers: shared hit=141958 read=12725
-> Bitmap Heap Scan on public.frontend_items (cost=11797.55..1411446.84 rows=520385 width=16) (actual time=213.889..2834.911 rows=524644 loops=1)
Output: id, presentation_code, presentation_name, total_items, net_cost, actual_cost, quantity, processing_date, price_per_unit, chemical_id, pct_id, practice_id, sha_id
Recheck Cond: ((frontend_items.chemical_id)::text = '0501012G0'::text)
Buffers: shared hit=141958 read=12725
-> Bitmap Index Scan on frontend_items_chemical_id_varchar_pattern_ops_idx (cost=0.00..11667.46 rows=520385 width=0) (actual time=172.574..172.574 rows=524644 loops=1)
Index Cond: ((frontend_items.chemical_id)::text = '0501012G0'::text)
Buffers: shared hit=2 read=2012
Total runtime: 3049.177 ms
您有 724242 行,查询耗时 12592.499 毫秒。这是每行 0.017387 毫秒,即每秒 57514 行。你在抱怨什么?我认为您的查询非常快。普通 HDD 使用索引扫描仅支持每秒 65 - 200 行的速率,尽管位图索引/堆扫描速度更快。我想您会发现 PostgreSQL 正在使用最适合您情况的查询计划。
如果再次执行查询,速度会变快吗?那时缓存会很热,因此重复执行可能会更快。如果它没有变得更快,那么更多的内存不太可能有帮助。 PostgreSQL 的数据页大小为 8 KB,因此您最多访问 724242*8 KB = 5.5 GB 的数据,即数据应该适合您的 RAM。
编辑:问题的编辑版本中提到的第二个查询显示每秒 172000 行的性能。因此,如果数据缓存在 RAM 中,此类查询可能会变得更快。我会选择在 RAM 中拟合整个数据集的方法。 RAM 很便宜,但开发人员的时间很昂贵。
由于所选行是特定的,因此在这种情况下添加多列索引可能会有所帮助。它可能有助于推动计划者使用索引扫描而不是位图扫描。
CREATE INDEX ON frontend_items(chemical_id, processing_date, total_items, actual_cost);
请注意,这是一种可能,但在优化查询时值得尝试。
当您需要多个项目的值时,该过程可以更快
(它帮助我计算了从 12 小时到 12 分钟的非规范化值)。
您可以在单个查询中查询所有项目(chemical
字段)的值。它肯定不会比查询一个值快,但在许多情况下它也可能不会(渐近地)慢很多(table 对所有值只扫描一次)。
查询将如下所示:
SELECT chemical_id, SUM(actual_cost) as cost, SUM(total_items)
as num_items, processing_date FROM frontend_items
GROUP BY chemical_id, processing_date;
当我在 table 上使用约 800 万条记录对其进行测试时,我在约 0.5 秒内获得了单个查询 运行,并在 2 秒内查询了项目的聚合值。
您还可以将这些结果保存到非规范化字段并向用户显示这些结果(如果他们不关心稍微过时的值)。
这里是数据库新手。这是我的查询,我使用的是 Postgres 9.3.5:
=# explain analyse SELECT SUM(actual_cost) as cost, SUM(total_items)
as num_items, processing_date FROM frontend_items
WHERE chemical_id='0501013B0' GROUP BY processing_date;
这是查询计划:
HashAggregate (cost=1648624.91..1648624.92 rows=1 width=16) (actual time=12591.844..12591.848 rows=17 loops=1)
-> Bitmap Heap Scan on frontend_items (cost=14520.24..1643821.35 rows=640474 width=16) (actual time=254.841..12317.746 rows=724242 loops=1)
Recheck Cond: ((chemical_id)::text = '0501013B0'::text)
-> Bitmap Index Scan on frontend_items_chemical_id_varchar_pattern_ops_idx (cost=0.00..14360.12 rows=640474 width=0) (actual time=209.538..209.538 rows=724242 loops=1)
Index Cond: ((chemical_id)::text = '0501013B0'::text)
Total runtime: 12592.499 ms
如您所见,Bitmap Heap Scan
占用了大部分时间。有什么办法可以加快速度吗?
如果需要,我可以创建更多索引:我的数据几乎是只读的(它每月更新一次)。
鉴于我想要多个属性,我想我无能为力,除了支付足够的 RAM 以将整个数据库保存在内存中之外,但非常感谢您的建议。
我可以一次只查找其中一个属性,如果这样可以加快速度的话。
注意:我 运行 在配备 16GB RAM 和 SSD 的 Macbook 上进行此操作。我已将 shared_buffers
设置为 4GB,将 work_mem
设置为 40MB。我最终将使用具有 32GB RAM 和 SSD 的服务器。
更新:table 架构如下:
Column | Type | Modifiers
-------------------+-------------------------+--------------------------------------------------------------------
id | integer | not null default nextval('frontend_items_id_seq'::regclass)
presentation_code | character varying(15) | not null
presentation_name | character varying(1000) | not null
total_items | integer | not null
net_cost | double precision | not null
actual_cost | double precision | not null
quantity | double precision | not null
processing_date | date | not null
price_per_unit | double precision | not null
chemical_id | character varying(9) | not null
pct_id | character varying(3) | not null
practice_id | character varying(6) | not null
sha_id | character varying(3) | not null
Indexes:
"frontend_items_pkey" PRIMARY KEY, btree (id)
"frontend_items_45fff4c7" btree (sha_id)
"frontend_items_4e2e609b" btree (pct_id)
"frontend_items_528f368c" btree (processing_date)
"frontend_items_6ea07fe3" btree (practice_id)
"frontend_items_a69d813a" btree (chemical_id)
"frontend_items_b9b2c7ab" btree (presentation_code)
"frontend_items_chemical_id_varchar_pattern_ops_idx" btree (chemical_id varchar_pattern_ops)
"frontend_items_pct_code_id_488a8bbfb2bddc6d_like" btree (pct_id varchar_pattern_ops)
"frontend_items_practice_id_bbbafffdb2c2bf1_like" btree (practice_id varchar_pattern_ops)
"frontend_items_presentation_code_69403ee04fda6522_like" btree (presentation_code varchar_pattern_ops)
"frontend_items_presentation_code_varchar_pattern_ops_idx" btree (presentation_code varchar_pattern_ops)
Foreign-key constraints:
"front_chemical_id_4619f68f65c49a8_fk_frontend_chemical_bnf_code" FOREIGN KEY (chemical_id) REFERENCES frontend_chemical(bnf_code) DEFERRABLE INITIALLY DEFERRED
"frontend__practice_id_bbbafffdb2c2bf1_fk_frontend_practice_code" FOREIGN KEY (practice_id) REFERENCES frontend_practice(code) DEFERRABLE INITIALLY DEFERRED
"frontend_items_pct_id_30c06df242c3d1ba_fk_frontend_pct_code" FOREIGN KEY (pct_id) REFERENCES frontend_pct(code) DEFERRABLE INITIALLY DEFERRED
"frontend_items_sha_id_4fa0ca3c3b9b67f_fk_frontend_sha_code" FOREIGN KEY (sha_id) REFERENCES frontend_sha(code) DEFERRABLE INITIALLY DEFERRED
这里是详细解释的输出:
# explain (verbose, buffers, analyse) SELECT SUM(actual_cost) as cost, SUM(total_items) as num_items, processing_date FROM frontend_items WHERE chemical_id='0501012G0' GROUP BY processing_date;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=1415349.73..1415349.74 rows=1 width=16) (actual time=3048.551..3048.556 rows=17 loops=1)
Output: sum(actual_cost), sum(total_items), processing_date
Buffers: shared hit=141958 read=12725
-> Bitmap Heap Scan on public.frontend_items (cost=11797.55..1411446.84 rows=520385 width=16) (actual time=213.889..2834.911 rows=524644 loops=1)
Output: id, presentation_code, presentation_name, total_items, net_cost, actual_cost, quantity, processing_date, price_per_unit, chemical_id, pct_id, practice_id, sha_id
Recheck Cond: ((frontend_items.chemical_id)::text = '0501012G0'::text)
Buffers: shared hit=141958 read=12725
-> Bitmap Index Scan on frontend_items_chemical_id_varchar_pattern_ops_idx (cost=0.00..11667.46 rows=520385 width=0) (actual time=172.574..172.574 rows=524644 loops=1)
Index Cond: ((frontend_items.chemical_id)::text = '0501012G0'::text)
Buffers: shared hit=2 read=2012
Total runtime: 3049.177 ms
您有 724242 行,查询耗时 12592.499 毫秒。这是每行 0.017387 毫秒,即每秒 57514 行。你在抱怨什么?我认为您的查询非常快。普通 HDD 使用索引扫描仅支持每秒 65 - 200 行的速率,尽管位图索引/堆扫描速度更快。我想您会发现 PostgreSQL 正在使用最适合您情况的查询计划。
如果再次执行查询,速度会变快吗?那时缓存会很热,因此重复执行可能会更快。如果它没有变得更快,那么更多的内存不太可能有帮助。 PostgreSQL 的数据页大小为 8 KB,因此您最多访问 724242*8 KB = 5.5 GB 的数据,即数据应该适合您的 RAM。
编辑:问题的编辑版本中提到的第二个查询显示每秒 172000 行的性能。因此,如果数据缓存在 RAM 中,此类查询可能会变得更快。我会选择在 RAM 中拟合整个数据集的方法。 RAM 很便宜,但开发人员的时间很昂贵。
由于所选行是特定的,因此在这种情况下添加多列索引可能会有所帮助。它可能有助于推动计划者使用索引扫描而不是位图扫描。
CREATE INDEX ON frontend_items(chemical_id, processing_date, total_items, actual_cost);
请注意,这是一种可能,但在优化查询时值得尝试。
当您需要多个项目的值时,该过程可以更快 (它帮助我计算了从 12 小时到 12 分钟的非规范化值)。
您可以在单个查询中查询所有项目(chemical
字段)的值。它肯定不会比查询一个值快,但在许多情况下它也可能不会(渐近地)慢很多(table 对所有值只扫描一次)。
查询将如下所示:
SELECT chemical_id, SUM(actual_cost) as cost, SUM(total_items)
as num_items, processing_date FROM frontend_items
GROUP BY chemical_id, processing_date;
当我在 table 上使用约 800 万条记录对其进行测试时,我在约 0.5 秒内获得了单个查询 运行,并在 2 秒内查询了项目的聚合值。
您还可以将这些结果保存到非规范化字段并向用户显示这些结果(如果他们不关心稍微过时的值)。