48
Как читать и интерпретировать вывод команды EXPLAIN ? Алексей Ермаков [email protected]

#RuPostgresLive 4: как писать и читать сложные SQL-запросы

Embed Size (px)

Citation preview

Page 1: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

Как читать и интерпретироватьвывод команды EXPLAIN ?

Алексей Ермаков[email protected]

Page 2: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

2 Как ускорить запрос?

• “Очень долгий, очень пристальный и очень задумчивый взгляд” назапрос

• EXPLAIN – основной инструмент анализа запросов

dataegret.com

Page 3: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

2 Как ускорить запрос?

• “Очень долгий, очень пристальный и очень задумчивый взгляд” назапрос

• EXPLAIN – основной инструмент анализа запросов

dataegret.com

Page 4: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

3 Как ускорить запрос?

• EXPLAIN без параметров показывает план запроса, но не выполняет1запрос и не показывает статистику выполнения запроса

1на самом деле может выполнить immutable/stable хранимки, но ведь они у вас ничегоне меняют, правда?

dataegret.com

Page 5: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

4 План запроса #1

explain select * from posts join categories on posts.category_id = categories.id whereposts.rating < 15;--------------------------------------------------------------------------------------------Nested Loop (cost=0.43..16.47 rows=1 width=32)

-> Index Scan using posts_rating_idx on posts (cost=0.29..8.30 rows=1 width=28)Index Cond: (rating < 15)

-> Index Only Scan using categories_pkey on categories (cost=0.14..8.16 rows=1 width=4)Index Cond: (id = posts.category_id)

dataegret.com

Page 6: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

5 План запроса #2

explain select * from posts join categories on posts.category_id = categories.id whereposts.rating < 35;Hash Join (cost=27.84..117.61 rows=601 width=32)

Hash Cond: (posts.category_id = categories.id)-> Bitmap Heap Scan on posts (cost=12.94..94.46 rows=601 width=28)

Recheck Cond: (rating < 35)-> Bitmap Index Scan on posts_rating_idx (cost=0.00..12.79 rows=601 width=0)

Index Cond: (rating < 35)-> Hash (cost=13.64..13.64 rows=100 width=4)

-> Index Only Scan using categories_pkey on categories (cost=0.14..13.64 rows=100 width=4)

dataegret.com

Page 7: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

6 План запроса #2

explain select * from posts join categories on posts.category_id = categories.id whereposts.rating < 35;Hash Join (cost=27.84..117.61 rows=601 width=32)

Hash Cond: (posts.category_id = categories.id)-> Bitmap Heap Scan on posts (cost=12.94..94.46 rows=601 width=28)

Recheck Cond: (rating < 35)-> Bitmap Index Scan on posts_rating_idx (cost=0.00..12.79 rows=601 width=0)

Index Cond: (rating < 35)-> Hash (cost=13.64..13.64 rows=100 width=4)

-> Index Only Scan using categories_pkey on categories (cost=0.14..13.64 rows=100 width=4)

dataegret.com

Page 8: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

7 План запроса

• Это дерево• Вышестоящие узлы запрашивают данные у нижестоящих• План запроса не меняется по мере выполнения, даже если оказалосьчто ожидания расходятся с реальностью на 6 порядков

• База не хранит историю неудачных или медленных планов запросов.Так что, если если запрос получает неверный план - он будетполучать его воспроизводимо2

2если не меняются настройки базы, распределение данных, размеры таблиц ииндексов, не используется geqo

dataegret.com

Page 9: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

8 Мы любим ORM

select website0_.websiteId as websiteI1_304_0_,website0_.websiteTitleType as websiteT2_304_0_,website0_.websiteUrl as websiteU3_304_0_,website0_.websiteLocalArticlePath as websiteL4_304_0_,... (еще 14кб текста)from Website website0_ where website0_.websiteId=$1

• Иногда план запроса читать проще, нежели сам запрос

dataegret.com

Page 10: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

8 Мы любим ORM

select website0_.websiteId as websiteI1_304_0_,website0_.websiteTitleType as websiteT2_304_0_,website0_.websiteUrl as websiteU3_304_0_,website0_.websiteLocalArticlePath as websiteL4_304_0_,... (еще 14кб текста)from Website website0_ where website0_.websiteId=$1

• Иногда план запроса читать проще, нежели сам запрос

dataegret.com

Page 11: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

9 Виды элементарных операций

