Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
НАРОДНЫЕ СРЕДСТВА
ОПТИМИЗАЦИИ ЗАПРОСОВ
В PostgreSQL
Писарев Николай
МЫ РАССМОТРИМ
● Основные моменты работы с РБД
● Что такое EXPLAIN и как с ним работать
● Индексы и их особенности
● Лайфхаки и хитрости
Основы работы с РБД
● Запись в таблице должна соответствовать объекту
● Правильно выбрать уровень нормализации
● Уделить немало времени на проектирование
● Структура БД должна быть гибкой
● Простота в поддержке жизненого цикла БД
● Правильно расставлять индексы,
не создавать бардак
● Строить запросы не выбирая все подряд (*),
только необходимое
● Приступать к оптимизации, когда действительно это
требуется
ORM vs SQl
●
ORM — удобно?
●
ORM — быстро?
Total runtime: (~)117.092 ms*
JAVA
@ManyToOne
@JoinColumn(name = "CATEGORY_ID")
public Category getCategory() {
return category;
}
PHP
$customers = Customer::find()
->where(['status' => TRUE])
->orderBy('id')
->limit(100, 10000);
* Подробный пример мы разберем ниже
☑
ORM vs SQL
●
SQL — удобно?
●
SQL — быстро?
Total runtime: (~)11.926 ms
SELECT * FROM (
SELECT * FROM posts
WHERE author = 'nick'
ORDER BY posts.publish
DESC LIMIT 10
) as posts
JOIN post_category
ON posts.category_id = category.id
ORDER BY posts.publish DESC LIMIT 10;
☒
* Подробный пример мы разберем ниже
Еще об ORM
● Позволяет представить запись в БД в виде объекта
● Удобнее понимать и легче писать чем SQL
● Поддержка и изменения не вызывают трудностей
● Сложные запросы иногда невозможно написать
● Трудно оптимизировать
● Иногда строится запрос, который не использует
индексы
А что с SQL?
● Сложные выборки, запросы
● Возможности оптимизации
● Функции и различные возможности SQL
● HighLoad — однозначно SQL (узкое место)
● Запрос с использованием индексов не всегда быстрый
● Замусоривает код (JAVA и многострочные литералы)
● Нет проверок на этапе компиляции
Народные средства оптимизации PostgreSQL
ANALYZE
ANALYZE считывается определённое количество
строк таблицы в базе данных, выбранных случайным
образом, и сохраняет результаты в системном
каталоге pg_statistic.
Затем планировщик запросов будет использовать эту
статистику для выбора эффективных планов
запросов.
=> ANALYZE VERBOSE;
WARNING: skipping "pg_statistic" --- only superuser or database owner can analyze it
WARNING: skipping "pg_type" --- only superuser or database owner can analyze it
INFO: analyzing "public.test"
INFO: "test": scanned 16669 of 16669 pages, containing 2000200 live rows
and 0 dead rows; 30000 rows in sample, 2000200 estimated total rows
VACUUM
● Очистка места (помечание), занимаемое
«мертвыми» кортежами
● По-умолчанию очищает все таблицы доступные
пользователю
● Без опции FULL может работать параллельно,
т. к. не требует исключительной блокировки
● With FULL работает медленно, требует блокировки
и возвращает освобожденное место операционной
системе.
● Autovacuum — демон очистки (VACUUM+ANALYZE)
● ! table bloating
Народные средства оптимизации PostgreSQL
Таблица TEST
=> CREATE TABLE test (id integer, text text);
=> INSERT INTO test
SELECT id, md5(random()::text)
FROM generate_series(1, 1000000) AS id;
=> d test
Table "public.test"
Column | Type | Modifiers
----------------+------------+-----------
column_1 | integer |
column_2 | text |
=> EXPLAIN SELECT * FROM test;
● Cost — у.е. для оценки затратности
операции.
1ое значение — затраты доступа к 1й строке
2ое значение — затраты для доступа ко
всем строкам.
● Rows — (~) количество строк при вызове
Seq Scan к этой таблице
● With — длина строки в байтах
Всё, что мы видели выше в выводе команды EXPLAIN —
ожидания планировщика.
=> EXPLAIN (ANALYZE) SELECT * FROM test;
● Actual time — реальное время в миллисекундах для
1ой и всех строк
● Rows — реальное количество строк полученных
● Loops — количество выполнений данной операции
● Plannig time — время выполнения EXPLAIN
● Execution time — общее время выполнения
● Heap Fetches — число реальных обращений к таблице
!!! DANGER !!!
EXPLAIN (ANALYZE) исполняет команды
=> EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM test;
● Buffers: shared read — количество блоков
считанных с диска;
● Buffers: shared hit — количество блоков,
считанных из кэша PostgreSQL.
CACHE
WHERE
=> EXPLAIN (ANALYZE)
SELECT * FROM test WHERE id
between 1500 and 1550;
● Индексов нет поэтому Seq Scan
● Каждая строка сравнивается с:
Filter: ((id >= 1500) AND (id <= 1550))
● Cost увеличилось
● Rows уменьшилось до ождаемого количества
Execution time: 222.979 ms
Index Scan
=> CREATE INDEX ON test(id);
=> EXPLAIN (ANALYZE)
SELECT * FROM test WHERE id
between 1500 and 1550;
● Теперь Index Scan using test_id_idx on test;
● Index Cond: ((id >= 1500) AND (id <= 1550))
Execution time: 0.127 ms
Seq Scan
A C D
1) С < 10
2) С < 10
3) С < 10
С < 10
Index Scan
A C D
С < 10
C
ИНДЕКСЫ
● Но что будет, если поменять условие
=> EXPLAIN (ANALYZE)
SELECT * FROM test WHERE id > 1550;
● Теперь Seq Scan on test
● Пришлось прочитать все строки, кроме
первых 1500..
● Время увеличилось, что не удивительно
Execution time: 678.852 ms
ИНДЕКСЫ
● А если выключить Seq Scan
=> SET enable_seqscan TO off;
● Теперь Index Scan using test_id_idx on test
● Но время запроса стало еще больше
Execution time: 749.952 ms
● И стоимость cost также увеличилась
Планировщик не дурак =)
Про индексы
● Индекс это дополнительная структура
данных (не SQL)
● Индексы требуют затраты на поддержание
● Замедляют обновление
● Замедляют репликацию
● Малая селективность — неэфективно
Индексы не панацея!
Index Only Scan
● EXPLAIN (ANALYZE)
SELECT id FROM test WHERE id < 450;
● Index Only Scan using test_id_idx on test
● Выбираем только поле id,
чтобы включить IOS
● Скорость очень большая
Execution time: 0.659 ms
Index Only Scan
A C D
С < 10
C
Visability
MAP
ИНДЕКСЫ ПО ТЕКСТУ
EXPLAIN (ANALYZE)
SELECT * FROM test WHERE text LIKE 'ab%';
● Seq Scan
After CREATE INDEX ON test(text);
● Также Seq Scan (211 ms), потому что UTF-8!
● Нужно использовать класс оператора
text_pattern_ops
CREATE INDEX ON test(text text_pattern_ops);
ИНДЕКСЫ ПО ТЕКСТУ
EXPLAIN (ANALYZE)
SELECT * FROM test WHERE text LIKE 'ab%';
● Bitmap Index Scan on test_text_idx1
● Сравниваем
Index Cond: (
(text ~>=~ 'ab'::text) AND (text ~<~ 'ac'::text)
)
● Далее Bitmap Heap Scan on test
проверяет существуют ли записи на самом
деле
Bitmap Index Scan
A C D
С < 10
C
1
1 1
1
1 0
Создание индексов
● Требуют блокировки при создании
● CONCURRENTLY создает в фоне, но долго (требует
2 прохода)
● Можно и нужно мониторить неиспользуемые
индексы (расходуются рессурсы и время)
● Можно находить дубликаты индексов
● Можно строить индексы по функциям, но
необходимо точное её повторение при запросе
● ! index bloating
Когда создавать индексы?
CREATE TABLE test5 (id integer PRIMARY KEY, v float8);
ACREATE INDEX test5_v_idx ON test5(v);
INSERT INTO test5
(SELECT id, random() FROM generate_series(1,1000000) id);
CREATE TABLE test5 (id integer PRIMARY KEY, v float8);
BINSERT INTO test5
(SELECT id, random() FROM generate_series(1,1000000) id);
CREATE INDEX test5_v_idx ON test5(v);
Когда создавать индексы?
CREATE TABLE a (id integer PRIMARY KEY, v float8); 1,991 ms
CREATE INDEX a_v_idx ON a(v); 0,506 ms
INSERT INTO a
(SELECT id, random() FROM
generate_series(1,1000000) id);
4909,127 ms
A = Total: 4911 ms
CREATE TABLE b (id integer PRIMARY KEY, v float8); 1,990 ms
INSERT INTO b
(SELECT id, random() FROM
generate_series(1,1000000) id);
938,852 ms
CREATE INDEX b_v_idx ON b(v); 1195,492 ms
B = Total: 2136 ms
Lifehack Show
Мониторим неиспользуемые индексы
SELECT schemaname || '.' || relname AS table, indexrelname AS index,
pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size, idx_scan as
index_scans
FROM pg_stat_user_indexes ui
JOIN pg_index i ON ui.indexrelid = i.indexrelid
WHERE NOT indisunique AND idx_scan < 50 AND pg_relation_size(relid) > 5 * 819
ORDER BY pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,
pg_relation_size(i.indexrelid) DESC;
table | index | index_size | index_scans
---------------------+---------------------------+---------------+-------------
public.test | test_text_idx | 56 MB | 0
public.test5 | test5_v_idx | 28 MB | 0
public.test6 | test6_v_idx | 21 MB | 0
public.test | test_text_idx1 | 56 MB | 3
public.test | test_id_idx | 21 MB | 36
Ищем дубликаты индексов
lk=> SELECT pg_size_pretty(SUM(pg_relation_size(idx))::BIGINT) AS SIZE,
(array_agg(idx))[1] AS idx1, (array_agg(idx))[2] AS idx2,
(array_agg(idx))[3] AS idx3, (array_agg(idx))[4] AS idx4
FROM ( SELECT indexrelid::regclass AS idx,
(indrelid::text ||E'n'|| indclass::text ||E'n'|| indkey::text ||E'n'||
COALESCE(indexprs::text,'')||E'n' || COALESCE(indpred::text,'')) AS KEY
FROM pg_index) sub
GROUP BY KEY HAVING COUNT(*)>1
ORDER BY SUM(pg_relation_size(idx)) DESC;
size | idx1 | idx2 | idx3 | idx4
---------+-----------------------+----------------------+------+------
32 kB | blocks_id_idx | blocks_id_idx1 | |
32 kB | blocks_type_idx1 | blocks_type_idx | |
32 kB | primary_key | ids | |
Оптимизация OFFSET
Ситуация:
SELECT …
FROM table1
JOIN table2 using (table2id)
JOIN table3 using (table3id)
WHERE
набор условий ТОЛЬКО по table1
Order by (набор полей table1) LIMIT ... OFFSET ...
Важно: сработает если соблюдается условие, что
логика выборки и offset реализуется в table1, а также
что присоединенные данные из таблиц table2 и table3
на запрос не влияют.
EXPLAIN ANALYZE
SELECT * FROM test
JOIN vals ON vals.test_id = test.id
WHERE val between 150 AND 9500
LIMIT 5 OFFSET 5000;
Execution time: 283.471 ms
EXPLAIN ANALYZE
SELECT * FROM ( SELECT * FROM test
WHERE val between 150 AND 9500
LIMIT 5 OFFSET 5000) AS test
JOIN vals ON vals.test_id = test.id;
Execution time: 4.079 ms
Оптимизация COUNT(*)
=> EXPLAIN ANALYZE SELECT count(*) FROM
cache_customers_rates;
Execution time: 18632.959 ms
=> EXPLAIN ANALYZE SELECT
(reltuples)::numeric FROM pg_class r WHERE
relkind='r' AND relname='cache_customers_rates';
Execution time: 0.079 ms
Получение строк в виде ROW
=> SELECT * FROM tasks;
id | type | status | params | out. | exc.
----+---------------+---------+------------------+------+-----
1 | refill_cache | new | {"threads":-1} | |
2 | refill_cache | new | {"threads":-1} | |
=> SELECT tasks FROM tasks;
tasks
--------------------------------------------------------------------
( 1, refill_cache, new, "{""threads"":-1}", "", "" )
( 2, refill_cache, new, "{""threads"":-1}", "", "" )
Получение строк в виде JSON
=> SELECT row_to_json(tasks) FROM tasks;
row_to_json
-----------------------------------------------------------------
{
"id":1,
"type":"refill_cache",
"status":"new",
"params":"{"threads":-1}",
"output":"",
"exception":""
}
Выбранные поля в виде JSON
=> SELECT row_to_json( t ) FROM ( SELECT id,
type FROM tasks) AS t;
row_to_json
------------------------------------------------
{ "id":1, "type":"refill_cache" }
{ "id":1, "type":"refill_cache" }
Данные в JSON ARRAY
=> SELECT
array_to_json( array_agg( row_to_json(tasks) ) )
FROM tasks;
[
{ "id":1, "type":"refill_cache", "status":"new",
"params":"{"threads":-1}", "output":"",
"exception":"" },
{ "id":2, "type":"refill_cache", "status":"new",
"params":"{"threads":-1}", "output":"",
"exception":"" }
]
OUTPUT to FILE
=> o tasks.json
=> SELECT array_to_json( array_agg( row_to_json(tasks) ) )
FROM tasks;
=> o
# cat tasks.json
array_to_json
-------------------------------------------------------------------------------
[{"id":1,"type":"refill_cache","status":"new","params":"{"thread
s":-1}","output":"","exception":""},
{"id":2,"type":"refill_cache","status":"new","params":"{"thread
s":-1}","output":"","exception":""}]
Народные средства оптимизации PostgreSQL
Полезная информация
●
https://wiki.postgresql.org/wiki/Index_Maintenance
Монторинг индексов
●
http://www.highload.ru/2013/abstracts/1170.html
Индексы
●
https://wiki.postgresql.org/wiki/Show_database_bloat
Table and index bloat
●
http://www.dalibo.org/_media/understanding_explain.pdf
EXPLAIN

