Недавно мы разобрались, как PostgreSQL хранит данные на диске . Но как убедиться, что СУБД именно так и работает? Вдруг мы что-то упустили или недопоняли? Можно прочитать данные с диска с помощью утилиты hexdump
и посмотреть, что там реально записано. Но это трудоемко, ведь все битики придется декодировать вручную. К счастью, с PostgreSQL идет расширение pageinspect, которое может декодировать битики за нас.
Pageinspect имеет много возможностей. Все они описаны в документации . Помимо прочего, расширение умеет декодировать индексы Hash, B-Tree, GiST, и другие. В рамках этого поста мы не будем уходить в такие дебри и рассмотрим только базовый функционал.
Включаем pageinspect:
Для эксперимента воспользуемся той же таблицей, что и в прошлый раз:
«id» SERIAL PRIMARY KEY NOT NULL ,
«name» NAME NOT NULL ,
«phone» INT NOT NULL ) ;
INSERT INTO phonebook ( «name» , «phone» )
VALUES ( ‘Alice’ , 123 ) , ( ‘Bob’ , 456 ) , ( ‘Charlie’ , 789 ) ;
Заметьте, что таблица имеет первичный ключ. По нему автоматически будет создан B-Tree индекс.
Чтобы прочитать нулевую страницу таблицы phonebook и декодировать ее заголовок, выполним следующий запрос:
-[ RECORD 1 ]———
lsn | 0/12631C00
checksum | 0
flags | 0
lower | 36
upper | 7904
special | 8192
pagesize | 8192
version | 4
prune_xid | 0
Здесь get_raw_page()
считывает страницу в виде бинаря, а page_header()
декодирует ее заголовок и возвращает, как record
.
Также мы можем заглянуть в кортежи, их заголовки и содержимое ItemIdData[]:
FROM heap_page_items(get_raw_page(‘phonebook’, 0));
lp | lp_off | lp_flags | lp_len | t_ctid | t_infomask2
—-+———+———-+———+———+————-
1 | 8096 | 1 | 96 | (0,1) | 3
2 | 8000 | 1 | 96 | (0,2) | 3
3 | 7904 | 1 | 96 | (0,3) | 3
Здесь lp*
соответствуют элементам массива ItemIdData[]. Столбец lp
представляет собой индекс массива, а остальные — декодированные поля соответствующего элемента. Вспомним, что хранится в ItemIdData :
{
unsigned lp_off : 15 , /* смещение кортежа */
lp_flags : 2 , /* состояние элемента */
lp_len : 15 ; /* размер кортежа */
} ItemIdData ;
Pageinspect не отображает флаги в текстовом виде, но их можно подсмотреть в исходном коде :
#define LP_NORMAL 1
#define LP_REDIRECT 2
#define LP_DEAD 3
Видим, что у нас здесь три нормальных (lp_flags = 1) кортежа. Наконец, t_ctid
и t_infomask2
— это одноименные поля из структуры HeapTupleHeaderData . Напомню, что t_ctid
представляет собой ItemPointer на более новую версию кортежа. Более новых версий пока нет, поэтому все кортежи ссылаются сами на себя. Поле t_infomask2
может хранить следующую информацию:
#define HEAP_NATTS_MASK 0x07FF
#define HEAP_KEYS_UPDATED 0x2000
#define HEAP_HOT_UPDATED 0x4000
#define HEAP_ONLY_TUPLE 0x8000
Пока что оно просто говорит, что все кортежи имеют по три атрибута. Никаких других флагов не проставлено.
Что будет, если обновить одну из строк? Например:
UPDATE 1
=# SELECT lp, lp_off, lp_flags, lp_len, t_ctid, to_hex(t_infomask2)
FROM heap_page_items(get_raw_page(‘phonebook’, 0));
lp | lp_off | lp_flags | lp_len | t_ctid | to_hex
—-+———+———-+———+———+———
1 | 8096 | 1 | 96 | (0,4) | 4003
2 | 8000 | 1 | 96 | (0,2) | 3
3 | 7904 | 1 | 96 | (0,3) | 3
4 | 7808 | 1 | 96 | (0,4) | 8003
Поле t_ctid
первого кортежа теперь указывает на четвертый кортеж. Кроме того, у старого кортежа был проставлен флаг HEAP_HOT_UPDATED, а у нового — HEAP_ONLY_TUPLE. Другими словами, мы видим HOT-цепочку. Следовательно, индекс по первичному ключу не был перестроен.
Если теперь выполнить VACCUM:
VACUUM
=# SELECT lp, lp_off, lp_flags, lp_len, t_ctid, to_hex(t_infomask2)
FROM heap_page_items(get_raw_page(‘phonebook’, 0));
lp | lp_off | lp_flags | lp_len | t_ctid | to_hex
—-+———+———-+———+———+———
1 | 4 | 2 | 0 | |
2 | 8096 | 1 | 96 | (0,2) | 3
3 | 8000 | 1 | 96 | (0,3) | 3
4 | 7904 | 1 | 96 | (0,4) | 8003
… то СУБД поймет, что старый кортеж больше никому не виден, а значит, может быть удален. При этом в поле lp_flags
соответствующего ItemIdData проставляется LP_REDIRECT
, а поле lp_off
указывает на более новую версию кортежа. Таким образом, хоть кортеж и был удален, HOT-цепочка не рвется.
При желании можно придумать еще множество захватывающих экспериментов. Придумать и провести которые я предлагаю вам самостоятельно. Моей целью было лишь показать, что pageinspect прост и приятен в использовании. Он может быть использован для отладки кода, диагностики проблем на проде, а также служить средством изучения внутренностей СУБД. Звучит как что-то, чем полезно уметь пользоваться.
Дополнение: Внутренности PostgreSQL: механизм TOAST