Методы извлечения данных• seq scan – последовательное чтение таблицы• index scan – random io (чтение индекса + чтение таблицы)• index only scan – random io (чтение только3 индекса)• bitmap index scan + bitmap heap scan – компромисс между seqscan/index scan, возможность использования нескольких индексов вOR/AND условиях

• CTE scan – чтение из CTE (блокWITH)• function scan – чтение строк из функции

3Возможно и чтение таблицы, зависит от состояния visibility mapdataegret.com

Page 12: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

10 Виды элементарных операций

Методы соединения данных• nested loop – оптимален для небольших наборов данных• hash join – оптимален для больших наборов данных• merge join – оптимален для больших наборов данных, в случае, еслиони отсортированы

dataegret.com

Page 13: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

11 Nested loop

for each outer row:for each inner row:

if join condition is true:output combined row

dataegret.com

Page 14: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

12 Виды элементарных операций

Методы обработки данных• sort – сортирует данные в памяти или на диске, есть более быстрыйtopN вариант

• limit – ограничивает выборку• aggregate – используется в агрегирующих функциях• hash aggregate – используется в группировках• unique – отбрасывает дубликаты из отсортированных выборок• gather – используется для объединения данных с различныхворкеров при одновременном выполнении

dataegret.com

Page 15: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

13 Характеристики каждой операции

explain select * from pg_database;QUERY PLAN

-----------------------------------------------------------Seq Scan on pg_database (cost=0.00..0.16 rows=6 width=271)(1 row)

Seq Scan тип операцииon pg_database объект, с которым проводится операцияcost=0.00..0.16 стоимость операции (startup..total cost)

rows=6 ожидаемое число строкwidth=271 средняя ширина строки в байтах

dataegret.com

Page 16: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

14 startup..total cost

explain select * from posts order by id limit 5;QUERY PLAN

--------------------------------------------------------------------------------------Limit (cost=0.29..0.46 rows=5 width=28)

-> Index Scan using posts_pkey on posts (cost=0.29..347.29 rows=10000 width=28)(2 rows)

• 0.29 + (347.29 - 0.29)*5/10000 = 0.4635

dataegret.com

Page 17: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

14 startup..total cost

explain select * from posts order by id limit 5;QUERY PLAN

--------------------------------------------------------------------------------------Limit (cost=0.29..0.46 rows=5 width=28)

-> Index Scan using posts_pkey on posts (cost=0.29..347.29 rows=10000 width=28)(2 rows)

• 0.29 + (347.29 - 0.29)*5/10000 = 0.4635

dataegret.com

Page 18: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

15 rows*width

• rows*width у корневого узла дает примерный порядок объемарезультата в байтах

• если запрос часто вызывается и при этом запрашивает большоечисло данных – то легко можно забить сеть между базой иприложением

• передача данных по сети может занимать большее время, чем времявыполнения запроса

• не всегда стоит запрашивать все поля, что есть, особенно если ониширокие и никак не используются

dataegret.com

Page 19: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

16 Опции команды EXPLAIN

EXPLAIN (ANALYZE,VERBOSE,COSTS,BUFFERS,TIMING4) select * from t1;QUERY PLAN

-------------------------------------------------------------------Seq Scan on public.t1 (cost=0.00..104424.80 rows=10000000 width=8)(actual time=0.218..2316.688 rows=10000000 loops=1)

Output: f1, f2Buffers: shared read=44248I/O Timings: read=322.7145

Planning time: 0.024 msExecution time: 3852.588 ms

4COSTS и TIMING опции включены по-умолчанию5I/O Timings отображается, когда track_io_timing включен

dataegret.com

Page 20: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

17 План запроса #3

Gather (cost=100.57..1338637.12 rows=2346550 width=1526) (actual time=4489.134..4855.942 rows=79061 loops=1)

Workers Planned: 4 Workers Launched: 4

-> Nested Loop (cost=0.57..1103882.12 rows=2346550 width=1526)

(actual time=4482.217..4810.097 rows=15812 loops=5)

-> Parallel Seq Scan on deferred_report r (cost=0.00..97294.45 rows=1345386 width=8)

(actual time=0.026..702.618 rows=1098890 loops=5)

Filter: (("timestamp" < ’2017-02-26 21:00:00’::timestamp without time zone) AND (proxy_id = 37) AND (status = ’pending’::deferred_report_status))

Rows Removed by Filter: 2709566

-> Index Scan using transactions_pkey on transactions t (cost=0.57..0.74 rows=1 width=1526)

(actual time=0.004..0.004 rows=0 loops=5494451)

Index Cond: (id = r.tx_id)

