Правильно, я получил ответ в другом месте (Огромное спасибо Hambut_Bulge), поэтому ради того, чтобы он был полезен для всех остальных, вот решение:
Во-первых, вы смешиваете старое иНовый стиль (ANSI) включается в тот же запрос.Это считается плохой идеей в кругах SQL.Под старым стилем я подразумеваю, что мы пишем запрос с объединением по этим линиям
SELECT a.column_name, b.column2
FROM table1 a, second_table b
WHERE a.id_key = b.fid_key
AND b.some_other_criteria = 'Y';
. В более новом стиле ANSI мы переписали бы вышеизложенное так:
SELECT a.column_name, b.column2
FROM table1 a INNER JOIN second_table b ON a.id_key = b.fid_key
WHERE b.some_other_criteria = 'Y';
Его аккуратнее илегче читать, какие биты являются условиями соединения, а какие - где.Также лучше всего привыкнуть использовать стиль ANSI, так как поддержка старого стиля может быть (в какой-то момент) прекращена.
Также старайтесь быть последовательными при использовании точечной нотации и / или псевдонимов.Опять же, это облегчает чтение больших запросов.
Возвращаясь к вашему проблемному запросу, я начал с того, что начал преобразовывать его в стиль ANSI, и сразу заметил, что у вас нет условия соединения между контактами и группами.Это означает, что оптимизатор создаст перекрестное соединение (также называемое декартовым продуктом), что, вероятно, было тем, чего вы не хотите делать.Перекрестное соединение (если вы не знали) объединяет каждую строку в таблице контактов с каждой строкой в таблице mgroups.Так что, если у вас есть 50 000 строк в контактах и 20 000 строк в mgroup, вы получите объединенный результирующий набор, содержащий 1 000 000 000 строк!
Другая вещь, которая значительно замедлит этот запрос, - это подзапрос к mgroups_exclude.Подзапрос выполняется один раз для каждой строки во внешнем запросе, например:
SELECT a.column1
FROM table1 a
WHERE a.id_key NOT IN ( SELECT * FROM table2 b WHERE a.id_key = b.fid_key);
Предположим, что table1 имеет 2 000 000 строк, а table2 имеет 500 000.Для каждой строки во внешнем запросе (таблица 1) база данных должна будет выполнить полное сканирование внутреннего запроса.Таким образом, чтобы получить результат, база данных прочитает 1 000 000 000 000 строк, а нас может интересовать только 1 000!Он не будет касаться никаких индексов, несмотря ни на что.
Чтобы обойти это, мы можем использовать левое соединение (также называемое левым внешним соединением) для двух таблиц.
SELECT a.column1
FROM table1 a LEFT JOIN table2 b ON a.id_key = b.fid_key
WHERE b.fid_key IS NULL;
ВнешнееДля объединения не требуется, чтобы каждая запись в соединенных таблицах имела соответствующую запись.Таким образом, в приведенном выше примере мы получили бы все записи из таблицы1, даже если в таблице 2 нет совпадений.Для несопоставленных записей база данных возвращает NULL, и мы можем проверить это в предложении where.Теперь оптимизатор может сканировать индексы в полях id_key двух таблиц (при условии, что они есть), что приводит к гораздо более быстрому запросу.
Итак, для завершения.Я бы переписал ваш оригинальный запрос так:
SELECT COUNT( a.c_id )
FROM contacts a
INNER JOIN mgroups b ON a.c_id = b.mg_id
LEFT JOIN mgroups_explicit c ON b.mg_id = c.me_mg_id
LEFT JOIN mgroups_exclude d ON a.c_id = d.mex_c_id
WHERE b.mg_id = '20'
AND a.site_id = '10'
AND a.c_tags LIKE '%tag1%'
AND d.mex_c_id IS NULL
GROUP BY c_id;