Отладка различий в производительности (и проблем) между запросом с двойным подзапросом, единственным подзапросом и всеми операторами внутреннего соединения - PullRequest
6 голосов
/ 02 мая 2020

У меня сложная бизнес-логика c, которая требует от меня выполнения двухуровневого вложенного запроса. Запросы генерируются Django ORM. В нижней части вопроса я приведу запросы как есть, а также полный EXPLAIN, подходящий для просмотра с PEV2 , но чтобы помочь читателям лучше понять вопрос, я буду Начнем с более концептуального объяснения.

Вот как выглядит очень наивное описание того, что мы делаем:

some_ids = get_id_based_on_some_conditions(*conditions*)
some_other_ids = get_some_other_ids_based_on_some_conditions_and_filtering_by_some_ids(*other_conditions*, some_ids)
results = get_results_based_on_even_more_conditions_and_filtering_by_some_other_ids(*another_set_of_conditions*, some_other_ids)

Перевод следующего псевдокода в фактический SQL с использованием Подзапросы довольно просты. Простой перевод превращается в следующий псевдопросмотр:

select 
    foo,
    bar
from
    t1,
    t2
where
    condition1 = something and
    condition2 in (   <---- first level subquery
        select 
            id
        from
            t3
        where
            condition3 = another_something and
            condition4 in (    <---- second level subquery
                select
                    another_id
                from
                    t4
                where
                    condition5 = something_something and
                    condition6 = another_something_something
            )
    )

Поскольку запрос занимает значительное время (~ 0,6 с), учитывая количество возвращаемых строк (чуть более 9.000) Я подумал, что это может помочь заменить подзапрос второго уровня внутренним объединением.

Это фактически сделало запрос еще медленнее (сейчас на ~ 1,7 с). Поэтому я подумал, что, возможно, планировщик не правильно понял, что произойдет с подзапросом с внутренним соединением, и сделал несколько серьезных просчетов / переоценок / недооценок, поэтому я заменил подзапрос первого уровня большим количеством внутренних объединений, что привело к еще худшему результаты (сейчас ~ 10 с).

Я часами анализировал EXPLAINS запросов и не могу понять, почему использование внутренних объединений делает все медленнее. Я также не знаю, как определить, является ли мой (на данный момент) лучший запрос лучшим, что я могу получить, или есть вещи, которые я не делаю и которые могут ускорить его.

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

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

Актуальные запросы и EXPLAINS как есть:

Запрос с подуровнями из двух уровней:

SELECT DISTINCT
    "phdrug_phdrug"."id",
    "phdrug_phdrug"."uuid",
    "phdrug_phdrug"."default_description",
    "phdrug_phdrug"."alternative_description",
    "phdrug_phdrug"."ean",
    "phdrug_phdrug"."mirror_ean",
    "phdrug_phdrug"."parent_ean",
    "phdrug_phdrug"."reg_num",
    "phdrug_phdrug"."medika_code",
    "phdrug_phdrug"."atc_iv",
    "phdrug_phdrug"."product_type",
    "phdrug_phdrug"."fraction",
    "phdrug_phdrug"."active",
    "phdrug_phdrug"."loyal",
    "phdrug_phdrug"."patent",
    "phdrug_phdrug"."chronics",
    "phdrug_phdrug"."recipe",
    "phdrug_phdrug"."deal",
    "phdrug_phdrug"."specialized",
    "phdrug_phdrug"."armored",
    "phdrug_phdrug"."top_hight_speciality",
    "phdrug_phdrug"."top_generic",
    "phdrug_phdrug"."hight_speciality",
    "phdrug_phdrug"."temp_8_15",
    "phdrug_phdrug"."temp_15_25",
    "phdrug_phdrug"."temp_2_8",
    "phdrug_phdrug"."temp_less_15",
    "phdrug_phdrug"."new",
    "phdrug_phdrug"."mdk_internal_code",
    "phdrug_phdrug"."mdk_single_id",
    "phdrug_phdrug"."mdk_object_id",
    "phdrug_phdrug"."is_from_mdk_db",
    "phdrug_phdrug"."top",
    "phdrug_phdrug"."laboratory_name",
    "phdrug_phdrug"."laboratory_alternative_name",
    "phdrug_phdrug"."imported",
    "phdrug_phdrug"."imported_country",
    "phdrug_phdrug"."laboratory_id",
    "phdrug_phdrug"."specialty",
    "phdrug_phdrug"."dimension_id",
    "phdrug_phdrug"."featured",
    "phdrug_phdrug"."top_ae_rank",
    "phdrug_phdrug"."top_farma_rank" 