Filter: (status = ’ok’::status)

Rows Removed by Filter: 1

Planning time: 0.736 ms

Execution time: 4861.460 ms

dataegret.com

Page 21: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

18 Filter

-> Parallel Seq Scan on deferred_report r (cost=0.00..97294.45 rows=1345386 width=8)(actual time=0.026..702.618 rows=1098890 loops=5)

Filter: (("timestamp" < ’2017-02-26 21:00:00’::timestamp without time zone) AND (proxy_id = 37) AND (status = ’pending’::deferred_report_status))Rows Removed by Filter: 2709566

• Оправдано ли использование seq scan в данном случае?

• worker извлек 10988901098890+2709566 ≈ 28% строк

• вероятно да, bitmap index scan может и был бы быстрее, но в 9.6 его еще нельзяраспараллелить по воркерам

dataegret.com

Page 22: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

18 Filter

-> Parallel Seq Scan on deferred_report r (cost=0.00..97294.45 rows=1345386 width=8)(actual time=0.026..702.618 rows=1098890 loops=5)

Filter: (("timestamp" < ’2017-02-26 21:00:00’::timestamp without time zone) AND (proxy_id = 37) AND (status = ’pending’::deferred_report_status))Rows Removed by Filter: 2709566

• Оправдано ли использование seq scan в данном случае?• worker извлек 1098890

1098890+2709566 ≈ 28% строк

• вероятно да, bitmap index scan может и был бы быстрее, но в 9.6 его еще нельзяраспараллелить по воркерам

dataegret.com

Page 23: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

18 Filter

-> Parallel Seq Scan on deferred_report r (cost=0.00..97294.45 rows=1345386 width=8)(actual time=0.026..702.618 rows=1098890 loops=5)

Filter: (("timestamp" < ’2017-02-26 21:00:00’::timestamp without time zone) AND (proxy_id = 37) AND (status = ’pending’::deferred_report_status))Rows Removed by Filter: 2709566

• Оправдано ли использование seq scan в данном случае?• worker извлек 1098890

1098890+2709566 ≈ 28% строк• вероятно да, bitmap index scan может и был бы быстрее, но в 9.6 его еще нельзя

распараллелить по воркерам

dataegret.com

Page 24: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

19 Filter

-> Index Scan using transactions_pkey on transactions t (cost=0.57..0.74 rows=1 width=1526)(actual time=0.004..0.004 rows=0 loops=5494451)

Index Cond: (id = r.tx_id)Filter: (status = ’ok’::status)Rows Removed by Filter: 1

• Используется ли оптимальный индекс в данном случае?

• Большинство строк отбрасываются по условию status = ‘ok’::status• Вероятно нет, может быть тут более уместен индекс по (id, status) или частичный индекс по idwhere status = ‘ok’

dataegret.com

Page 25: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

19 Filter

-> Index Scan using transactions_pkey on transactions t (cost=0.57..0.74 rows=1 width=1526)(actual time=0.004..0.004 rows=0 loops=5494451)

Index Cond: (id = r.tx_id)Filter: (status = ’ok’::status)Rows Removed by Filter: 1

• Используется ли оптимальный индекс в данном случае?• Большинство строк отбрасываются по условию status = ‘ok’::status

• Вероятно нет, может быть тут более уместен индекс по (id, status) или частичный индекс по idwhere status = ‘ok’

dataegret.com

Page 26: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

19 Filter

-> Index Scan using transactions_pkey on transactions t (cost=0.57..0.74 rows=1 width=1526)(actual time=0.004..0.004 rows=0 loops=5494451)

Index Cond: (id = r.tx_id)Filter: (status = ’ok’::status)Rows Removed by Filter: 1

• Используется ли оптимальный индекс в данном случае?• Большинство строк отбрасываются по условию status = ‘ok’::status• Вероятно нет, может быть тут более уместен индекс по (id, status) или частичный индекс по idwhere status = ‘ok’

dataegret.com

Page 27: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

20 Инструменты, позволяющие визуализировать план запроса

• https://explain.depesz.com/• http://tatiyants.com/pev/

dataegret.com

Page 28: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

21 explain.depesz.com

dataegret.com

Page 29: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

22 Filter

WHERE dispatch_time + INTERVAL ’1 hour’ > now()WHERE (DATE(o.created) >= ’2017-03-23’ AND DATE(o.created) <= ’2017-04-21’)

• конструкции такого вида не могут использовать обычный индекс по соответствующему полю• необходимо привести их к виду индексируемое_поле индексируемый_оператор выражение

