Postgres: как «выбор счетчика (*)» может привести к появлению грязных блоков? - PullRequest
1 голос
/ 13 января 2020

Пытаясь оптимизировать некоторые запросы в 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 блоков.

Два вопроса:

  • Понимаю ли я это правильно?
  • Как операция «только для чтения» может загрязнить блоки?

1 Ответ

0 голосов
/ 13 января 2020

Чтобы ответить на это, я должен объяснить кое-что о PostgreSQL внутренностях.

В PostgreSQL строки никогда не обновляются на месте. Скорее каждый UPDATE создает новую версию строки. Точно так же DELETE не удаляет строку, но помечает ее как недействительную.

Каждая версия строки содержит идентификатор транзакции, которая ее создала, и транзакции, которая пометила ее как недействительную. Вместе эти идентификаторы транзакций определяют видимость версии строки.

COMMIT и ROLLBACK вообще не касаются таблицы, они только отмечают транзакцию, совершенную или прерванную в журнал фиксации .

Теперь запрос, который читает версию строки, должен обратиться к журналу фиксации, чтобы определить, может ли он увидеть версию строки или нет. Например, если транзакция, которая создала версию строки, откатывается, версия строки невидима.

Вы можете себе представить, что это создаст много трафика c в журнале фиксации, что снизит производительность , если не было никакой оптимизации на месте: если оператор, обращающийся к строке, обнаружит, что транзакция создания или удаления завершена, он установит так называемый бит подсказки в версии строки. Последующим читателям теперь не нужно больше просматривать журнал фиксации.

Ваш запрос первым прочитал некоторые строки в таблице, поэтому он установил эти биты подсказок. Эта модификация делает блок, содержащий версию строки, «грязным», то есть его необходимо записать в хранилище.

Это объясняет, как запросы на чтение могут закончить запись данных в PostgreSQL. По этой причине часто рекомендуется VACUUM таблица после массового изменения данных: это займет бремя установки битов подсказки от первого считывателя.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...