在尝试优化 Postgres 11 中的某些查询时,我偶然发现了这种我无法理解的行为。
> explain (analyze, buffers) select count(*) from events;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=2533599.69..2533599.70 rows=1 width=8) (actual time=113869.828..113869.828 rows=1 loops=1)
Buffers: shared hit=204077 read=2205033 dirtied=16112
I/O Timings: read=319766.985
-> Gather (cost=2533599.48..2533599.69 rows=2 width=8) (actual time=113869.814..113871.340 rows=3 loops=1)
Workers Planned: 2
Workers Launched: 2
Buffers: shared hit=204077 read=2205033 dirtied=16112
I/O Timings: read=319766.985
-> Partial Aggregate (cost=2532599.48..2532599.49 rows=1 width=8) (actual time=113866.031..113866.032 rows=1 loops=3)
Buffers: shared hit=204077 read=2205033 dirtied=16112
I/O Timings: read=319766.985
-> Parallel Seq Scan on events (cost=0.00..2507901.58 rows=9879158 width=0) (actual time=0.048..111664.011 rows=8055167 loops=3)
Buffers: shared hit=204077 read=2205033 dirtied=16112
I/O Timings: read=319766.985
Planning Time: 0.142 ms
Execution Time: 113871.415 ms
我的理解是我的 select count(*)
弄脏了 16112 个 block 。
两个问题:
- 我的理解正确吗?
- 只读操作如何使 block 变脏?
最佳答案
为了回答这个问题,我必须解释一些有关 PostgreSQL 内部结构的内容。
在 PostgreSQL 中,行永远不会就地更新。相反,每个 UPDATE
都会创建该行的一个新版本。同样,DELETE
不会删除该行,而是将其标记为无效。
每个行版本都带有创建它的事务的 ID 以及将其标记为无效的事务的 ID。这些事务 ID 共同决定行版本的可见性。
COMMIT
和 ROLLBACK
根本不接触表,它们仅在提交日志中标记事务已提交或中止。 p>
现在,读取行版本的查询必须查阅提交日志以确定它是否可以看到行版本。例如,如果创建行版本的事务被回滚,则行版本将不可见。
你可以想象,如果没有适当的优化,这会在提交日志上产生大量流量,这会损害性能:如果访问行的语句发现创建或删除事务已结束,它将在行版本上设置所谓的提示位。后续读者现在不必再查阅提交日志。
您的查询是第一个读取表中某些行的查询,因此它设置了这些提示位。此修改使得包含行版本的 block “脏”,即必须将其写入存储。
这解释了读取查询如何最终在 PostgreSQL 中写入数据。因此,在批量数据修改后VACUUM
表通常是一个好主意:它将承担第一个读取器设置提示位的责任。
关于sql - Postgres : how can a "select count(*)" cause dirty blocks?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59716940/