Как оптимизировать SQL-запрос с несколькими соединениями и фильтрами?

Вопрос или проблема

Как оптимизировать SQL-запрос с несколькими соединениями и фильтрами?

У меня есть SQL-запрос, который извлекает информацию о скидочных купонах на основе нескольких критериев соответствия. Существуют две таблицы – discount_coupons и fixed_coupon_eligibility. Ниже представлены запросы на создание таблиц (с индексами)

CREATE TABLE discount_coupons (
    id                     BIGINT AUTO_INCREMENT PRIMARY KEY,
    code                   VARCHAR(64) NOT NULL,
    status                 INT NOT NULL,
    description            VARCHAR(255) NULL,
    discountValue          DOUBLE NOT NULL,
    maxDiscountValue       DOUBLE NOT NULL,
    discountType           INT NOT NULL,
    availableCount         INT NOT NULL,
    consumedCount          INT NOT NULL,
    availabilityStarts     DATETIME NOT NULL,
    availabilityEnds       DATETIME NOT NULL,
    createdAt              DATETIME NOT NULL,
    updatedAt              DATETIME NOT NULL,
    gender                 VARCHAR(255) NULL,
    forPaywall             BIT DEFAULT b'0' NULL,
    CONSTRAINT uc_discount_coupons_code UNIQUE (code)
) COLLATE = utf8mb4_unicode_ci;

CREATE INDEX idx_forPaywall ON discount_coupons (forPaywall, availabilityEnds);

И

CREATE TABLE fixed_coupon_eligibility (
    id                 BIGINT AUTO_INCREMENT PRIMARY KEY,
    discount_coupon_id BIGINT,
    allowed            BIT NOT NULL,
    attribute          VARCHAR(64) NULL,
    attribute_value    VARCHAR(64) NULL,
    CONSTRAINT FK_FIXED_COUPON_ELIGIBILITY_ON_DISCOUNT_COUPON
        FOREIGN KEY (discount_coupon_id) REFERENCES discount_coupons (id)
) COLLATE = utf8mb4_unicode_ci;

CREATE INDEX idx_discount_coupon_id_attribute_attribute_value_index
    ON fixed_coupon_eligibility (discount_coupon_id, attribute, attribute_value);

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

SELECT
    d1_0.code,
    d1_0.description,
    d1_0.discounttype,
    d1_0.discountvalue
FROM discount_coupons d1_0
JOIN fixed_coupon_eligibility f1_0 ON d1_0.id = f1_0.discount_coupon_id
-- повторите еще 11 раз с аналогичными объединениями для разных атрибутов...
WHERE d1_0.status = 0
AND d1_0.availabilitystarts <= '2024-09-06 08:08:57'
AND d1_0.availabilityends >= '2024-09-06 08:08:57'
AND d1_0.availablecount >= 1
AND d1_0.forpaywall = 1
AND f1_0.allowed = 1
AND f1_0.attribute="ATTRIBUTE1"
AND f1_0.attribute_value IN ('ALL', 'VALUE2')
AND f2_0.allowed = 1
AND f2_0.attribute="ATTRIBUTE2"
AND f2_0.attribute_value IN( 'ALL', 'VALUE2' )
AND f3_0.allowed = 1
AND f3_0.attribute="ATTRIBUTE3"
AND f3_0.attribute_value IN( 'ALL', 'VALUE2' )
-- дополнительные аналогичные условия AND для каждого объединения...
....
AND f12_0.allowed = 1
AND f12_0.attribute="ATTRIBUTE12"
AND f12_0.attribute_value IN( 'ALL', 'VALUE2' );

Когда число строк, соответствующих начальным условиям в таблице discount_coupons, увеличивается, нагрузка на процессор возрастает до 100%, и база данных в конечном итоге выходит из строя. Запрос включает несколько объединений с одной и той же таблицей (fixed_coupon_eligibility) для фильтрации на основе различных атрибутов.

Обратите внимание на ALL в запросах, которые составляют большинство строк во второй таблице.
(Число строк, где attribute_value = ALL > 90%)

