2. Преимущества денормализации
• При правильном использовании –
повышение производительности
• Упрощение SQL-запросов
• При хранении документов – меньше
изменений в модели данных
4. Хранение данных в массивах - плюсы
• Исчезает JOIN в SQL запросе – быстрее
извлекаются данные
• При использовании GIN и GiST индексов –
быстрый поиск по значению массива
• Более простые SQL запросы
5. Хранение данных в массивах - минусы
• Нет готовой поддержки со стороны
ORM
• Большие массивы и частые апдейты –
большой overhead из-за MVCC
6. Пример: использование
промежуточной таблицы
SELECT
r.*
FROM
recording r JOIN recording_tag rt ON r.id = rt.recording JOIN
tag t ON rt.tag = t.id
WHERE
t.name = 'jazz';
7. План запроса
Nested Loop (cost=0.00..377.00 rows=21 width=124) (actual time=0.203.
-> Nested Loop (cost=0.00..21.47 rows=21 width=4) (actual time=0.1
-> Index Scan using tag_name_idx on tag t (cost=0.00..8.31 rows=
Index Cond: ((name)::text = 'jazz'::text)
-> Index Only Scan using recording_tag_tag_recording_idx on recor
Index Cond: (tag = t.id)
Heap Fetches: 0
-> Index Scan using recording_pkey on recording r (cost=0.00..16.9
Index Cond: (id = rt.recording)
Total runtime: 214.631 ms
9. План запроса
Bitmap Heap Scan on recording (cost=107.43..27372.8
Recheck Cond: (tags @> '{jazz}'::text[])
-> Bitmap Index Scan on recording_tags_idx (cost
Index Cond: (tags @> '{jazz}'::text[])
Total runtime: 49.235 ms
10. Массивы в качестве внешних ключей
• Давно просили
• В 9.2 скорее всего будет (патч в
статусе ready for committer)
11. Массивы в качестве внешних ключей
• Ключевое слово EACH перед именем
столбца
• Новый действия ON DELETE и ON UPDATE –
EACH CASCADE и EACH SET NULL
12. Пример
CREATE TABLE film (
id serial,
title text NOT NULL,
...
actor_ids integer[],
FOREIGN KEY (EACH actor_ids) REFERENCES actor (id)
ON DELETE EACH CASCADE ON UPDATE EACH CASCADE
);
13. Массивы и планировщик
• До 9.2 – константные оценки селективности
для операторов &&, @>, <@. Планировщик
“слеп”.
• В 9.2 – сбор специфичной статистики для
массивов. Более адекватные планы.
14. Пример
SELECT
*
FROM
artist_credit ac JOIN recording r ON ac.id =
r.artist_credit
WHERE
ac.artist_ids && '{40}'::int[];
15. До PostgreSQL 9.2
Hash Join (cost=6031.33..369386.51 rows=55296 width=175) (actual time
Hash Cond: (r.artist_credit = ac.id)
-> Seq Scan on recording r (cost=0.00..293679.83 rows=11059583 wid
-> Hash (cost=5996.72..5996.72 rows=2769 width=47) (actual time=0.
-> Bitmap Heap Scan on artist_credit ac (cost=69.80..5996.72
Recheck Cond: (artist_ids && '{40}'::integer[])
-> Bitmap Index Scan on artist_credit_artist_ids_idx
(cost=0.00..69.11 rows=2769 width=0) (actual time=0.050..0.050 rows=6
loops=1)
Index Cond: (artist_ids && '{40}'::integer[])
Total runtime: 48455.56 ms
16. PostgreSQL 9.2
Nested Loop (cost=16.21..20984.78 rows=559 width=171) (actual
-> Bitmap Heap Scan on artist_credit ac (cost=16.21..122.58
Recheck Cond: (artist_ids && '{40}'::integer[])
-> Bitmap Index Scan on artist_credit_artist_ids_idx
(cost=0.00..16.21 rows=28 width=0) (actual time=0.024..0.024
rows=6 loops=1)
Index Cond: (artist_ids && '{40}'::integer[])
-> Index Scan using recording_artist_credit_idx on recording
Index Cond: (artist_credit = ac.id)
Total runtime: 6.338 ms
17. Как это работает
Cобирается следующая статистика:
• Самые частые элементы массивов
• Их частоты
• Гистограмма числа уникальных элементов
Можно посмотреть в pg_stats.
19. Встроенная поддержка JSON в 9.2
• Тип json и фукнции row_to_json и
array_to_json.
• Извлекать данные из JSON нечем –
остается только собирать в нём
ответы.
20. Встроенная поддержка JSON в 9.2
• Можно собирать JSON-объект на стороне
СУБД
• Проще обработка результатов запроса
• Меньше размер ответа/число запросов
21. Пример
SELECT
row_to_json(x)
FROM
(SELECT
f.*,
(SELECT array_agg(a.*) FROM film_actor fa JOIN actor a ON
fa.actor_id = a.actor_id WHERE fa.film_id = f.film_id) AS actors
FROM
film f
LIMIT 1) x
23. Модуль-расширение PL/v8
• Javascript, как процедурный язык для
PostgreSQL
• На основе движка v8 от Google
• Можно делать любые манипуляции с
JSON-данными
24. PL/v8 – индексирование
• Пишем JS-функцию, которая извлекает
то, что нужно: значение или массив
• Строим expression index
• Делаем поиск по этому expression
25. Пример: хранимый документ
{
"title":"DOZEN LION",
"description":"A Taut Drama of a Cat And a Girl who must Defeat a
Frisbee in The Canadian Rockies",
"release_year":2006,
"rental_rate":4.99,
"rating":"NC-17",
"actors":["NATALIE HOPKINS","CAMERON WRAY","JADA RYDER","BEN
HARRIS","LAURA BRODY","KENNETH HOFFMAN"],
"categories":["Documentary"]
}
26. Пример: функция извлечения массива
CREATE OR REPLACE FUNCTION
get_text_array(key text, data text)
RETURNS text[] AS $$
return JSON.parse(data)[key];
$$ LANGUAGE plv8 IMMUTABLE STRICT;
27. Пример: функция извлечения числа
CREATE OR REPLACE FUNCTION
get_float(key text, data text)
RETURNS float AS $$
return JSON.parse(data)[key];
$$ LANGUAGE plv8 IMMUTABLE STRICT;
28. Пример: индексы
CREATE INDEX film_json_actors_idx ON
film_json USING gin
(get_text_array('actors', data));
CREATE INDEX film_json_rental_rate_idx
ON film_json
(get_float('rental_rate', data));
29. Пример: поисковый запрос
SELECT data
FROM film_json
WHERE
get_text_array('actors', data) @>
'{MARY KEITEL}'::text[] AND
get_float('rental_rate', data)
BETWEEN 4.9 AND 5.0;
30. Пример: план запроса
Bitmap Heap Scan on film_json (cost=20.92..60.10 rows=13 width
Recheck Cond: ((get_text_array('actors'::text, data) @> '{"MA
-> BitmapAnd (cost=20.92..20.92 rows=13 width=0) (actual ti
-> Bitmap Index Scan on film_json_actors_idx (cost=0.00..
Index Cond: (get_text_array('actors'::text, data) @>
-> Bitmap Index Scan on film_json_rental_rate_idx (cost=0
Index Cond: ((get_float('rental_rate'::text, data) >=
Total runtime: 0.490 ms
31. PL/v8 - ограничения
• JSON хранится как текст
• Каждый раз приходится делать
JSON.parse
• Нет универсального индекса для
документов
33. Range types (диапазонные типы)
• Пара, задающая верхнюю и нижнюю
границы диапазона
• Различные виды интервалов (a,b), (a,b],
[a,b), [a,b], (-∞,b), (-∞,b+, (a,+∞), *a,+∞),
(-∞;+∞), Ø (“empty”)
34. Применение range types
• Темпоральные данные (хранение
интервала актуальности данных)
• Данные с точностью
35. Индексирование range types
• Btree индекс поддерживает операторы >, <,
=. Как правило, не слишком полезен.
• GiST поддерживает &&, @>, <@ и т.д.
• Можно не хранить данные как range, а
просто строить expression index.
37. Пример: запрос
SELECT *
FROM price
WHERE
'2012-03-29'::timestamp >= actual_from AND
'2012-03-29'::timestamp < actual_to;
38. Пример: план запроса
Seq Scan on price (cost=0.00..204053.83 rows
Filter: (('2012-03-29 00:00:00'::timestamp
Rows Removed by Filter: 9995049
Total runtime: 2601.073 ms
40. Пример: план запроса
Bitmap Heap Scan on price (cost=127071.99..2
Recheck Cond: (('2012-03-29 00:00:00'::time
-> Bitmap Index Scan on price_actual_from_
Index Cond: (('2012-03-29 00:00:00'::
Total runtime: 566.923 ms
43. Пример: план запроса
Bitmap Heap Scan on price (cost=464.57..25929.50 rows=10
Recheck Cond: (tsrange(actual_from, actual_to) @> '2012
-> Bitmap Index Scan on price_actual_time_idx (cost=0
Index Cond: (tsrange(actual_from, actual_to) @> '
Total runtime: 80.287 ms
44. Перспективы развития
• Универсальное индексирование для JSON
• Сбор статистики для hstore, JSON и т.д.
• GiST индексы для массивов разных типов,
не только integer