Главная страница
Навигация по странице:

  • EXPLAIN SELECT num_tickets, count( * ) AS num_bookings FROM ( SELECT b.book_ref, ( SELECT count( * ) FROM tickets t WHERE t.book_ref = b.book_ref

  • ORDER BY num_tickets DESC;

  • CREATE INDEX tickets_book_ref_key ON tickets ( book_ref );

  • EXPLAIN ANALYZE SELECT num_tickets, count( * ) AS num_bookings FROM ( SELECT b.book_ref, count( * ) FROM bookings b, tickets t

  • GROUP by num_tickets ORDER BY num_tickets DESC;

  • Контрольные вопросы и задания Перед выполнением упражнений нужно восстановить измененные значения пара- метров:SET enable_hashjoin = on; SETSET enable_nestloop = on;

  • EXPLAIN SELECT * FROM bookings ORDER BY book_ref;

  • EXPLAIN SELECT total_amount FROM bookings ORDER BY total_amount DESC LIMIT 5;

  • EXPLAIN SELECT city, count( * ) FROM airports GROUP BY city HAVING count( * ) > 1;

  • EXPLAIN ANALYZE SELECT a.aircraft_code AS a_code, a.model, ( SELECT count( r.aircraft_code ) FROM routes r WHERE r.aircraft_code = a.aircraft_code

  • EXPLAIN ANALYZE SELECT a.aircraft_code AS a_code, a.model, count( r.aircraft_code ) AS num_routes FROM aircrafts a LEFT OUTER JOIN routes r

  • EXPLAIN ANALYZE SELECT * FROM routes; EXPLAIN ANALYZE WITH f3 AS ( SELECT f2.flight_no, ...

  • EXPLAIN ANALYZE SELECT b.book_ref, sum( tf.amount ) FROM bookings b, tickets t, ticket_flights tf WHERE b.book_ref = t.book_ref AND t.ticket_no = tf.ticket_no

  • SELECT book_ref, total_amount FROM bookings ORDER BY 1;

  • CREATE TEMP TABLE flights_tt AS SELECT * FROM flights_v;

  • EXPLAIN ANALYZE SELECT * FROM flights_v; EXPLAIN ANALYZE SELECT * FROM flights_tt;

  • EXPLAIN ANALYZE SELECT count( * ) FROM tickets WHERE passenger_name = IVAN IVANOV;

  • Учебное пособие СанктПетербург бхвпетербург


    Скачать 1.88 Mb.
    НазваниеУчебное пособие СанктПетербург бхвпетербург
    Дата12.02.2023
    Размер1.88 Mb.
    Формат файлаpdf
    Имя файлаsql_primer.pdf
    ТипУчебное пособие
    #933464
    страница19 из 20
    1   ...   12   13   14   15   16   17   18   19   20
    ANALYZE aircrafts;
    ANALYZE
    В качестве примера ситуации, в которой оптимизация запроса представляется обос- нованной, рассмотрим следующую задачу. Предположим, что необходимо опреде- лить степень загруженности кассиров нашей авиакомпании в сентябре 2016 г. Для этого, в частности, требуется выявить распределение числа операций бронирования по числу билетов, оформленных в рамках этих операций. Другими словами, это озна- чает, что нужно подсчитать число операций бронирования, в которых был оформлен только один билет, число операций, в которых было оформлено два билета и т. д.
    Эту задачу можно переформулировать так: для каждой строки, отобранной из табли- цы «Бронирования» (bookings), нужно подсчитать соответствующие строки в таб- лице «Билеты» (tickets). Речь идет о строках, в которых значение поля book_ref такое же, что и в текущей строке таблицы bookings. Буквальное следование такой формулировке задачи приводит к получению запроса с коррелированным подзапро- сом в предложении SELECT. Но это еще не окончательное решение. Теперь нужно сгруппировать полученный набор строк по значениям числа оформленных билетов.
    312

    10.5. Оптимизация запросов
    Получаем такой запрос:
    EXPLAIN
    SELECT num_tickets, count( * ) AS num_bookings
    FROM
    ( SELECT b.book_ref,
    ( SELECT count( * ) FROM tickets t
    WHERE t.book_ref = b.book_ref
    )
    FROM bookings b
    WHERE date_trunc( 'mon', book_date ) = '2016-09-01'
    ) AS count_tickets( book_ref, num_tickets )
    GROUP by num_tickets
    ORDER BY num_tickets DESC;
    QUERY PLAN
    -----------------------------------------------------------------------
    GroupAggregate (cost=14000017.12..27994373.35 rows=1314 width=16)
    Group Key: ((SubPlan 1))
    -> Sort (cost=14000017.12..14000020.40 rows=1314 width=8)
    Sort Key: ((SubPlan 1)) DESC
    -> Seq Scan on bookings b
    (cost=0.00..13999949.05 rows=1314 width=8)
    Filter: (date_trunc('mon'::text, book_date) = '2016-09-01 00:00:00+08'::timestamp with time zone)
    SubPlan 1
    -> Aggregate (cost=10650.17..10650.18 rows=1 width=8)
    -> Seq Scan on tickets t
    (cost=0.00..10650.16 rows=2 width=0)
    Filter: (book_ref = b.book_ref)
    (10 строк)
    В плане получены очень большие оценки общей стоимости выполнения запроса:
    cost=14000017.12..27994373.35
    Универсальной зависимости между оценкой стоимости и реальным временем вы- полнения запроса не существует. Не всегда можно даже приблизительно предполо- жить, в какие затраты времени выльется та или иная оценка стоимости. Но, тем не менее, при рассмотрении других запросов оценок такого порядка нам еще не встре- чалось. Планировщик предполагает, что из таблицы tickets в подзапросе будет извлекаться всего по две строки, и эту операцию нужно будет проделать 1 314 раз:
    столько строк предположительно будет выбрано из таблицы bookings. Как видно
    313

    Глава 10. Повышение производительности
    из плана, для просмотра строк в таблице tickets используется ее последовательное сканирование. В результате оценка стоимости этого узла плана получается высокой:
    cost=0.00..10650.16
    Если у вас не очень мощный компьютер, то время получения результата может выйти за разумные пределы, и вам придется прервать процесс с помощью клавиш
    <
    Ctrl>+.
    Что можно сделать для ускорения выполнения запроса? Давайте создадим индекс для таблицы tickets по столбцу book_ref, по которому происходит поиск в ней.
    CREATE INDEX tickets_book_ref_key
    ON tickets ( book_ref );
    CREATE INDEX
    Повторим запрос, добавив параметр ANALYZE в команду EXPLAIN. Новый план, в ко- тором отражены и фактические результаты, будет таким:
    QUERY PLAN
    -------------------------------------------------------------------------
    GroupAggregate (cost=22072.70..38484.52 rows=1314 width=16)
    (actual time=3656.554..3787.562 rows=5 loops=1)
    Group Key: ((SubPlan 1))
    -> Sort (cost=22072.70..22075.99 rows=1314 width=8)
    (actual time=3656.533..3726.969 rows=165534 loops=1)
    Sort Key: ((SubPlan 1)) DESC
    Sort Method: external merge Disk: 2912kB
    -> Seq Scan on bookings b (cost=0.00..22004.64 rows=1314 width=8)
    (actual time=0.219..3332.162 rows=165534 loops=1)
    Filter: (date_trunc('mon'::text, book_date) = '2016-09-01 00:00:00+08'::timestamp with time zone)
    Rows Removed by Filter: 97254
    SubPlan 1
    -> Aggregate (cost=12.46..12.47 rows=1 width=8)
    (actual time=0.016..0.016 rows=1 loops=165534)
    -> Index Only Scan using tickets_book_ref_key on tickets t
    (cost=0.42..12.46 rows=2 width=0)
    (actual time=0.013..0.014 rows=1 loops=165534)
    Index Cond: (book_ref = b.book_ref)
    Heap Fetches: 230699
    Planning time: 0.290 ms
    Execution time: 3788.690 ms
    (15 строк)
    314

    10.5. Оптимизация запросов
    Теперь планировщик использует индекс для поиска в таблице tickets. Причем это поиск исключительно по индексу (Index Only Scan), поскольку нас интересует толь- ко число строк — count( * ), а не их содержание. Обратите внимание на различие предполагаемого и фактического числа извлекаемых строк. Тем не менее запрос стал выполняться значительно — на порядок — быстрее.
    Результат имеет такой вид:
    num_tickets | num_bookings
    -------------+--------------
    5 |
    13 4 |
    536 3 |
    7966 2 |
    47573 1 |
    109446
    (5 строк)
    Кроме создания индекса есть и другой способ: замена коррелированного подзапроса соединением таблиц.
    EXPLAIN ANALYZE
    SELECT num_tickets, count( * ) AS num_bookings
    FROM
    ( SELECT b.book_ref, count( * )
    FROM bookings b, tickets t
    WHERE date_trunc( 'mon', b.book_date ) = '2016-09-01'
    AND t.book_ref = b.book_ref
    GROUP BY b.book_ref
    ) AS count_tickets( book_ref, num_tickets )
    GROUP by num_tickets
    ORDER BY num_tickets DESC;
    QUERY PLAN
    ------------------------------------------------------------------------
    GroupAggregate (cost=16966.67..16978.53 rows=200 width=16)
    (actual time=4092.258..4219.477 rows=5 loops=1)
    Group Key: count_tickets.num_tickets
    -> Sort (cost=16966.67..16969.96 rows=1314 width=8)
    (actual time=4092.236..4161.294 rows=165534 loops=1)
    Sort Key: count_tickets.num_tickets DESC
    Sort Method: external merge Disk: 2912kB
    -> Subquery Scan on count_tickets
    (cost=16858.57..16898.61 rows=1314 width=8)
    (actual time=3176.113..3862.133 rows=165534 loops=1)
    315

    Глава 10. Повышение производительности
    -> GroupAggregate (cost=16858.57..16885.47 rows=1314 width=15)
    (actual time=3176.111..3765.157 rows=165534 loops=1)
    Group Key: b.book_ref
    -> Sort (cost=16858.57..16863.16 rows=1834 width=7)
    (actual time=3176.098..3552.334 rows=230699 loops=1)
    Sort Key: b.book_ref
    Sort Method: external merge Disk: 3824kB
    -> Hash Join (cost=5632.24..16759.16 rows=1834 width=7)
    (actual time=498.701..1091.509 rows=230699 loops=1)
    Hash Cond: (t.book_ref = b.book_ref)
    -> Seq Scan on tickets t
    (cost=0.00..9733.33 rows=366733 width=7)
    (actual time=0.047..170.792 rows=366733 loops=1)
    -> Hash (cost=5615.82..5615.82 rows=1314 width=7)
    (actual time=498.624..498.624 rows=165534 loops=1)
    Buckets: 262144 (originally 2048)
    Batches: 2 (originally 1)
    Memory Usage: 3457kB
    -> Seq Scan on bookings b
    (cost=0.00..5615.82 rows=1314 width=7)
    (actual time=0.019..267.728 rows=165534 loops=1)
    Filter: (date_trunc('mon'::text, book_date) =
    '2016-09-01 00:00:00+08'::timestamp with time zone)
    Rows Removed by Filter: 97254
    Planning time: 2.183 ms
    Execution time: 4221.133 ms
    (21 строка)
    В данном плане используется соединение хешированием (Hash Join). При этом ин- декс по таблице tickets игнорируется: таблица просматривается последовательно
    (Seq Scan on tickets t). Время выполнения модифицированного запроса оказыва- ется несколько большим, чем в предыдущем случае, когда в запросе присутствовал коррелированный подзапрос. Таким образом, можно заключить, что для ускорения работы оригинального запроса можно было либо создать индекс, либо модифициро- вать сам запрос, даже не создавая индекса.
    Другие методы оптимизации выполнения запросов представлены в разделе «Кон- трольные вопросы и задания». Рекомендуем вам самостоятельно с ними ознакомить- ся и поэкспериментировать.
    316

    Контрольные вопросы и задания
    Контрольные вопросы и задания
    Перед выполнением упражнений нужно восстановить измененные значения пара- метров:
    SET enable_hashjoin = on;
    SET
    SET enable_nestloop = on;
    SET
    1. Как вы думаете, почему при сканировании по индексу оценка стоимости ресур- сов, требующихся для выдачи первых результатов, не равна нулю, хотя исполь- зуется индекс, совпадающий с порядком сортировки?
    EXPLAIN_SELECT_total_amount_FROM_bookings_ORDER_BY_total_amount_DESC_LIMIT_5;'>EXPLAIN_SELECT_*_FROM_bookings_ORDER_BY_book_ref;'>EXPLAIN
    SELECT *
    FROM bookings
    ORDER BY book_ref;
    QUERY PLAN
    ----------------------------------------------------------------
    Index Scan using bookings_pkey on bookings (cost=0.42..8511.24
    rows=262788 width=21)
    (1 строка)
    2. Как вы думаете, если в запросе присутствует предложение ORDER BY, и создан индекс по тем столбцам, которые фигурируют в предложении ORDER BY, то все- гда ли будет использоваться этот индекс или нет? Почему? Проверьте ваши предположения с помощью команды EXPLAIN.
    3. Самостоятельно выполните команду EXPLAIN для запроса, содержащего общее табличное выражение (CTE). Посмотрите, на каком уровне находится узел пла- на, отвечающий за это выражение, как он оформляется. Учтите, что общие таб- личные выражения всегда материализуются, т. е. вычисляются однократно и результат их вычисления сохраняется в памяти, а затем все последующие об- ращения в рамках запроса направляются уже к этому материализованному ре- зультату.
    4. Прокомментируйте следующий план, попробуйте объяснить значения всех его узлов и параметров.
    317

    Глава 10. Повышение производительности
    EXPLAIN
    SELECT total_amount
    FROM bookings
    ORDER BY total_amount DESC
    LIMIT 5;
    QUERY PLAN
    ----------------------------------------------------------------
    Limit (cost=8666.69..8666.71 rows=5 width=6)
    -> Sort (cost=8666.69..9323.66 rows=262788 width=6)
    Sort Key: total_amount DESC
    -> Seq Scan on bookings (cost=0.00..4301.88 rows=262788
    width=6)
    (4 строки)
    5. В подавляющем большинстве городов только один аэропорт, но есть и такие го- рода, в которых более одного аэропорта. Давайте их выявим.
    EXPLAIN
    SELECT city, count( * )
    FROM airports
    GROUP BY city
    HAVING count( * ) > 1;
    QUERY PLAN
    -----------------------------------------------------------------
    HashAggregate (cost=3.82..4.83 rows=101 width=25)
    Group Key: city
    Filter: (count(*) > 1)
    -> Seq Scan on airports (cost=0.00..3.04 rows=104 width=17)
    (4 строки)
    Для подсчета числа аэропортов в городах используется последовательное ска- нирование и формирование хеш-таблицы (HashAggregate), ключом которой является столбец city. Затем из нее отбираются те записи, значения которых соответствуют условию
    Filter: (count(*) > 1)
    Как вы думаете, чем можно объяснить, что вторая оценка стоимости в парамет- ре cost для узла Seq Scan, равная 3,04, не совпадает с первой оценкой стоимо- сти в параметре cost для узла HashAggregate?
    318

    Контрольные вопросы и задания
    6. Выполните команду EXPLAIN для запроса, в котором использована какая- нибудь из оконных функций. Найдите в плане выполнения запроса узел с име- нем WindowAgg. Попробуйте объяснить, почему он занимает именно этот уро- вень в плане.
    7. Проанализируйте план выполнения операций вставки и удаления строк. При- чем сделайте это таким образом, чтобы данные в таблицах фактически измене- ны не были.
    8.* Замена коррелированного подзапроса соединением таблиц является одним из способов повышения производительности.
    Предположим, что мы задались вопросом: сколько маршрутов обслуживают са- молеты каждого типа? При этом нужно учитывать, что может иметь место такая ситуация, когда самолеты какого-либо типа не обслуживают ни одного марш- рута. Поэтому необходимо использовать не только представление «Маршруты»
    (routes), но и таблицу «Самолеты» (aircrafts).
    Это первый вариант запроса, в нем используется коррелированный подзапрос.
    EXPLAIN ANALYZE
    SELECT a.aircraft_code AS a_code,
    a.model,
    ( SELECT count( r.aircraft_code )
    FROM routes r
    WHERE r.aircraft_code = a.aircraft_code
    ) AS num_routes
    FROM aircrafts a
    GROUP BY 1, 2
    ORDER BY 3 DESC;
    А в этом варианте коррелированный подзапрос раскрыт и заменен внешним соединением:
    EXPLAIN ANALYZE
    SELECT a.aircraft_code AS a_code,
    a.model,
    count( r.aircraft_code ) AS num_routes
    FROM aircrafts a
    LEFT OUTER JOIN routes r
    ON r.aircraft_code = a.aircraft_code
    GROUP BY 1, 2
    ORDER BY 3 DESC;
    319

    Глава 10. Повышение производительности
    Причина использования внешнего соединения в том, что может найтись мо- дель самолета, не обслуживающая ни одного маршрута, и если не использовать внешнее соединение, она вообще не попадет в результирующую выборку.
    Исследуйте планы выполнения обоих запросов. Попытайтесь найти объясне- ние различиям в эффективности их выполнения. Чтобы получить усредненную картину, выполните каждый запрос несколько раз. Поскольку таблицы, участ- вующие в запросах, небольшие, то различие по абсолютным затратам времени выполнения будет незначительным. Но если бы число строк в таблицах было большим, то экономия ресурсов сервера могла оказаться заметной.
    Предложите аналогичную пару запросов к базе данных «Авиаперевозки». Про- ведите необходимые эксперименты с вашими запросами.
    9. Одним из способов повышения производительности является изменение схе- мы данных, связанное с денормализацией, а именно: создание материализо- ванных представлений. В главе 5 было описано такое материализованное пред- ставление — «Маршруты» (routes). Команда для его создания была приведена в главе 6.
    Проведите эксперимент: сначала выполните выборку из готового представле- ния, а затем ту выборку, которая это представление формирует.
    EXPLAIN ANALYZE
    SELECT * FROM routes;
    EXPLAIN ANALYZE
    WITH f3 AS ( SELECT f2.flight_no, ...
    Поскольку второй запрос очень громоздкий, то можно поступить таким обра- зом: сначала сохраните его в текстовом файле, а затем выполните с помощью команды \i утилиты psql.
    Вы увидите, что затраты времени отличаются практически на два порядка. Ко- нечно, нужно помнить, что материализованные представления необходимо пе- риодически обновлять, чтобы их содержимое было актуальным.
    10.* Одним из способов повышения производительности является изменение схе- мы данных, связанное с денормализацией, а именно: использование вычисля- емых столбцов. Для примера рассмотрим таблицу «Бронирования» (bookings).
    В ней столбец «Полная сумма бронирования» (total_amount) является вычис- ляемым. Мы не будем сейчас говорить о том, каким образом его значения син- хронизируются с данными в таблице «Перелеты» (ticket_flifgts), а лишь
    320

    Контрольные вопросы и задания
    рассмотрим два запроса, возвращающие полные суммы бронирований. Пред- положим, что указанного столбца в таблице bookings не было бы. Тогда запрос,
    возвращающий полные суммы бронирований, выглядел бы так:
    EXPLAIN ANALYZE
    SELECT b.book_ref, sum( tf.amount )
    FROM bookings b, tickets t, ticket_flights tf
    WHERE b.book_ref = t.book_ref
    AND t.ticket_no = tf.ticket_no
    GROUP BY 1
    ORDER BY 1;
    Но благодаря наличию вычисляемого столбца total_amount те же сведения можно получить с гораздо меньшими затратами ресурсов:
    EXPLAIN ANALYZE
    SELECT book_ref, total_amount
    FROM bookings
    ORDER BY 1;
    Попробуйте предложить еще какой-нибудь вычисляемый столбец для одной из таблиц базы данных «Авиаперевозки». Проведите эксперименты, подтвержда- ющие эффективность вашего решения.
    11.* Одним из способов повышения производительности является изменение схемы данных, а именно: использование временных таблиц. Предположим, что нам предстоит сделать много выборок из представления «Рейсы» (flights_v), в та- ком случае имеет смысл подумать о создании временной таблицы:
    CREATE TEMP TABLE flights_tt AS
    SELECT * FROM flights_v;
    Сформируйте планы для получения простой выборки из представления и из временной таблицы и сравните полученные результаты. Как вы думаете, поче- му план, сформированный для получения даже простой выборки из представ- ления, многоуровневый?
    EXPLAIN ANALYZE
    SELECT * FROM flights_v;
    EXPLAIN ANALYZE
    SELECT * FROM flights_tt;
    321

    Глава 10. Повышение производительности
    Выполните более сложные запросы к представлению и временной таблице и сравните полученные результаты. Чтобы увидеть фактические затраты време- ни, включайте в команду EXPLAIN опцию ANALYZE.
    Подумайте, при выполнении каких запросов к базе данных «Авиаперевозки»
    было бы целесообразно создать временную таблицу. Создайте ее и проведите эксперименты, подтверждающие эффективность ее использования.
    12. Одним из способов повышения производительности является изменение схемы данных, связанное с денормализацией, а именно: создание индексов.
    Выполните следующий простой запрос к таблице «Билеты»:
    EXPLAIN ANALYZE
    SELECT count( * )
    FROM tickets
    WHERE passenger_name = 'IVAN IVANOV';
    Создайте индекс по столбцу passenger_name:
    CREATE INDEX passenger_name_key
    ON tickets ( passenger_name );
    Теперь повторите запрос и сравните полученные планы и фактические резуль- таты.
    Предложите какой-нибудь запрос к базе данных «Авиаперевозки», для выпол- нения которого было бы целесообразно создать индекс. Создайте индекс и по- вторите запрос. Изучите полученный план, посмотрите, используется ли индекс планировщиком.
    13.* В самом конце главы мы выполняли оптимизацию запроса путем создания ин- декса и модификации текста запроса. Был сформирован такой запрос:
    1   ...   12   13   14   15   16   17   18   19   20


    написать администратору сайта