Что извлекается с диска во время запроса?

13

Довольно простой вопрос, возможно, где-то ответили, но я не могу сформировать правильный поисковый вопрос для Google ...

Влияет ли количество столбцов в конкретной таблице на производительность запроса при выполнении запросов к подмножеству этой таблицы?

Например, если таблица Foo имеет 20 столбцов, но мой запрос выбирает только 5 из этих столбцов, влияет ли наличие 20 (скажем, 10) столбцов на производительность запроса? Предположим для простоты, что все, что содержится в предложении WHERE, включено в эти 5 столбцов.

Я обеспокоен использованием буферного кэша Postgres в дополнение к дисковому кэшу операционной системы. Я очень плохо понимаю физический дизайн хранилища Postgres. Таблицы хранятся на нескольких страницах (по умолчанию 8 КБ на страницу), но я не совсем понимаю, как оттуда устроены кортежи. Достаточно ли умен PG, чтобы получать с диска только те данные, которые содержат эти 5 столбцов?

Jmoney38
источник
Вы говорите о загрузке 50 байтов, но не оставшихся 150. Ваш диск, вероятно, читает с большим шагом, чем это!
Andomar
Откуда вы получаете эти цифры?
Jmoney38

Ответы:

14

Физическое хранилище для строк описано в документации по разметке страницы базы данных . Содержимое столбца для одной и той же строки хранится на одной и той же странице диска, за исключением заметного содержимого TOAST (слишком большого для размещения на странице). Содержимое извлекается последовательно в каждой строке, как объяснено:

Чтобы прочитать данные, вам нужно изучить каждый атрибут по очереди. Сначала проверьте, является ли поле пустым в соответствии с нулевым растровым изображением. Если это так, переходите к следующему. Затем убедитесь, что у вас есть правильное выравнивание. Если это поле с фиксированной шириной, то все байты просто помещаются.

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

Теперь случай TOAST: когда отдельное поле превышает ~ 2 КБ, движок сохраняет содержимое поля в отдельной физической таблице. Он также вступает в игру, когда вся строка не помещается на странице (8 КБ по умолчанию): некоторые поля перемещаются в хранилище TOAST. Док говорит:

Если это поле переменной длины (attlen = -1), то это немного сложнее. Все типы данных переменной длины имеют общую структуру заголовка struct varlena, которая включает в себя общую длину хранимого значения и некоторые биты флага. В зависимости от флагов данные могут быть либо встроенными, либо в таблице TOAST; это тоже может быть сжато

Содержимое TOAST не извлекается, когда оно явно не требуется, поэтому его влияние на общее количество извлекаемых страниц невелико (несколько байтов на столбец). Это объясняет результаты в ответе @ dezso.

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

Даниэль Верите
источник
Это обалденный ответ. Именно то, что я ищу. Спасибо.
Jmoney38
1
Хороший ресурс, который я нашел в отношении структуры строк (pageinspect и некоторые примеры использования) здесь .
Jmoney38
9

Ответ Даниэля фокусируется на стоимости чтения отдельных строк. В этом контексте: Помещение NOT NULLстолбцов фиксированного размера в первую очередь в вашей таблице немного помогает. Помещение соответствующих столбцов первым (те, которые вы запрашиваете) немного помогает. Минимизация заполнения (из-за выравнивания данных), играя тетрис выравнивания с вашими столбцами, может немного помочь. Но самый важный эффект пока не упомянут, особенно для больших столов.

Очевидно, что из-за дополнительных столбцов строка занимает больше места на диске, поэтому на одной странице данных умещается меньше строк (по умолчанию 8 КБ). Отдельные строки расположены на нескольких страницах. Механизм базы данных обычно должен извлекать целые страницы, а не отдельные строки . Не имеет значения, являются ли отдельные строки несколько меньше или больше - до тех пор, пока нужно прочитать одинаковое количество страниц.

Если запрос извлекает (относительно) небольшую часть большой таблицы, где строки распределены более или менее случайным образом по всей таблице, поддерживаемой индексом, это приведет к примерно одинаковому количеству чтений страниц, с небольшим вниманием в размер строки. Нерелевантные столбцы не будут сильно тормозить вас в таком (редком) случае.

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

Для больших баз данных обычно недостаточно оперативной памяти, чтобы хранить все это в кэш-памяти. Большие строки занимают больше кеша, больше конфликтов, меньше обращений к кешу, больше дискового ввода-вывода. А чтение с диска обычно намного дороже. Меньше так с SSD, но существенная разница остается. Это добавляет к вышеупомянутому пункту о чтениях страницы.

Это может иметь значение, а может и не иметь значения, если не относящиеся к делу столбцы имеют значение TOAST. Соответствующие столбцы также могут быть TOAST-ed, возвращая большую часть того же эффекта.

Эрвин Брандштеттер
источник
1

Небольшой тест:

CREATE TABLE test2 (
    id serial PRIMARY KEY,
    num integer,
    short_text varchar(32),
    longer_text varchar(1000),
    long_long_text text
);

INSERT INTO test2 (num, short_text, longer_text, long_long_text)
SELECT i, lpad('', 32, 'abcdefeghji'), lpad('', 1000, 'abcdefeghji'), lpad('', (random() * 10000)::integer, 'abcdefeghji')
FROM generate_series(1, 10000) a(i);

ANALYZE test2;

SELECT * FROM test2;
[...]
Time: 1091.331 ms

SELECT num FROM test2;
[...]
Time: 21.310 ms

Ограничение запроса до первых 250 строк ( WHERE num <= 250) приводит к 34,539 мс и 8,343 мс соответственно. Выбор всего, кроме long_long_textэтого ограниченного набора, приводит к 18,432 мс. Это показывает, что с вашей точки зрения PG достаточно умен.

Dezso
источник
Ну, я, конечно, ценю вклад. Тем не менее, я не могу с уверенностью сказать, что этот тестовый сценарий доказывает то, что я первоначально предложил. Есть несколько вопросов. Во-первых, когда вы впервые запускаете «SELECT * FROM test2», это должно было заполнить ваш общий буферный кеш. Этот запрос занял бы гораздо больше времени для извлечения с диска. Таким образом, второй запрос теоретически был бы намного быстрее, потому что он был бы извлечен из кеша SB. Но я согласен, что он «предлагает», чтобы PG выбирал только те строки, которые ему нужны, на основе ваших последующих тестов / сравнений.
Jmoney38
Вы правы, этот тест (будучи простым) имеет свои недостатки. Если у меня будет достаточно времени, я постараюсь охватить и их.
Дезсо