Как именно определяется видимость строки?

10

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

CREATE TABLE vis (
  id serial,
  is_active boolean
);

INSERT INTO vis (is_active) VALUES (FALSE);

SELECT ctid, xmin, xmax, * FROM vis;

  ctid xmin  xmax  id  is_active 
───────┼─────┼──────┼────┼───────────
 (0,1) 2699     0   1  f

Когда мы обновляем его (поскольку флаг был установлен FALSEслучайно), он немного меняется:

UPDATE vis SET is_active = TRUE;

SELECT ctid, xmin, xmax, * FROM vis;

 ctid  xmin  xmax  id  is_active 
──────┼──────┼──────┼────┼───────────
(0,2)  2700     0   1  t

В соответствии с моделью MVCC, используемой PostgreSQL, была записана новая физическая строка, а старая недействительна (это видно из ctid). Новый все еще виден для всех последующих транзакций.

Теперь происходит интересная вещь, когда мы откатываем UPDATE:

BEGIN;

    UPDATE vis SET is_active = TRUE;

ROLLBACK;

SELECT ctid, xmin, xmax, * FROM vis;

 ctid   xmin  xmax  id  is_active 
───────┼──────┼──────┼────┼───────────
 (0,2)  2700  2702   1  t

Версия строки остается прежней, но теперь xmaxнастроена на что-то. Несмотря на это, последующие транзакции могут видеть эту (без изменений) строку.

Прочитав немного об этом, вы можете выяснить несколько вещей о видимости строк. Существует карта видимости , но она говорит только о том, видна ли вся страница - она ​​определенно не работает на уровне строк (кортежей). Затем есть журнал коммитов (он же clog) - но как Postgres выясняет, должен ли он посетить его?

Я решил взглянуть на биты информационной маски, чтобы понять, как на самом деле работает видимость. Чтобы увидеть их, проще всего использовать расширение pageinspect . Чтобы узнать, какие биты установлены, я создал таблицу для их хранения:

CREATE TABLE infomask (
  i_flag text,
  i_bits bit(16)
);

INSERT INTO infomask
VALUES 
('HEAP_HASNULL', x'0001'::bit(16)),
('HEAP_HASVARWIDTH', x'0002'::bit(16)),
('HEAP_HASEXTERNAL', x'0004'::bit(16)),
('HEAP_HASOID', x'0008'::bit(16)),
('HEAP_XMAX_KEYSHR_LOCK', x'0010'::bit(16)),
('HEAP_COMBOCID', x'0020'::bit(16)),
('HEAP_XMAX_EXCL_LOCK', x'0040'::bit(16)),
('HEAP_XMAX_LOCK_ONLY', x'0080'::bit(16)),
('HEAP_XMIN_COMMITTED', x'0100'::bit(16)),
('HEAP_XMIN_INVALID', x'0200'::bit(16)),
('HEAP_XMAX_COMMITTED', x'0400'::bit(16)),
('HEAP_XMAX_INVALID', x'0800'::bit(16)),
('HEAP_XMAX_IS_MULTI', x'1000'::bit(16)),
('HEAP_UPDATED', x'2000'::bit(16)),
('HEAP_MOVED_OFF', x'4000'::bit(16)),
('HEAP_MOVED_IN', x'8000'::bit(16)),
('HEAP_XACT_MASK', x'FFF0'::bit(16));

Затем проверил, что находится внутри моей visтаблицы - обратите внимание, что pageinspectпоказывает физическое содержимое кучи, поэтому возвращаются не только видимые строки:

SELECT t_xmin, t_xmax, string_agg(i_flag, ', ') FILTER (WHERE (t_infomask::bit(16) & i_bits)::integer::boolean)
  FROM heap_page_items(get_raw_page('vis', 0)),
       infomask
 GROUP BY t_xmin, t_xmax;

 t_xmin  t_xmax                       string_agg                      
