2015-01-21 2 views
-2

У меня проблема с медленным запросом MySQL (MySQL 5+). Давайте подумаем из трех таблиц:Медленные внутренние соединения с подзапросами в MySQL

customers: 
- id_customer : int (PRIMARY) 
- name  : varchar(255) 

customers_addresses: 
- id_customers_addresses : int (PRIMARY) 
- id_customer : int (INDEX) 
- street : varchar(255) 
- zipcode : varchar(255) 
- city : varchar(255) 

customers_contacts: 
- id_customers_contacts : int (PRIMARY) 
- id_customer : int (INDEX) 
- type : varchar(255) 
- value : varchar(255) 

Теперь моя цель состоит в том, чтобы собрать все адреса и контактную информацию в одном запросе и с одной строки на каждого клиента. Моя первая попытка была с помощью LEFT JOIN с, как некоторые клиенты не имеют никакого адреса и/или контактную информацию:

SELECT customers.id_customer, 
     customers.name, 
     X.contact AS contact, 
     Y.street, 
     Y.zipcode, 
     Y.city 
FROM customers 
LEFT JOIN 
(
    SELECT 
    GROUP_CONCAT(CONCAT(type, ': ', value) SEPARATOR ', ') AS contact, 
    id_customer 
    FROM customers_contacts 
    GROUP BY id_customer 
) AS X 
ON X.id_customer = customers.id_customer 

LEFT JOIN 
(
    SELECT 
    GROUP_CONCAT(street SEPARATOR '<br>') AS street, 
    GROUP_CONCAT(zipcode SEPARATOR '<br>') AS zipcode, 
    GROUP_CONCAT(city SEPARATOR '<br>') AS city, 
    id_customer 
    FROM customers_addresses 
    GROUP BY id_customer 
) AS Y 
ON Y.id_customer = customers.id_customer 
WHERE Y.street LIKE '%Avenue%' 
ORDER BY customers.name DESC 
LIMIT 0, 20 

Этот запрос потребовался более 130 секунд, чтобы завершить (за ~ 7000 записей в каждой таблице), что далеко из хорошо.

Предварение EXPLAIN EXTENDED дает:

id select_type table    type possible_keys key   key_len  ref  rows filtered Extra 
1 PRIMARY  customers   ref  name   name   3   const 4334 100.00  Using where; Using temporary; Using filesort 
1 PRIMARY  <derived2>   ALL  NULL   NULL   NULL  NULL 7793 100.00 
1 PRIMARY  <derived3>   ALL  NULL   NULL   NULL  NULL 8580 100.00  Using where 
3 DERIVED  customers_addresses index NULL   id_customer 5   NULL 8651 100.00 
2 DERIVED  customers_contacts index NULL   id_customer 4   NULL 9314 100.00 

Я прочитал некоторые сообщения StackOverflow и документации MySQL. Оба говорят, что INNER JOIN намного быстрее. Я пытался повторить LEFT JOIN поведение с INNER JOIN с помощью UNION ALL:

SELECT customers.id_customer, 
     customers.name, 
     X.contact AS contact, 
     Y.street, 
     Y.zipcode, 
     Y.city 
FROM customers 
INNER JOIN 
(
    SELECT 
    GROUP_CONCAT(CONCAT(type, ': ', value) SEPARATOR ', ') AS contact, 
    id_customer 
    FROM customers_contacts 
    GROUP BY id_customer 
    UNION ALL 
    SELECT 
    '' AS contact, 
    id_customer 
    FROM customers 
    WHERE id_customer NOT IN (SELECT DISTINCT id_customer FROM customers_contacts) 
) AS X 
ON X.id_customer = customers.id_customer 

INNER JOIN 
(
    SELECT 
    GROUP_CONCAT(street SEPARATOR '<br>') AS street, 
    GROUP_CONCAT(zipcode SEPARATOR '<br>') AS zipcode, 
    GROUP_CONCAT(city SEPARATOR '<br>') AS city, 
    id_customer 
    FROM customers_addresses 
    GROUP BY id_customer 
    UNION ALL 
    SELECT 
    '' AS street, 
    '' AS zipcode, 
    '' AS city, 
    id_customer 
    FROM customers 
    WHERE id_customer NOT IN (SELECT DISTINCT id_customer FROM customers_addresses) 
) AS Y 
ON Y.id_customer = customers.id_customer 
WHERE Y.street LIKE '%Avenue%' 
ORDER BY customers.name DESC 
LIMIT 0, 20 

