在提出最近的问题时,我的 EXPLAIN ANALYZE 输出中出现了一些神秘的启动时间组件。我玩得更远了,发现如果我删除正则表达式WHERE
子句,启动时间会下降到接近 0。
我运行了以下 bash 脚本作为测试:
for i in $(seq 1 10)
do
if (( $RANDOM % 2 == 0 ))
then
echo "Doing plain count"
psql -e -c "EXPLAIN ANALYZE SELECT count(*) FROM ui_events_v2"
else
echo "Doing regex count"
psql -e -c "EXPLAIN ANALYZE SELECT count(*) FROM ui_events_v2 WHERE page ~ 'foo'"
fi
done
第一个查询返回约 3000 万行的计数,第二个查询仅计算 7 行。它们在 RDS 中的 PG 12.3 只读副本上运行,其他活动最少。正如我所料,这两个版本所花费的时间大致相同。下面是一些用 过滤的输出grep
:
Doing plain count
-> Parallel Seq Scan on ui_events_v2 (cost=0.00..3060374.07 rows=12632507 width=0) (actual time=0.086..38622.215 rows=10114306 loops=3)
Doing regex count
-> Parallel Seq Scan on ui_events_v2 (cost=0.00..3091955.34 rows=897 width=0) (actual time=16856.679..41398.062 rows=2 loops=3)
Doing plain count
-> Parallel Seq Scan on ui_events_v2 (cost=0.00..3060374.07 rows=12632507 width=0) (actual time=0.162..39454.499 rows=10114306 loops=3)
Doing plain count
-> Parallel Seq Scan on ui_events_v2 (cost=0.00..3060374.07 rows=12632507 width=0) (actual time=0.036..39213.171 rows=10114306 loops=3)
Doing regex count
-> Parallel Seq Scan on ui_events_v2 (cost=0.00..3091955.34 rows=897 width=0) (actual time=12711.308..40015.734 rows=2 loops=3)
Doing plain count
-> Parallel Seq Scan on ui_events_v2 (cost=0.00..3060374.07 rows=12632507 width=0) (actual time=0.244..39277.683 rows=10114306 loops=3)
Doing regex count
^CCancel request sent
所以,有几个问题:
正则表达式扫描中“实际时间”的启动组件是什么,为什么它要大得多?(10-20 秒对 0-1 秒)
虽然“成本”和“时间”不是可比单位,但规划者似乎认为启动成本在所有情况下都应该为 0——这是被愚弄了吗?
为什么这些策略看起来不同?两个计划都提到
Partial Aggregate
,但是正则表达式查询说实际行是2
,但是普通版本说实际行是 ~1000 万(我猜这是 2 个工人和 1 个领导者之间的某种平均值,总和为 ~3000 万)。如果我必须自己实现这个,我可能会将几个count(*)
操作的结果相加,而不是合并行和计数 - 计划是否表明它是如何做到这一点的?
所以我不隐藏任何东西,下面是每个查询计划的完整版本:
EXPLAIN ANALYZE SELECT count(*) FROM ui_events_v2
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=3093171.59..3093171.60 rows=1 width=8) (actual time=39156.499..39156.499 rows=1 loops=1)
-> Gather (cost=3093171.37..3093171.58 rows=2 width=8) (actual time=39156.356..39157.850 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=3092171.37..3092171.38 rows=1 width=8) (actual time=39154.405..39154.406 rows=1 loops=3)
-> Parallel Seq Scan on ui_events_v2 (cost=0.00..3060587.90 rows=12633390 width=0) (actual time=0.033..38413.690 rows=10115030 loops=3)
Planning Time: 7.968 ms
Execution Time: 39157.942 ms
(8 rows)
EXPLAIN ANALYZE SELECT count(*) FROM ui_events_v2 WHERE page ~ 'foo'
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=3093173.83..3093173.84 rows=1 width=8) (actual time=39908.495..39908.495 rows=1 loops=1)
-> Gather (cost=3093173.61..3093173.82 rows=2 width=8) (actual time=39908.408..39909.848 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial Aggregate (cost=3092173.61..3092173.62 rows=1 width=8) (actual time=39906.317..39906.318 rows=1 loops=3)
-> Parallel Seq Scan on ui_events_v2 (cost=0.00..3092171.37 rows=897 width=0) (actual time=17250.058..39906.308 rows=2 loops=3)
Filter: (page ~ 'foo'::text)
Rows Removed by Filter: 10115028
Planning Time: 0.803 ms
Execution Time: 39909.921 ms
(10 rows)
秘诀在于
Rows Removed by Filter: 10115028
:顺序扫描需要 17 秒才能找到并返回第一个结果行。
优化器不知道第一行通过过滤器需要多长时间。由于它对计划的质量没有任何影响,它只是将启动成本设置为 0。
两个计划的工作方式相同:三个进程中的每一个都扫描表的三分之一并计算行数(
Partial Aggregate
返回 1 行的那个)。在第一种情况下,每个工人计算 1000 万行。这三个数字在
Finalize Aggregate
舞台上被收集和相加。