Мои вопросы таковы:

  1. Могу ли я что-то сделать со значениями ‘ALL’?
  2. Может ли какое-либо изменение архитектуры помочь мне с производительностью?
  3. Есть ли какой-либо дополнительный индекс, который может мне помочь? Для справки, 10% моей базы данных имеет forPaywall = true, для которого большая часть фильтрации происходит по availabilityEnds, поэтому я использовал этот индекс.

Мои ограничения заключаются в том, что используемая база данных должна быть только MySQL, и между всеми 12 параметрами есть условие AND.

Спасибо, что дошли до этого момента. Любая помощь будет очень ценна.

Я пробовал изменять различные индексы на таблице скидочных купонов. Лучшие индексы, которые сработали до сих пор, вы видите.

Я также пробовал explain запросы, чтобы увидеть выполнение, которые показали, что внешняя таблица (discount_coupons) сканирует около 3,793 строк (из 1,2 миллиона строк) и выдает только около 57 строк после фильтрации.

Ответ или решение

Для оптимизации SQL запроса с множественными объединениями и фильтрами, как в вашем случае, есть несколько подходов, которые могут помочь улучшить производительность:

1. Улучшение обработки значений ‘ALL’:

Если в вашем запросе много строк с атрибутом attribute_value = 'ALL' (более 90% от общего числа строк), вы можете рассмотреть возможность выполнения отдельного запроса для этих записей. Таким образом, вы сможете избежать дополнительных объединений для этих записей и, возможно, уменьшить количество строк, обрабатываемых во многих объединениях.

Стратегия:

  • Сначала выполните запрос, который выбирает коды купонов, относящиеся к атрибуту ALL, независимо от остальных атрибутов.
  • Затем объедините результаты этого запроса с другими атрибутами, которые вам нужны.

Примерный запрос:

SELECT d.code, d.description, d.discountValue
FROM discount_coupons d
WHERE d.status = 0
AND d.availabilitystarts <= '2024-09-06 08:08:57'
AND d.availabilityends >= '2024-09-06 08:08:57'
AND d.availablecount >= 1
AND d.forpaywall = 1
AND d.id IN (
    SELECT f.discount_coupon_id
    FROM fixed_coupon_eligibility f
    WHERE f.allowed = 1 
    AND f.attribute = 'ATTRIBUTE1' 
    AND f.attribute_value IN ('ALL', 'VALUE2')
    -- здесь добавьте другие объединения с атрибутами без 'ALL'
)

2. Архитектурные изменения:

Рассмотрите возможность использования некоторых архитектурных улучшений:

  • Шардинг и репликация: Если у вас есть возможность горизонтального масштабирования базы данных, вы можете разделить данные по разным серверам (шардинг).
  • Кэширование результатов: Используйте кэширование для хранения часто запрашиваемых результатов. Это может значительно уменьшить нагрузку на базу данных.
  • Оптимизация конфигурации MySQL: Убедитесь, что настройки вашего сервера MySQL оптимальны. Например, увеличьте размеры буферов соединений и кэша, чтобы уменьшить необходимость обращения к диску.

3. Дополнительные индексы:

Вы уже добавили индекс для forPaywall и availabilityEnds, что хорошо. Однако, возможно, есть смысл добавить более специализированные индексы или пересмотреть имеющиеся.

  • Исходя из ваших фильтров, попытайтесь создать составной индекс, который будет включать несколько столбцов, например:
CREATE INDEX idx_discount_coupons_filters ON discount_coupons (status, availabilityStarts, availabilityEnds, availableCount, forPaywall);
  • В таблице fixed_coupon_eligibility вы также можете рассмотреть составной индекс, который включает allowed, attribute, attribute_value, как это уже частично сделано. Возможно, стоит добавить индекс для allowed и discount_coupon_id вместе.

Заключение:

Для вашей ситуации оптимизация включает в себя комбинацию улучшения обработки данных, архитектурных изменений и корректировки индексов. Обсудите эти предложения с командой разработки, чтобы совместно выбрать лучший путь и протестировать предложенные изменения. После реализации рекомендуемых мер обязательно проведите тестирование производительности, чтобы убедиться в улучшении ситуации.

Оцените материал
Добавить комментарий

Капча загружается...