Виміряйте розмір рядка таблиці PostgreSQL


83

У мене є таблиця PostgreSQL. select *дуже повільно, а select idприємно і швидко. Я думаю, що може бути, що розмір ряду дуже великий, і це потребує певного часу для транспортування, або це може бути якийсь інший фактор.

Мені потрібні всі поля (або майже всі), тому вибір лише підмножини не є швидким виправленням. Вибір потрібних полів все ще повільний.

Ось моя схема таблиці мінус назви:

integer                  | not null default nextval('core_page_id_seq'::regclass)
character varying(255)   | not null
character varying(64)    | not null
text                     | default '{}'::text
character varying(255)   | 
integer                  | not null default 0
text                     | default '{}'::text
text                     | 
timestamp with time zone | 
integer                  | 
timestamp with time zone | 
integer                  | 

Розмір текстового поля може бути будь-якого розміру. Але все-таки не більше кількох кілобайт в гіршому випадку.

Запитання

  1. Чи є щось з цього приводу, що кричить "божевільно неефективний"?
  2. Чи є спосіб виміряти розмір сторінки в командному рядку Postgres, щоб допомогти мені налагодити це?

Насправді ... одна колонка - 11 Мб. Це пояснить це, я думаю. То чи є спосіб зробити, length(*)а не просто length(field)? Я знаю, що знаки не байти, але мені потрібно лише приблизно значення.
Джо

Відповіді:


101

Q2: way to measure page size

PostgreSQL надає ряд функцій розміру об'єкта бази даних . Я упакував найцікавіші з цього запиту і додав кілька функцій доступу до статистики внизу. (Додатковий модуль pgstattuple надає ще корисні функції.)

Це покаже, що різні методи вимірювання "розміру рядка" призводять до дуже різних результатів. Все залежить від того, що ви хочете точно виміряти.

Для цього запиту потрібен Postgres 9.3 або пізнішої версії . Старіші версії див. Нижче.

Використовуючи VALUESвираз у LATERALпідзапиті , щоб уникнути написання обчислень для кожного рядка.

Замініть public.tbl(двічі) назви за таблицею, що відповідають вимогам схеми, щоб отримати компактне уявлення про зібрану статистику щодо розміру рядків. Ви можете зафіксувати це у функції plpgsql для багаторазового використання, введіть назву таблиці як параметр і використовуйте EXECUTE...

SELECT l.metric, l.nr AS "bytes/ct"
     , CASE WHEN is_size THEN pg_size_pretty(nr) END AS bytes_pretty
     , CASE WHEN is_size THEN nr / NULLIF(x.ct, 0) END AS bytes_per_row
FROM  (
   SELECT min(tableoid)        AS tbl      -- = 'public.tbl'::regclass::oid
        , count(*)             AS ct
        , sum(length(t::text)) AS txt_len  -- length in characters
   FROM   public.tbl t                     -- provide table name *once*
   ) x
 , LATERAL (
   VALUES
      (true , 'core_relation_size'               , pg_relation_size(tbl))
    , (true , 'visibility_map'                   , pg_relation_size(tbl, 'vm'))
    , (true , 'free_space_map'                   , pg_relation_size(tbl, 'fsm'))
    , (true , 'table_size_incl_toast'            , pg_table_size(tbl))
    , (true , 'indexes_size'                     , pg_indexes_size(tbl))
    , (true , 'total_size_incl_toast_and_indexes', pg_total_relation_size(tbl))
    , (true , 'live_rows_in_text_representation' , txt_len)
    , (false, '------------------------------'   , NULL)
    , (false, 'row_count'                        , ct)
    , (false, 'live_tuples'                      , pg_stat_get_live_tuples(tbl))
    , (false, 'dead_tuples'                      , pg_stat_get_dead_tuples(tbl))
   ) l(is_size, metric, nr);

Результат:

              метричні | байт / ct | bytes_pretty | bytes_per_row
----------------------------------- + ---------- + --- ----------- + ---------------
 core_relation_size | 44138496 | 42 Мб | 91
 vidibility_map | 0 | 0 байт | 0
 free_space_map | 32768 | 32 кБ | 0
 table_size_incl_toast | 44179456 | 42 Мб | 91
 indexes_size | 33128448 | 32 Мб | 68
 total_size_incl_toast_and_indexes | 77307904 | 74 Мб | 159
 live_rows_in_text_representation | 29987360 | 29 Мб | 62
 ------------------------------ | | |
 рядок_рахунок | 483424 | |
 live_tuples | 483424 | |
 dead_tuples | 2677 | |

Для старих версій (Postgres 9.2 або новіші):

WITH x AS (
   SELECT count(*)               AS ct
        , sum(length(t::text))   AS txt_len  -- length in characters
        , 'public.tbl'::regclass AS tbl      -- provide table name as string
   FROM   public.tbl t                       -- provide table name as name
   ), y AS (
   SELECT ARRAY [pg_relation_size(tbl)
               , pg_relation_size(tbl, 'vm')
               , pg_relation_size(tbl, 'fsm')
               , pg_table_size(tbl)
               , pg_indexes_size(tbl)
               , pg_total_relation_size(tbl)
               , txt_len
             ] AS val
        , ARRAY ['core_relation_size'
               , 'visibility_map'
               , 'free_space_map'
               , 'table_size_incl_toast'
               , 'indexes_size'
               , 'total_size_incl_toast_and_indexes'
               , 'live_rows_in_text_representation'
             ] AS name
   FROM   x
   )