dataegret.com

Page 30: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

23 Опция buffers

-> Index Only Scan using user_balance_id_type on user_balance ub (cost=0.00..0.56 rows=1 width=8)(actual time=0.031..1.453 rows=1 loops=29721)

Index Cond: ((id = o.user_balance_id) AND (type = 1))Heap Fetches: 7192482Buffers: shared hit=7608576

• Что-то пошло не так...

• Читаем 760857629721 = 256 страниц, чтобы извлечь одну строку

• Вероятно, очень большой bloat в таблице/индексе• Длинные транзакции? Не справляется автовакуум?• Отстающая реплика с hot_standby_feedback = on !

dataegret.com

Page 31: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

23 Опция buffers

-> Index Only Scan using user_balance_id_type on user_balance ub (cost=0.00..0.56 rows=1 width=8)(actual time=0.031..1.453 rows=1 loops=29721)

Index Cond: ((id = o.user_balance_id) AND (type = 1))Heap Fetches: 7192482Buffers: shared hit=7608576

• Что-то пошло не так...• Читаем 7608576

29721 = 256 страниц, чтобы извлечь одну строку

• Вероятно, очень большой bloat в таблице/индексе• Длинные транзакции? Не справляется автовакуум?• Отстающая реплика с hot_standby_feedback = on !

dataegret.com

Page 32: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

23 Опция buffers

-> Index Only Scan using user_balance_id_type on user_balance ub (cost=0.00..0.56 rows=1 width=8)(actual time=0.031..1.453 rows=1 loops=29721)

Index Cond: ((id = o.user_balance_id) AND (type = 1))Heap Fetches: 7192482Buffers: shared hit=7608576

• Что-то пошло не так...• Читаем 7608576

29721 = 256 страниц, чтобы извлечь одну строку• Вероятно, очень большой bloat в таблице/индексе

• Длинные транзакции? Не справляется автовакуум?• Отстающая реплика с hot_standby_feedback = on !

dataegret.com

Page 33: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

23 Опция buffers

-> Index Only Scan using user_balance_id_type on user_balance ub (cost=0.00..0.56 rows=1 width=8)(actual time=0.031..1.453 rows=1 loops=29721)

Index Cond: ((id = o.user_balance_id) AND (type = 1))Heap Fetches: 7192482Buffers: shared hit=7608576

• Что-то пошло не так...• Читаем 7608576

29721 = 256 страниц, чтобы извлечь одну строку• Вероятно, очень большой bloat в таблице/индексе• Длинные транзакции? Не справляется автовакуум?

• Отстающая реплика с hot_standby_feedback = on !

dataegret.com

Page 34: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

23 Опция buffers

-> Index Only Scan using user_balance_id_type on user_balance ub (cost=0.00..0.56 rows=1 width=8)(actual time=0.031..1.453 rows=1 loops=29721)

Index Cond: ((id = o.user_balance_id) AND (type = 1))Heap Fetches: 7192482Buffers: shared hit=7608576

• Что-то пошло не так...• Читаем 7608576

29721 = 256 страниц, чтобы извлечь одну строку• Вероятно, очень большой bloat в таблице/индексе• Длинные транзакции? Не справляется автовакуум?• Отстающая реплика с hot_standby_feedback = on !

dataegret.com

Page 35: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

24 I/O timings

-> Bitmap Heap Scan on delivery fi (cost=872967.27..4196621.37 rows=32425255 width=36)

(actual time=30842.138..37818.082 rows=6674571 loops=1)

Recheck Cond: ((datetime_start >= ’2017-05-11 00:00:00’::timestamp without time zone) AND (datetime_end <= ’2017-05-18 06:00:00’::timestamp without time zone))

Filter: ((NOT is_deleted) OR (qty > 0))

Rows Removed by Filter: 60545

Heap Blocks: exact=667323

Buffers: shared hit=47584 read=827816 dirtied=1 written=2292

I/O Timings: read=30152.376 write=42.134

-> Bitmap Index Scan on delivery_datetime_start_datetime_end_idx (cost=0.00..864860.96 rows=32514577 width=0) (actual time=30602.293..30602.293 rows=6812447 loops=1)

Index Cond: ((datetime_start >= ’2017-05-11 00:00:00’::timestamp without time zone) AND (datetime_end <= ’2017-05-18 06:00:00’::timestamp without time zone))

Buffers: shared hit=80 read=207997

I/O Timings: read=27315.687