────────┼────────┼──────────────────────────────────────────────────────
   2699    2700  HEAP_XMIN_COMMITTED, HEAP_XMAX_COMMITTED
   2700    2702  HEAP_XMIN_COMMITTED, HEAP_XMAX_INVALID, HEAP_UPDATED
   2702       0  HEAP_XMIN_INVALID, HEAP_XMAX_INVALID, HEAP_UPDATED

Из вышесказанного я понимаю, что первая версия ожила с транзакцией 2699, а затем была успешно заменена новой версией на 2700.
Затем на следующую, которая была активна с 2700 года, была предпринята попытка отката UPDATEв 2702 году, как видно из HEAP_XMAX_INVALID,
Последний никогда не был рожден, как показано HEAP_XMIN_INVALID.

Итак, исходя из вышесказанного, первый и последний случаи очевидны - они больше не видны для транзакции 2703 или выше.
Второй нужно где-то искать - полагаю, это журнал коммитов, иначе clog.

Чтобы еще больше усложнить проблемы, последующее UPDATEприводит к следующему:

 t_xmin  t_xmax                      string_agg                     
────────┼────────┼────────────────────────────────────────────────────
   2699    2700  HEAP_XMIN_COMMITTED, HEAP_XMAX_COMMITTED
   2702       0  HEAP_XMIN_INVALID, HEAP_XMAX_INVALID, HEAP_UPDATED
   2703       0  HEAP_XMAX_INVALID, HEAP_UPDATED
   2700    2703  HEAP_XMIN_COMMITTED, HEAP_UPDATED

Здесь я вижу уже двух кандидатов, которые могли быть видны. Итак, наконец, вот мои вопросы:

  • Мое предположение, что clogэто место, на которое нужно смотреть, чтобы определить видимость в этих случаях?
  • Какие флаги (или комбинации флагов) говорят системе посетить clog?
  • Есть ли способ проверить, что внутри clog? В clogболее ранних версиях Postgres есть упоминания о коррупции и подсказка, что можно создать поддельный файл вручную. Эта часть информации очень поможет в этом.
Dezso
источник

Ответы:

6

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

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

Какие флаги (или комбинация флагов) сообщают системе о необходимости посещения засорения?

Если нет heap_xmin_committedили heap_xmin_invalid, то вы должны посетить засорение, чтобы увидеть, каково было расположение xmin. Если транзакция все еще выполняется, строка не видна вам, и вы не можете установить какие-либо флаги. Если транзакция зафиксирована или откатана, вы устанавливаете heap_xmin_committedили heap_xmin_invalidсоответственно (если это удобно - это не обязательно), чтобы будущим людям не приходилось искать их.

Если xminон действителен и зафиксирован, а если xmaxне ноль, и нет heap_max_committedили heap_max_invalid, то вы должны посетить засорение, чтобы узнать, каково было расположение этой транзакции.

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

Я не знаю удобного способа сделать это. Вы можете использовать «od», чтобы подходящим способом вывести файлы засорения, чтобы проверить их, и выяснить, где проверять, используя макросы, определенные вsrc/backend/access/transam/clog.c

Я удивлен, что в PGXN нет расширений, которые бы сработали для вас, но я не смог их найти. Но я думаю, что это было бы не так уж и полезно, потому что вам действительно нужно это делать, пока ваш сервер не работает.

jjanes
источник
4

Взгляните на реализацию HeapTupleSatisfiesMVCC () : фактическая clogпроверка происходит в TransactionIdDidCommit () , но она вызывается только в том случае, если статус транзакции не может быть выведен из битов информационной маски (макрос HeapTupleHeaderXminCommitted () и друзья).

Я проследил доступ к pg_clogфункциям, TransactionDidCommit()а TransactionDidAbort()затем посмотрел, где они вызываются, и, похоже, единственное место в коде, касающемся вашего вопроса, находится в нем HeapTupleSatisfiesMVCC(). Из кода этой функции вы можете видеть, что фактический поиск засорения может произойти, только если кортежу не установлены соответствующие биты информационной маски: код начинается с проверки битов с помощью HeapTupleHeaderXminCommitted()et al. И поиск засорения происходит, только если биты не установлены.

Алекс
источник