Этот запрос улучшена производительность на 20 секунд. Но 110 секунд все еще неприемлемы.

Предварение EXPLAIN EXTENDED:

id select_type   table    type   possible_keys  key  key_len ref   rows filtered Extra 
1 PRIMARY    <derived2>   ALL   NULL    NULL  NULL NULL  8596 100.00  Using temporary; Using filesort 
1 PRIMARY    <derived5>   ALL   NULL    NULL  NULL NULL  8604 100.00  Using join buffer 
1 PRIMARY    customers   eq_ref   PRIMARY,name,name3 PRIMARY 4  Y.id_kunde 1  100.00  Using where 
5 DERIVED    customers_addresses index   NULL    id_kunde 5  NULL  8651 100.00 
6 UNION    customers   index   NULL    name2 767  NULL  8677 100.00  Using where; Using index 
7 DEPENDENT SUBQUERY customers_addresses index_subquery id_kunde   id_kunde 5  func  2  100.00  Using index 
NULL UNION RESULT  <union5,6>   ALL   NULL    NULL  NULL NULL  NULL NULL 
2 DERIVED    customers_contacts index   NULL    id_kunde 4  NULL  10411 100.00 
3 UNION    customers   index   NULL    name2 767  NULL  8677 100.00  Using where; Using index 
4 DEPENDENT SUBQUERY customers_contacts index_subquery id_kunde   id_kunde 4  func  1  100.00  Using index 
NULL UNION RESULT  <union2,3>   ALL   NULL    NULL  NULL NULL  NULL NULL 

Так вот мой вопрос: Как улучшить один из этих запросов и/или таблиц базы данных, чтобы получить супер быстрый ответ? Я не только заинтересован в решении, но и в стратегии, как предотвратить такую ​​производительность в будущем.

С уважением.

+0

«Этот запрос занял более 130 секунд, чтобы завершить« Er, no. Этот запрос является синтаксически неправильным. – Strawberry

+0

Извините, я упустил часть 'WHERE', пока убирал запрос на переполнение стека. должно быть правильным (проверено) – Timm

+0

Почему 'LEFT JOIN' на' customers_addresses', когда он должен иметь улицу 'LIKE '% Avenue%''? –

ответ

1

Как общее правило, которое применяется здесь, можно сказать следующее:

Всякий раз, когда вы используете запрос, который присоединяется отборный результат (подзапросы), MySQL должен запустить эти подзапросы, а затем создать таблицу от результатов. Вы делаете это дважды, а это значит, что MySQL сначала создает 2 таблицы, а только после их завершения. Благодаря правильному управлению памятью для MySQL это делается в памяти. Но эти таблицы создаются без индекса, так как MySQL не может магически определить, какой индекс лучше всего подходит для этих производных таблиц, и потому, что они обычно создаются в памяти, запросы на них довольно быстрые (не так быстро, как SELECT с использованием ключей).

Затем, когда две таблицы являются исчерпывающими, MySQL должен присоединиться к вашей исходной таблице с обоими, и создать третья таблица «на лету», которая должна быть отфильтрована и отсортирована на основе ваших критериев.

Это убийца исполнения. Одна из проблем, которые предъявляют ваши требования, заключается в том, что каждый клиент должен приводить только к одной строке. Это не то, как база данных сохраняет информацию, и поэтому вы платите цену во время выполнения для преобразования данных (ваши инструкции GROUP_CONCAT). Я не уверен на 100%, что делает текущий движок базы данных MySQL с UNION-инструкциями, поэтому я бы не стал комментировать их.

Использование простых ВНУТРЕННИХ СОЕДИНЕНИЙ по доступным клавишам, но в результате получается несколько строк для клиента, когда результатом является несколько адресов, вы обнаружите, что производительность сильно ускоряется. Вы можете легко перебирать клиентов на своем языковом уровне программирования, запрашивая адреса для одного клиента за раз, если вам не комфортно разделять результат всех клиентов и всех связанных адресов с клиентами на этом уровне.

TL; DR: оставьте свое требование или живите с накладными расходами.

Смежные вопросы