• 30c из 37с ушло на чтение с диска• Отфильтровалось относительно мало строк

dataegret.com

Page 36: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

24 I/O timings

-> Bitmap Heap Scan on delivery fi (cost=872967.27..4196621.37 rows=32425255 width=36)

(actual time=30842.138..37818.082 rows=6674571 loops=1)

Recheck Cond: ((datetime_start >= ’2017-05-11 00:00:00’::timestamp without time zone) AND (datetime_end <= ’2017-05-18 06:00:00’::timestamp without time zone))

Filter: ((NOT is_deleted) OR (qty > 0))

Rows Removed by Filter: 60545

Heap Blocks: exact=667323

Buffers: shared hit=47584 read=827816 dirtied=1 written=2292

I/O Timings: read=30152.376 write=42.134

-> Bitmap Index Scan on delivery_datetime_start_datetime_end_idx (cost=0.00..864860.96 rows=32514577 width=0) (actual time=30602.293..30602.293 rows=6812447 loops=1)

Index Cond: ((datetime_start >= ’2017-05-11 00:00:00’::timestamp without time zone) AND (datetime_end <= ’2017-05-18 06:00:00’::timestamp without time zone))

Buffers: shared hit=80 read=207997

I/O Timings: read=27315.687

• 30c из 37с ушло на чтение с диска

• Отфильтровалось относительно мало строк

dataegret.com

Page 37: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

24 I/O timings

-> Bitmap Heap Scan on delivery fi (cost=872967.27..4196621.37 rows=32425255 width=36)

(actual time=30842.138..37818.082 rows=6674571 loops=1)

Recheck Cond: ((datetime_start >= ’2017-05-11 00:00:00’::timestamp without time zone) AND (datetime_end <= ’2017-05-18 06:00:00’::timestamp without time zone))

Filter: ((NOT is_deleted) OR (qty > 0))

Rows Removed by Filter: 60545

Heap Blocks: exact=667323

Buffers: shared hit=47584 read=827816 dirtied=1 written=2292

I/O Timings: read=30152.376 write=42.134

-> Bitmap Index Scan on delivery_datetime_start_datetime_end_idx (cost=0.00..864860.96 rows=32514577 width=0) (actual time=30602.293..30602.293 rows=6812447 loops=1)

Index Cond: ((datetime_start >= ’2017-05-11 00:00:00’::timestamp without time zone) AND (datetime_end <= ’2017-05-18 06:00:00’::timestamp without time zone))

Buffers: shared hit=80 read=207997

I/O Timings: read=27315.687

• 30c из 37с ушло на чтение с диска• Отфильтровалось относительно мало строк

dataegret.com

Page 38: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

25 I/O timings

• 30c из 37с ушло на чтение с диска• Отфильтровалось относительно мало строк• Происходит ли значительная фильтрация строк на более высокомуровне ?

• Если да - возможно нужно соединять таблицы каким-то другимспособом, чтобы приходилось меньше читать данных

dataegret.com

Page 39: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

25 I/O timings

• 30c из 37с ушло на чтение с диска• Отфильтровалось относительно мало строк• Происходит ли значительная фильтрация строк на более высокомуровне ?

• Если да - возможно нужно соединять таблицы каким-то другимспособом, чтобы приходилось меньше читать данных

dataegret.com

Page 40: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

26 Sort

explain analyze select distinct f1 from test_ndistinct ;QUERY PLAN

-------------------------------------------------------------------------------------------Unique (cost=1571431.43..1621431.49 rows=100000 width=4)

(actual time=4791.872..7551.150 rows=90020 loops=1)-> Sort (cost=1571431.43..1596431.46 rows=10000012 width=4)

(actual time=4791.870..6893.413 rows=10000000 loops=1)Sort Key: f1Sort Method: external merge Disk: 101648kB-> Seq Scan on test_ndistinct (cost=0.00..135314.12 rows=10000012 width=4)

(actual time=0.041..938.093 rows=10000000 loops=1)Planning time: 0.099 msExecution time: 7714.701 ms

dataegret.com

Page 41: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

27 HashAggregate

set work_mem = ’8MB’;SETexplain analyze select distinct f1 from test_ndistinct ;

QUERY PLAN-------------------------------------------------------------------------------------------HashAggregate (cost=160314.15..161314.15 rows=100000 width=4)

(actual time=2371.902..2391.415 rows=90020 loops=1)Group Key: f1-> Seq Scan on test_ndistinct (cost=0.00..135314.12 rows=10000012 width=4)