FROM
    "phdrug_phdrug"
    INNER JOIN "monetary_drugprice" ON ( "phdrug_phdrug"."id" = "monetary_drugprice"."drug_id" )
    INNER JOIN "phdrug_phdrugpicture" ON ( "phdrug_phdrug"."id" = "phdrug_phdrugpicture"."drug_id" ) 
WHERE
    (
        "monetary_drugprice"."id" IN (
        SELECT
            V0."id" 
        FROM
            "monetary_drugprice" V0 
        WHERE
            (
                V0."pricelist_id" IN (
                SELECT DISTINCT ON
                    ( U0."id" ) U0."id" 
                FROM
                    "monetary_pricelist" U0
                    INNER JOIN "monetary_pricelistdestinations" U1 ON ( U0."id" = U1."pricelist_id" )
                    INNER JOIN "organization_organization" U2 ON ( U0."manager_id" = U2."id" )
                    INNER JOIN "courier_carrier_pricelists" U3 ON ( U0."id" = U3."pricelist_id" )
                    INNER JOIN "courier_carrier" U4 ON ( U3."carrier_id" = U4."id" )
                    INNER JOIN "courier_carrierdelivery" U5 ON ( U4."id" = U5."carrier_id" )
                    INNER JOIN "monetary_pricelistcountry" U6 ON ( U0."id" = U6."pricelist_id" ) 
                WHERE
                    (
                        (
                            U0."expires" = FALSE 
                            OR (
                                U0."expires" = TRUE 
                                AND ( U0."datestart" AT TIME ZONE'UTC' ) :: DATE <= '2020-05-01' 
                                AND ( U0."dateend" AT TIME ZONE'UTC' ) :: DATE >= '2020-05-01' 
                            ) 
                        ) 
                        AND U0."active" = TRUE 
                        AND U1."to_public" = TRUE 
                        AND U2."organization_type" = 2 
                        AND (
                            U5."dst_country" = 'MX' 
                            OR U5."ignore_country_filter" = TRUE 
                        ) 
                        AND U6."country" = 'MX' 
                        AND U2."active" = TRUE 
                    ) 
                ) 
                AND V0."stock" > 0 
            ) 
        ) 
        AND "phdrug_phdrug"."active" = TRUE 
        AND "phdrug_phdrugpicture"."is_main" = TRUE 
    ) 
ORDER BY
    "phdrug_phdrug"."id" ASC,
    "phdrug_phdrug"."default_description" ASC

Полное объяснение: https://pastebin.com/jDy3FyKp

Запрос с одноуровневым подзапросом:

SELECT DISTINCT
    "phdrug_phdrug"."id",
    "phdrug_phdrug"."uuid",
    "phdrug_phdrug"."default_description",
    "phdrug_phdrug"."alternative_description",
    "phdrug_phdrug"."ean",
    "phdrug_phdrug"."mirror_ean",
    "phdrug_phdrug"."parent_ean",
    "phdrug_phdrug"."reg_num",
    "phdrug_phdrug"."medika_code",
    "phdrug_phdrug"."atc_iv",
    "phdrug_phdrug"."product_type",
    "phdrug_phdrug"."fraction",
    "phdrug_phdrug"."active",
    "phdrug_phdrug"."loyal",
    "phdrug_phdrug"."patent",
    "phdrug_phdrug"."chronics",
    "phdrug_phdrug"."recipe",
    "phdrug_phdrug"."deal",
    "phdrug_phdrug"."specialized",
    "phdrug_phdrug"."armored",
    "phdrug_phdrug"."top_hight_speciality",
    "phdrug_phdrug"."top_generic",
    "phdrug_phdrug"."hight_speciality",
    "phdrug_phdrug"."temp_8_15",
    "phdrug_phdrug"."temp_15_25",
    "phdrug_phdrug"."temp_2_8",
    "phdrug_phdrug"."temp_less_15",
    "phdrug_phdrug"."new",
    "phdrug_phdrug"."mdk_internal_code",
    "phdrug_phdrug"."mdk_single_id",
    "phdrug_phdrug"."mdk_object_id",
    "phdrug_phdrug"."is_from_mdk_db",
    "phdrug_phdrug"."top",
    "phdrug_phdrug"."laboratory_name",
    "phdrug_phdrug"."laboratory_alternative_name",
    "phdrug_phdrug"."imported",
    "phdrug_phdrug"."imported_country",
    "phdrug_phdrug"."laboratory_id",
    "phdrug_phdrug"."specialty",
    "phdrug_phdrug"."dimension_id",
    "phdrug_phdrug"."featured",
    "phdrug_phdrug"."top_ae_rank",
    "phdrug_phdrug"."top_farma_rank" 