SELECT unnest(name)                AS metric
     , unnest(val)                 AS "bytes/ct"
     , pg_size_pretty(unnest(val)) AS bytes_pretty
     , unnest(val) / NULLIF(ct, 0) AS bytes_per_row
FROM   x, y

UNION ALL SELECT '------------------------------', NULL, NULL, NULL
UNION ALL SELECT 'row_count', ct, NULL, NULL FROM x
UNION ALL SELECT 'live_tuples', pg_stat_get_live_tuples(tbl), NULL, NULL FROM x
UNION ALL SELECT 'dead_tuples', pg_stat_get_dead_tuples(tbl), NULL, NULL FROM x;

Той самий результат.

Q1: anything inefficient?

Ви можете оптимізувати порядок стовпців, щоб зберегти кілька байтів у рядку, витрачених на даний момент для вирівнювання.

integer                  | not null default nextval('core_page_id_seq'::regclass)
integer                  | not null default 0
character varying(255)   | not null
character varying(64)    | not null
text                     | default '{}'::text
character varying(255)   | 
text                     | default '{}'::text
text                     |
timestamp with time zone |
timestamp with time zone |
integer                  |
integer                  |

Це економить від 8 до 18 байт у рядку. Я називаю це «стовпчик тетрису» . Деталі:

Також врахуйте:


Ваш фрагмент до 9.3 кидає ділення на нуль, якщо таблиця порожня. Я насправді хотів використати версію 9.3+, але помилково вибрав неправильну і довелося витратити кілька годин на її виправлення ... Тепер я не можу відпустити весь цей час. Замініть , unnest(val) / ctйого, , (LEAST(unnest(val), unnest(val) * ct)) / (ct - 1 + sign(ct))і воно не кинеться. Обґрунтування полягає в тому, що, коли ctє 0, valбуде замінено 0і ctбуде замінено 1.
GuiRitter

1
@GuiRitter: Дякую, що вказали. Однак я застосував більш просте виправлення. Також деякі загальні оновлення під час роботи, але запит залишається тим самим.
Ервін Брандштеттер

35

Наближення розміру рядка, включаючи вміст TOAST 'ed, легко отримати, запитуючи довжину TEXT-репрезентації всього рядка:

SELECT octet_length(t.*::text) FROM tablename AS t WHERE primary_key=:value;

Це близьке наближення до кількості байтів, які будуть отримані на стороні клієнта при виконанні:

SELECT * FROM tablename WHERE primary_key=:value;

... припускаючи, що абонент запиту вимагає результатів у текстовому форматі, що і робить більшість програм (можливий бінарний формат, але це не варто проблем у більшості випадків).

Ця ж методика може бути застосована для пошуку Nрядків "найбільшого тексту" tablename:

SELECT primary_key, octet_length(t.*::text) FROM tablename AS t
   ORDER BY 2 DESC LIMIT :N;

Відмінний спосіб швидко отримати деякі оцінки при роботі з великими даними (наприклад, більшість розмірів рядків лежить у стовпцях, що зберігаються в тості змінної довжини), хороша ідея!
fgblomqvist

14

Є кілька речей, які могли б статися. Взагалі я сумніваюся, що довжина - це проксимальна проблема. Я підозрюю, що натомість у вас є проблеми, пов'язані з тривалістю.

Ви кажете, що текстові поля можуть досягати кількох к. Рядок не може перевищувати 8 Кб в основному сховищі, і, ймовірно, ваші великі текстові поля були ТОСТУВАННЯ або переміщені з основного сховища в розширене сховище в окремих файлах. Це робить ваше основне сховище швидшим (тому вибір ідентифікатора насправді швидше, оскільки доступ до меншої кількості сторінок дисків), але вибір * стає повільнішим, оскільки є більше випадкових вводу-виводу.

Якщо ваш загальний розмір рядків все ще знаходиться нижче 8 к, ви можете спробувати змінити налаштування пам’яті. Однак я хотів би попередити, що ви можете отримати погані речі під час вставлення негабаритного атрибута в основний сховище, тому краще не торкатися цього, якщо вам цього не потрібно, а якщо це потрібно, встановити відповідні обмеження за допомогою перевірки обмежень. Тож перевезення, мабуть, не єдине. Це може бути збірка багатьох, багатьох полів, які вимагають випадкових зчитувань. Велика кількість випадкових зчитувань також може спричинити пропуски кеш-пам'яті, а велика кількість необхідної пам'яті може вимагати матеріалізації на диску, а велика кількість широких рядків, якщо з'єднання присутнє (і є таке, якщо TOAST задіяний), може знадобитися дорожче приєднатися до моделей тощо.

Перше, на що я хотів би звернути увагу, - це вибрати менше рядків і подивитися, чи це допомагає. Якщо це працює, ви можете спробувати додати ще більше оперативної пам’яті на сервер, але я б почав і побачив, де продуктивність починає падати через зміни плану і кеш спочатку пропускає.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.