(actual time=0.093..871.619 rows=10000000 loops=1)Planning time: 0.048 msExecution time: 2396.186 ms

dataegret.com

Page 42: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

28 Ошибки планировщика

explain analyze select account_id from accountwhere last_update <= ’2017-10-23’ and discarded = false limit 200;-------------------------------------------------------------------------------------------Limit (cost=0.08..26.15 rows=200 width=4) (actual time=0.584..18.087 rows=2 loops=1)

-> Index Scan using account_last_update_special1_key on account(cost=0.08..18878.72 rows=144854 width=4) (actual time=0.582..18.084 rows=2 loops=1)

Index Cond: (last_update <= ’2017-10-23’::date)

• Ошибки в оценке числа строк в несколько порядков могут привести в итоге к сильнонеоптимальному плану

• Можно посмотреть на собираемую autoanalyze статистику чтобы понять причину• Можно посмотреть переписать запрос каким-то иным способом, заставив планировщик

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

dataegret.com

Page 43: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

28 Ошибки планировщика

explain analyze select account_id from accountwhere last_update <= ’2017-10-23’ and discarded = false limit 200;-------------------------------------------------------------------------------------------Limit (cost=0.08..26.15 rows=200 width=4) (actual time=0.584..18.087 rows=2 loops=1)

-> Index Scan using account_last_update_special1_key on account(cost=0.08..18878.72 rows=144854 width=4) (actual time=0.582..18.084 rows=2 loops=1)

Index Cond: (last_update <= ’2017-10-23’::date)

• Ошибки в оценке числа строк в несколько порядков могут привести в итоге к сильнонеоптимальному плану

• Можно посмотреть на собираемую autoanalyze статистику чтобы понять причину• Можно посмотреть переписать запрос каким-то иным способом, заставив планировщик

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

dataegret.com

Page 44: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

28 Ошибки планировщика

explain analyze select account_id from accountwhere last_update <= ’2017-10-23’ and discarded = false limit 200;-------------------------------------------------------------------------------------------Limit (cost=0.08..26.15 rows=200 width=4) (actual time=0.584..18.087 rows=2 loops=1)

-> Index Scan using account_last_update_special1_key on account(cost=0.08..18878.72 rows=144854 width=4) (actual time=0.582..18.084 rows=2 loops=1)

Index Cond: (last_update <= ’2017-10-23’::date)

• Ошибки в оценке числа строк в несколько порядков могут привести в итоге к сильнонеоптимальному плану

• Можно посмотреть на собираемую autoanalyze статистику чтобы понять причину

• Можно посмотреть переписать запрос каким-то иным способом, заставив планировщиксоединять таблицы нужным образом и использовать нужные индексы

dataegret.com

Page 45: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

28 Ошибки планировщика

explain analyze select account_id from accountwhere last_update <= ’2017-10-23’ and discarded = false limit 200;-------------------------------------------------------------------------------------------Limit (cost=0.08..26.15 rows=200 width=4) (actual time=0.584..18.087 rows=2 loops=1)

-> Index Scan using account_last_update_special1_key on account(cost=0.08..18878.72 rows=144854 width=4) (actual time=0.582..18.084 rows=2 loops=1)

Index Cond: (last_update <= ’2017-10-23’::date)

• Ошибки в оценке числа строк в несколько порядков могут привести в итоге к сильнонеоптимальному плану

• Можно посмотреть на собираемую autoanalyze статистику чтобы понять причину• Можно посмотреть переписать запрос каким-то иным способом, заставив планировщик

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

dataegret.com

Page 46: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

29 Что можно делать ?

• Если запрос выполняется за разумное время, смотрим его explainanalyze

• Пытаемся найти узлы, на которые больше всего уходит времени• Нет ли у нас где-то явно пропущенных индесов, в местах где многострок фильтруется ?

• Нет ли проблем с bloat ? Смотрим explain (analyze, buffers)• Нет ли проблем с дисками (track_io_timing)?• Хватает ли work_mem ?• Не ошибкается ли планировщик в оценке числа строк на порядки?• Не сильно ли велико время планирования?

dataegret.com

Page 47: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

30 Что можно почитать?

• depesz: Explaining the unexplainable• PostgreSQL Manual 14.1. Using EXPLAIN• Bruce Momjian – Explaining the Postgres Query Optimizer• www.slideshare.net/alexius2/

dataegret.com

Page 48: #RuPostgresLive 4: как писать и читать сложные SQL-запросы

31 Вопросы?

[email protected]

dataegret.com