FROM
    "phdrug_phdrug"
    INNER JOIN "monetary_drugprice" ON ( "phdrug_phdrug"."id" = "monetary_drugprice"."drug_id" )
    INNER JOIN "phdrug_phdrugpicture" ON ( "phdrug_phdrug"."id" = "phdrug_phdrugpicture"."drug_id" ) 
WHERE
    (
        "monetary_drugprice"."id" IN (
        SELECT
            U0."id" 
        FROM
            "monetary_drugprice" U0
            INNER JOIN "monetary_pricelist" U1 ON ( U0."pricelist_id" = U1."id" )
            INNER JOIN "monetary_pricelistdestinations" U2 ON ( U1."id" = U2."pricelist_id" )
            INNER JOIN "organization_organization" U3 ON ( U1."manager_id" = U3."id" )
            INNER JOIN "courier_carrier_pricelists" U4 ON ( U1."id" = U4."pricelist_id" )
            INNER JOIN "courier_carrier" U5 ON ( U4."carrier_id" = U5."id" )
            INNER JOIN "courier_carrierdelivery" U6 ON ( U5."id" = U6."carrier_id" )
            INNER JOIN "monetary_pricelistcountry" U7 ON ( U1."id" = U7."pricelist_id" ) 
        WHERE
            (
                (
                    U1."expires" = FALSE 
                    OR (
                        U1."expires" = TRUE 
                        AND ( U1."datestart" AT TIME ZONE'UTC' ) :: DATE <= '2020-05-01' 
                        AND ( U1."dateend" AT TIME ZONE'UTC' ) :: DATE >= '2020-05-01' 
                    ) 
                ) 
                AND U1."active" = TRUE 
                AND U2."to_public" = TRUE 
                AND U3."organization_type" = 2 
                AND (
                    U6."dst_country" = 'MX' 
                    OR U6."ignore_country_filter" = TRUE 
                ) 
                AND U7."country" = 'MX' 
                AND U3."active" = TRUE 
                AND U0."stock" > 0 
            ) 
        ) 
        AND "phdrug_phdrug"."active" = TRUE 
        AND "phdrug_phdrugpicture"."is_main" = TRUE 
    ) 
ORDER BY
    "phdrug_phdrug"."id" ASC,
    "phdrug_phdrug"."default_description" ASC

Полное объяснение: https://pastebin.com/NidTZMxY

Запрос только с внутренними объединениями:

SELECT DISTINCT
    "phdrug_phdrug"."id",
    "phdrug_phdrug"."uuid",
    "phdrug_phdrug"."default_description",
    "phdrug_phdrug"."alternative_description",
    "phdrug_phdrug"."ean",
    "phdrug_phdrug"."mirror_ean",
    "phdrug_phdrug"."parent_ean",
    "phdrug_phdrug"."reg_num",
    "phdrug_phdrug"."medika_code",
    "phdrug_phdrug"."atc_iv",
    "phdrug_phdrug"."product_type",
    "phdrug_phdrug"."fraction",
    "phdrug_phdrug"."active",
    "phdrug_phdrug"."loyal",
    "phdrug_phdrug"."patent",
    "phdrug_phdrug"."chronics",
    "phdrug_phdrug"."recipe",
    "phdrug_phdrug"."deal",
    "phdrug_phdrug"."specialized",
    "phdrug_phdrug"."armored",
    "phdrug_phdrug"."top_hight_speciality",
    "phdrug_phdrug"."top_generic",
    "phdrug_phdrug"."hight_speciality",
    "phdrug_phdrug"."temp_8_15",
    "phdrug_phdrug"."temp_15_25",
    "phdrug_phdrug"."temp_2_8",
    "phdrug_phdrug"."temp_less_15",
    "phdrug_phdrug"."new",
    "phdrug_phdrug"."mdk_internal_code",
    "phdrug_phdrug"."mdk_single_id",
    "phdrug_phdrug"."mdk_object_id",
    "phdrug_phdrug"."is_from_mdk_db",
    "phdrug_phdrug"."top",
    "phdrug_phdrug"."laboratory_name",
    "phdrug_phdrug"."laboratory_alternative_name",
    "phdrug_phdrug"."imported",
    "phdrug_phdrug"."imported_country",
    "phdrug_phdrug"."laboratory_id",
    "phdrug_phdrug"."specialty",
    "phdrug_phdrug"."dimension_id",
    "phdrug_phdrug"."featured",
    "phdrug_phdrug"."top_ae_rank",
    "phdrug_phdrug"."top_farma_rank" 
FROM
    "phdrug_phdrug"
    INNER JOIN "monetary_drugprice" ON ( "phdrug_phdrug"."id" = "monetary_drugprice"."drug_id" )
    INNER JOIN "monetary_pricelist" ON ( "monetary_drugprice"."pricelist_id" = "monetary_pricelist"."id" )
    INNER JOIN "monetary_pricelistdestinations" ON ( "monetary_pricelist"."id" = "monetary_pricelistdestinations"."pricelist_id" )
    INNER JOIN "organization_organization" ON ( "monetary_pricelist"."manager_id" = "organization_organization"."id" )
    INNER JOIN "courier_carrier_pricelists" ON ( "monetary_pricelist"."id" = "courier_carrier_pricelists"."pricelist_id" )
    INNER JOIN "courier_carrier" ON ( "courier_carrier_pricelists"."carrier_id" = "courier_carrier"."id" )
    INNER JOIN "courier_carrierdelivery" ON ( "courier_carrier"."id" = "courier_carrierdelivery"."carrier_id" )
    INNER JOIN "monetary_pricelistcountry" ON ( "monetary_pricelist"."id" = "monetary_pricelistcountry"."pricelist_id" )
    INNER JOIN "phdrug_phdrugpicture" ON ( "phdrug_phdrug"."id" = "phdrug_phdrugpicture"."drug_id" ) 
WHERE
    (
        (
            "monetary_pricelist"."expires" = FALSE 
            OR (
                "monetary_pricelist"."expires" = TRUE 
                AND ( "monetary_pricelist"."datestart" AT TIME ZONE'UTC' ) :: DATE <= '2020-05-01' 
                AND ( "monetary_pricelist"."dateend" AT TIME ZONE'UTC' ) :: DATE >= '2020-05-01' 
            ) 
        ) 
        AND "monetary_pricelist"."active" = TRUE 
        AND "monetary_pricelistdestinations"."to_public" = TRUE 
        AND "organization_organization"."organization_type" = 2 
        AND (
            "courier_carrierdelivery"."dst_country" = 'MX' 
            OR "courier_carrierdelivery"."ignore_country_filter" = TRUE 
        ) 
        AND "monetary_pricelistcountry"."country" = 'MX' 
        AND "organization_organization"."active" = TRUE 
        AND "monetary_drugprice"."stock" > 0 
        AND "phdrug_phdrug"."active" = TRUE 
        AND "phdrug_phdrugpicture"."is_main" = TRUE 
    ) 
ORDER BY
    "phdrug_phdrug"."id" ASC,
    "phdrug_phdrug"."default_description" ASC

Полное объяснение: https://pastebin.com/DaVztBuV

...