More Related Content

Народные средства оптимизации PostgreSQL

  • 2. МЫ РАССМОТРИМ ● Основные моменты работы с РБД ● Что такое EXPLAIN и как с ним работать ● Индексы и их особенности ● Лайфхаки и хитрости
  • 3. Основы работы с РБД ● Запись в таблице должна соответствовать объекту ● Правильно выбрать уровень нормализации ● Уделить немало времени на проектирование ● Структура БД должна быть гибкой ● Простота в поддержке жизненого цикла БД ● Правильно расставлять индексы, не создавать бардак ● Строить запросы не выбирая все подряд (*), только необходимое ● Приступать к оптимизации, когда действительно это требуется
  • 4. ORM vs SQl ● ORM — удобно? ● ORM — быстро? Total runtime: (~)117.092 ms* JAVA @ManyToOne @JoinColumn(name = "CATEGORY_ID") public Category getCategory() { return category; } PHP $customers = Customer::find() ->where(['status' => TRUE]) ->orderBy('id') ->limit(100, 10000); * Подробный пример мы разберем ниже ☑
  • 5. ORM vs SQL ● SQL — удобно? ● SQL — быстро? Total runtime: (~)11.926 ms SELECT * FROM ( SELECT * FROM posts WHERE author = 'nick' ORDER BY posts.publish DESC LIMIT 10 ) as posts JOIN post_category ON posts.category_id = category.id ORDER BY posts.publish DESC LIMIT 10; ☒ * Подробный пример мы разберем ниже
  • 6. Еще об ORM ● Позволяет представить запись в БД в виде объекта ● Удобнее понимать и легче писать чем SQL ● Поддержка и изменения не вызывают трудностей ● Сложные запросы иногда невозможно написать ● Трудно оптимизировать ● Иногда строится запрос, который не использует индексы
  • 7. А что с SQL? ● Сложные выборки, запросы ● Возможности оптимизации ● Функции и различные возможности SQL ● HighLoad — однозначно SQL (узкое место) ● Запрос с использованием индексов не всегда быстрый ● Замусоривает код (JAVA и многострочные литералы) ● Нет проверок на этапе компиляции
  • 9. ANALYZE ANALYZE считывается определённое количество строк таблицы в базе данных, выбранных случайным образом, и сохраняет результаты в системном каталоге pg_statistic. Затем планировщик запросов будет использовать эту статистику для выбора эффективных планов запросов. => ANALYZE VERBOSE; WARNING: skipping "pg_statistic" --- only superuser or database owner can analyze it WARNING: skipping "pg_type" --- only superuser or database owner can analyze it INFO: analyzing "public.test" INFO: "test": scanned 16669 of 16669 pages, containing 2000200 live rows and 0 dead rows; 30000 rows in sample, 2000200 estimated total rows
  • 10. VACUUM ● Очистка места (помечание), занимаемое «мертвыми» кортежами ● По-умолчанию очищает все таблицы доступные пользователю ● Без опции FULL может работать параллельно, т. к. не требует исключительной блокировки ● With FULL работает медленно, требует блокировки и возвращает освобожденное место операционной системе. ● Autovacuum — демон очистки (VACUUM+ANALYZE) ● ! table bloating
  • 12. Таблица TEST => CREATE TABLE test (id integer, text text); => INSERT INTO test SELECT id, md5(random()::text) FROM generate_series(1, 1000000) AS id; => d test Table "public.test" Column | Type | Modifiers ----------------+------------+----------- column_1 | integer | column_2 | text |
  • 13. => EXPLAIN SELECT * FROM test; ● Cost — у.е. для оценки затратности операции. 1ое значение — затраты доступа к 1й строке 2ое значение — затраты для доступа ко всем строкам. ● Rows — (~) количество строк при вызове Seq Scan к этой таблице ● With — длина строки в байтах
  • 14. Всё, что мы видели выше в выводе команды EXPLAIN — ожидания планировщика. => EXPLAIN (ANALYZE) SELECT * FROM test; ● Actual time — реальное время в миллисекундах для 1ой и всех строк ● Rows — реальное количество строк полученных ● Loops — количество выполнений данной операции ● Plannig time — время выполнения EXPLAIN ● Execution time — общее время выполнения ● Heap Fetches — число реальных обращений к таблице !!! DANGER !!! EXPLAIN (ANALYZE) исполняет команды
  • 15. => EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM test; ● Buffers: shared read — количество блоков считанных с диска; ● Buffers: shared hit — количество блоков, считанных из кэша PostgreSQL. CACHE
  • 16. WHERE => EXPLAIN (ANALYZE) SELECT * FROM test WHERE id between 1500 and 1550; ● Индексов нет поэтому Seq Scan ● Каждая строка сравнивается с: Filter: ((id >= 1500) AND (id <= 1550)) ● Cost увеличилось ● Rows уменьшилось до ождаемого количества Execution time: 222.979 ms
  • 17. Index Scan => CREATE INDEX ON test(id); => EXPLAIN (ANALYZE) SELECT * FROM test WHERE id between 1500 and 1550; ● Теперь Index Scan using test_id_idx on test; ● Index Cond: ((id >= 1500) AND (id <= 1550)) Execution time: 0.127 ms
  • 18. Seq Scan A C D 1) С < 10 2) С < 10 3) С < 10 С < 10
  • 19. Index Scan A C D С < 10 C
  • 20. ИНДЕКСЫ ● Но что будет, если поменять условие => EXPLAIN (ANALYZE) SELECT * FROM test WHERE id > 1550; ● Теперь Seq Scan on test ● Пришлось прочитать все строки, кроме первых 1500.. ● Время увеличилось, что не удивительно Execution time: 678.852 ms
  • 21. ИНДЕКСЫ ● А если выключить Seq Scan => SET enable_seqscan TO off; ● Теперь Index Scan using test_id_idx on test ● Но время запроса стало еще больше Execution time: 749.952 ms ● И стоимость cost также увеличилась Планировщик не дурак =)
  • 22. Про индексы ● Индекс это дополнительная структура данных (не SQL) ● Индексы требуют затраты на поддержание ● Замедляют обновление ● Замедляют репликацию ● Малая селективность — неэфективно Индексы не панацея!
  • 23. Index Only Scan ● EXPLAIN (ANALYZE) SELECT id FROM test WHERE id < 450; ● Index Only Scan using test_id_idx on test ● Выбираем только поле id, чтобы включить IOS ● Скорость очень большая Execution time: 0.659 ms
  • 24. Index Only Scan A C D С < 10 C Visability MAP
  • 25. ИНДЕКСЫ ПО ТЕКСТУ EXPLAIN (ANALYZE) SELECT * FROM test WHERE text LIKE 'ab%'; ● Seq Scan After CREATE INDEX ON test(text); ● Также Seq Scan (211 ms), потому что UTF-8! ● Нужно использовать класс оператора text_pattern_ops CREATE INDEX ON test(text text_pattern_ops);
  • 26. ИНДЕКСЫ ПО ТЕКСТУ EXPLAIN (ANALYZE) SELECT * FROM test WHERE text LIKE 'ab%'; ● Bitmap Index Scan on test_text_idx1 ● Сравниваем Index Cond: ( (text ~>=~ 'ab'::text) AND (text ~<~ 'ac'::text) ) ● Далее Bitmap Heap Scan on test проверяет существуют ли записи на самом деле
  • 27. Bitmap Index Scan A C D С < 10 C 1 1 1 1 1 0
  • 28. Создание индексов ● Требуют блокировки при создании ● CONCURRENTLY создает в фоне, но долго (требует 2 прохода) ● Можно и нужно мониторить неиспользуемые индексы (расходуются рессурсы и время) ● Можно находить дубликаты индексов ● Можно строить индексы по функциям, но необходимо точное её повторение при запросе ● ! index bloating
  • 29. Когда создавать индексы? CREATE TABLE test5 (id integer PRIMARY KEY, v float8); ACREATE INDEX test5_v_idx ON test5(v); INSERT INTO test5 (SELECT id, random() FROM generate_series(1,1000000) id); CREATE TABLE test5 (id integer PRIMARY KEY, v float8); BINSERT INTO test5 (SELECT id, random() FROM generate_series(1,1000000) id); CREATE INDEX test5_v_idx ON test5(v);
  • 30. Когда создавать индексы? CREATE TABLE a (id integer PRIMARY KEY, v float8); 1,991 ms CREATE INDEX a_v_idx ON a(v); 0,506 ms INSERT INTO a (SELECT id, random() FROM generate_series(1,1000000) id); 4909,127 ms A = Total: 4911 ms CREATE TABLE b (id integer PRIMARY KEY, v float8); 1,990 ms INSERT INTO b (SELECT id, random() FROM generate_series(1,1000000) id); 938,852 ms CREATE INDEX b_v_idx ON b(v); 1195,492 ms B = Total: 2136 ms
  • 32. Мониторим неиспользуемые индексы SELECT schemaname || '.' || relname AS table, indexrelname AS index, pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size, idx_scan as index_scans FROM pg_stat_user_indexes ui JOIN pg_index i ON ui.indexrelid = i.indexrelid WHERE NOT indisunique AND idx_scan < 50 AND pg_relation_size(relid) > 5 * 819 ORDER BY pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST, pg_relation_size(i.indexrelid) DESC; table | index | index_size | index_scans ---------------------+---------------------------+---------------+------------- public.test | test_text_idx | 56 MB | 0 public.test5 | test5_v_idx | 28 MB | 0 public.test6 | test6_v_idx | 21 MB | 0 public.test | test_text_idx1 | 56 MB | 3 public.test | test_id_idx | 21 MB | 36
  • 33. Ищем дубликаты индексов lk=> SELECT pg_size_pretty(SUM(pg_relation_size(idx))::BIGINT) AS SIZE, (array_agg(idx))[1] AS idx1, (array_agg(idx))[2] AS idx2, (array_agg(idx))[3] AS idx3, (array_agg(idx))[4] AS idx4 FROM ( SELECT indexrelid::regclass AS idx, (indrelid::text ||E'n'|| indclass::text ||E'n'|| indkey::text ||E'n'|| COALESCE(indexprs::text,'')||E'n' || COALESCE(indpred::text,'')) AS KEY FROM pg_index) sub GROUP BY KEY HAVING COUNT(*)>1 ORDER BY SUM(pg_relation_size(idx)) DESC; size | idx1 | idx2 | idx3 | idx4 ---------+-----------------------+----------------------+------+------ 32 kB | blocks_id_idx | blocks_id_idx1 | | 32 kB | blocks_type_idx1 | blocks_type_idx | | 32 kB | primary_key | ids | |
  • 34. Оптимизация OFFSET Ситуация: SELECT … FROM table1 JOIN table2 using (table2id) JOIN table3 using (table3id) WHERE набор условий ТОЛЬКО по table1 Order by (набор полей table1) LIMIT ... OFFSET ... Важно: сработает если соблюдается условие, что логика выборки и offset реализуется в table1, а также что присоединенные данные из таблиц table2 и table3 на запрос не влияют.
  • 35. EXPLAIN ANALYZE SELECT * FROM test JOIN vals ON vals.test_id = test.id WHERE val between 150 AND 9500 LIMIT 5 OFFSET 5000; Execution time: 283.471 ms EXPLAIN ANALYZE SELECT * FROM ( SELECT * FROM test WHERE val between 150 AND 9500 LIMIT 5 OFFSET 5000) AS test JOIN vals ON vals.test_id = test.id; Execution time: 4.079 ms
  • 36. Оптимизация COUNT(*) => EXPLAIN ANALYZE SELECT count(*) FROM cache_customers_rates; Execution time: 18632.959 ms => EXPLAIN ANALYZE SELECT (reltuples)::numeric FROM pg_class r WHERE relkind='r' AND relname='cache_customers_rates'; Execution time: 0.079 ms
  • 37. Получение строк в виде ROW => SELECT * FROM tasks; id | type | status | params | out. | exc. ----+---------------+---------+------------------+------+----- 1 | refill_cache | new | {"threads":-1} | | 2 | refill_cache | new | {"threads":-1} | | => SELECT tasks FROM tasks; tasks -------------------------------------------------------------------- ( 1, refill_cache, new, "{""threads"":-1}", "", "" ) ( 2, refill_cache, new, "{""threads"":-1}", "", "" )
  • 38. Получение строк в виде JSON => SELECT row_to_json(tasks) FROM tasks; row_to_json ----------------------------------------------------------------- { "id":1, "type":"refill_cache", "status":"new", "params":"{"threads":-1}", "output":"", "exception":"" }
  • 39. Выбранные поля в виде JSON => SELECT row_to_json( t ) FROM ( SELECT id, type FROM tasks) AS t; row_to_json ------------------------------------------------ { "id":1, "type":"refill_cache" } { "id":1, "type":"refill_cache" }
  • 40. Данные в JSON ARRAY => SELECT array_to_json( array_agg( row_to_json(tasks) ) ) FROM tasks; [ { "id":1, "type":"refill_cache", "status":"new", "params":"{"threads":-1}", "output":"", "exception":"" }, { "id":2, "type":"refill_cache", "status":"new", "params":"{"threads":-1}", "output":"", "exception":"" } ]
  • 41. OUTPUT to FILE => o tasks.json => SELECT array_to_json( array_agg( row_to_json(tasks) ) ) FROM tasks; => o # cat tasks.json array_to_json ------------------------------------------------------------------------------- [{"id":1,"type":"refill_cache","status":"new","params":"{"thread s":-1}","output":"","exception":""}, {"id":2,"type":"refill_cache","status":"new","params":"{"thread s":-1}","output":"","exception":""}]