Вопрос или проблема
Как оптимизировать 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%)
Мои вопросы таковы:
- Могу ли я что-то сделать со значениями ‘ALL’?
- Может ли какое-либо изменение архитектуры помочь мне с производительностью?
- Есть ли какой-либо дополнительный индекс, который может мне помочь? Для справки, 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
вместе.
Заключение:
Для вашей ситуации оптимизация включает в себя комбинацию улучшения обработки данных, архитектурных изменений и корректировки индексов. Обсудите эти предложения с командой разработки, чтобы совместно выбрать лучший путь и протестировать предложенные изменения. После реализации рекомендуемых мер обязательно проведите тестирование производительности, чтобы убедиться в улучшении ситуации.