Нужна помощь в оптимизации запроса MySQL без соединения

Мой запрос в настоящее время занимает примерно 3 секунды, и я уверен, что его можно оптимизировать. Я просто не могу понять, как его оптимизировать.

В моем приложении достаточно большая таблица products (примерно 500 000 записей). Каждый продукт может быть указан в одном из 50 доменов (перечисленных в таблице domains). Связи между продуктами и доменами хранятся в таблице domains_products (которая содержит примерно 1 400 000 записей). Медленный запрос находится в разделе администратора моего приложения, где мне нужно иметь возможность видеть продукты, которые НЕ указаны ни в одном домене.

Разобранный до голых костей с удаленными всеми несвязанными соединениями, рассматриваемый запрос выглядит следующим образом:

SELECT    `products`.*
FROM      `products`
LEFT JOIN `domains_products`
ON        `domains_products`.`product_id` = `products`.`id`
WHERE     `products`.`deleted` = 'N'
AND       `domains_products`.`domain_id` IS NULL
ORDER BY  `products`.`id` ASC

В таком виде запрос занимает более 3 секунд и возвращает чуть более 3000 товаров (что верно). Если я удалю любое предложение WHERE, запрос займет 0,12 секунды (но, очевидно, не вернет правильные результаты).

Обе таблицы используют механизм InnoDB. Таблица products имеет первичный ключ в столбце id и индекс в столбце deleted. В таблице domains_products есть только столбцы product_id и domain_id, первичный ключ находится в обоих этих столбцах, и у них обоих есть собственный индекс. Все соответствующие столбцы являются NOT NULL столбцами.

EXPLAIN дает мне это:

id select_type table            type possible_keys key        key_len ref         rows   Extra
1  SIMPLE      products         ref  deleted       deleted    1       const       188616 Using where
1  SIMPLE      domains_products ref  product_id    product_id 4       products.id 1      Using where; Using index; Not exists

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

Профайлер говорит следующее:

Status               Time
Starting             62 µs
Checking Permissions 7 µs
Checking Permissions 5 µs
Opening Tables       38 µs
System Lock          13 µs
Init                 37 µs
Optimizing           17 µs
Statistics           1,3 ms
Preparing            25 µs
Executing            5 µs
Sorting Result       5 µs
Sending Data         3,3 s
End                  28 µs
Query End            8 µs
Closing Tables       25 µs
Freeing Items        297 µs
Logging Slow Query   4 µs
Cleaning Up          5 µs

Обратите внимание, что он, кажется, висит на Sending Data. Я попытался заменить соединение на NOT IN:

SELECT `products`.*
FROM   `products`
WHERE  `products`.`deleted` = 'N'
AND    `product`.`id` NOT IN (
    SELECT `product_id`
    FROM   `domains_products`
)
ORDER BY `products`.`id` ASC

Этот запрос дает точно такие же результаты, но занимает 3,8 секунды.

Может ли кто-нибудь указать мне правильное направление для оптимизации этого запроса?


person rickdenhaan    schedule 01.01.2013    source источник
comment
Re: Обратите внимание, что, хотя MySQL обнаружила правильные ключи, похоже, что на самом деле они их не используют: почему вы так говорите?   -  person ruakh    schedule 01.01.2013
comment
Вы удалили большие части таблицы или внесли другие большие изменения? Возможно, проблему решит OPTIMIZE TABLE.   -  person lc.    schedule 01.01.2013
comment
@ruakh Я не эксперт в чтении синтаксиса EXPLAIN, но, поскольку он говорит «Используя где» в дополнительном столбце, я предполагаю, что MySQL не использует индекс. Поправьте меня если я ошибаюсь.   -  person rickdenhaan    schedule 02.01.2013
comment
@лк. Собственно, два дня назад все приложение было перенесено на новый сервер (с VPS на выделенный сервер). Таким образом, вся база данных совершенно новая. Этот запрос был медленным и на старом VPS, я надеялся, что переезд ускорит процесс.   -  person rickdenhaan    schedule 02.01.2013
comment
@rickdenhaan: я тоже не эксперт в чтении вывода EXPLAIN, и, учитывая объяснение Using where в документации, я определенно понимаю, почему вы интерпретируете его так, как делаете. Все, что я могу сказать, это то, что я совершенно уверен, что документация вводит в заблуждение и что Using where здесь безвреден.   -  person ruakh    schedule 02.01.2013
comment
@ruakh Хорошо, возможно, MySQL использует индекс, а может и нет. Этот запрос все еще медленный ;-)   -  person rickdenhaan    schedule 02.01.2013
comment
Есть ли внешний ключ, связывающий две таблицы, или просто индекс?   -  person lc.    schedule 02.01.2013
comment
@lc В domains_products есть фактические внешние ключи: domain_id ссылки domains.id (при каскадном удалении/обновлении) и product_id ссылки products.id (при каскадном удалении/обновлении)   -  person rickdenhaan    schedule 03.01.2013


Ответы (3)


Похоже, проблема в столбце «удалено». Я предполагаю, что почти все элементы в таблице продуктов отмечены «N», что делает индекс в столбце «удалено» в этом случае довольно бесполезным.

Одна вещь, которую вы можете сделать, это создать другую таблицу, скажем, delete_domains_products, в которой будет храниться product_id (и domain_id, если хотите). Затем вы создаете триггер, чтобы каждый раз, когда запись удалялась из domains_products, она вставляла запись в эту таблицу. Тогда у вас будет меньший набор для запроса. И когда вы закончите, вы можете обрезать эту таблицу в следующий раз, так что это всегда должно быть довольно быстро.

person sirezekiel    schedule 02.01.2013
comment
В настоящее время существует 15991 продукт, где deleted – это Y, и 487 601 продукт, где deleted – это N. Я попробую вашу идею и дам вам знать, что получится. - person rickdenhaan; 03.01.2013
comment
Спасибо, это (в основном) решило проблему. Я добавил новую таблицу, содержащую только идентификаторы удаленных продуктов, и добавил триггер в мою таблицу products для автоматического добавления идентификатора, если продукт установлен как удаленный (и наоборот). Это сократило запрос до управляемого среднего значения 1,5 секунды, что я считаю приемлемым. - person rickdenhaan; 08.01.2013

Попробуйте создать следующие индексы, а затем повторно выполните запрос:

  1. domains_products (идентификатор_продукта, идентификатор_домена)
  2. товары (id, удалено)

Расскажите нам, как это происходит

person ronpy    schedule 01.01.2013
comment
Первичный ключ для domains_products находится в обоих этих столбцах. Я добавил индекс в таблицу продуктов, запрос занял 2,9, 3,5 и 3,4 секунды три раза, когда я его запускал. - person rickdenhaan; 02.01.2013
comment
Да, это оба индекса составных полей. - person rickdenhaan; 03.01.2013

Попробуйте это и дайте мне знать, сколько времени это займет.

SELECT `products`.*
FROM   `products`
WHERE  `products`.`deleted` = 'N'
AND    NOT EXISTS (SELECT 1 
               FROM `domains_products` 
               WHERE `domains_products`.`product_id` = `products`.`id`
              );
ORDER BY `products`.`id` ASC
person Raghvendra Parashar    schedule 01.01.2013
comment
Этот запрос занимает 0,0015 секунды, но возвращает неверные результаты. Если я переверну его: SELECT products.* FROM products WHERE products.deleted = 'N' AND NOT EXISTS (SELECT 1 FROM domains_products WHERE domains_products.product_id = products.id ) ORDER BY products.id ASC возвращает правильные результаты, но принимает 4,2 секунды. - person rickdenhaan; 02.01.2013