чем jsonb_array_elements?
структура примерно такая:
data:jsonb
[
{"channel":"1","items": [...]},
{"channel":"2","items": [...]},
{"channel":"3","items": [...]}
]
Надо выбрать все items у первого channel. Сейчас использую два jsonb_array_elements - вначале по исходному массиву, потом по items. Поскольку значение всегда пишется по порядку, попытался упростить до
jsonb_array_elements(data -> 0 ->'items')
но получилось раз в 20 медленнее.
грубо - 600 исходных строк разворачиваются в ~18000 в первом случае за 80-100 мс, во втором за 1800 мс
UPD: подробности https://pastebin.com/ZY4FcSDC
Хочу привести объяснение того загадочного случая, что произошел в январе, когда jsonb_array_elements() оказалась значительно быстрее jsonb -> 0. На эту проблему легко наткнуться при работе с большими jsonb. Для иллюстрации создадим таблицу с одной с строкой, содержащей jsonb массив из 10 000 элементов. Общий размер таблицы -- всего лишь 64 КБ (сам jsonb -- 120 КБ, сжат до 34 КБ в TOAST-таблице; TOAST-индекс -- 16 КБ). CREATE TABLE test AS SELECT jsonb_build_object( 'id', 1, 'a', (SELECT jsonb_agg(i) FROM generate_series(1, 10000) i) ) jb Извлечение самих элементов массива работает достаточно быстро: EXPLAIN (ANALYZE, BUFFERS) SELECT jsonb_array_elements(jb -> 'a') FROM test; ProjectSet (...) Buffers: shared hit=7 ... Execution Time: 4.104 ms Но стоит добавить к ним jb -> 'id', и все сразу замедляется аж в 300 раз, а количество прочитанных блоков возрастает в 8570 раз: EXPLAIN (ANALYZE, BUFFERS) SELECT jb -> 'id', jsonb_array_elements(jb -> 'a') FROM test; ProjectSet (...) Buffers: shared hit=60013 ... Execution Time: 1267.9 ms Причина этого заключается в том, что для каждого элемента массива, возвращаемого jsonb_array_elements(), вычисляется jb -> 'id'. А jb у нас достаточно большой и попадает в TOAST, откуда он будет 10000 раз извлекаться. Каждое такое извлечение приводит к чтению 6 блоков (2 -- TOAST-индекс, 4 -- TOAST-таблица). При первом вызове jsonb_array_elements() тоже делается detoast. Такое же поведение мы получим, если будем извлекать несколько ключей (или путей) из большого jsonb, и для каждого ключа тоже будет делаться detoast (`SELECT jb->'key1', jb->'key2', ...`). К сожалению, PostgreSQL пока совсем не умеет кэшировать результат detoast и каждый доступ к большому jsonb в функциях и операторах приводит к detoast. Проблему можно решить довольно общим методом, сделав принудительный detoast содержимого jsonb через применение к нему какого-то пустого оператора и materialized CTE. EXPLAIN (ANALYZE, BUFFERS) WITH detoasted AS MATERIALIZED ( SELECT jb || '{}' AS jb FROM test ) SELECT jb -> 'id', jsonb_array_elements(jb -> 'a') FROM detoasted; ProjectSet (...) Buffers: shared hit=7 ... Execution Time: 6.467 ms
Обсуждают сегодня