Итак, у меня есть система, которая хранит контакты и позволяет их помещать в группы. Эти группы могут быть определены по критериям (все с фамилией "кузнец" ) или путем явного добавления/исключения людей.
Проблема, с которой я столкнулась, заключается в том, что когда я перечисляю группы рассылки, мне нужно подсчитать, сколько контактов в каждом из них. Это число может меняться по мере добавления/удаления контактов из таблицы контактов. В небольших группах/количествах контактов это нормально, однако использование контактов 50k ish сталкивается с проблемами.
Пример запроса, который я использую для этого, выглядит следующим образом:
SELECT COUNT(c_id) FROM contacts, mgroups
LEFT JOIN mgroups_explicit ON mg_id = me_mg_id
WHERE mgroups.site_id = '10'
AND mg_id = '20'
AND me_c_id = c_id
AND contacts.site_id = '10'
OR (contacts.site_id = '10' AND ( c_tags LIKE '%tag1%')) AND c_id NOT IN
( SELECT mex_c_id FROM mgroups_exclude WHERE c_id = mex_c_id ) GROUP BY c_id
Таблица критериев не используется в этом запросе, поскольку проблема возникает, когда большие группы создаются явно, а не с критериями. Это необходимо, поскольку группы, основанные на критериях, растут или сжимаются "на лету", когда вы изменяете свои контакты, где, как явные, обычно задаются в камне. Таким образом, в этом случае, если вы явно добавляете 20k контактов в группу, она добавляет 20k строк в таблицу, помеченную этим mg_id как внешний ключ.
В основном это занимает возраст/время/получает неправильный номер/вообще не работает очень хорошо. Мне нужно либо выяснить более эффективный запрос, либо найти лучший способ сохранить все.
Любые идеи?
5 основных таблиц, составляющих базу данных
contacts - where the actual contacts reside
Field Type Null Default Comments
c_id int(8) No
site_id int(6) No
c_email varchar(500) No
c_source varchar(255) No
c_subscribed tinyint(1) No 0
c_special tinyint(1) No 0
c_domain text No
c_title varchar(12) No
c_name varchar(128) No
c_surname varchar(128) No
c_company varchar(128) No
c_jtitle text No
c_ad1 text No
c_ad2 text No
c_ad3 text No
c_county varchar(64) No
c_city varchar(128) No
c_postcode varchar(32) No
c_lat varchar(100) No
c_lng varchar(100) No
c_country varchar(64) No
c_tel varchar(20) No
c_mob varchar(20) No
c_dob date No
c_registered datetime No
c_updated datetime No
c_twitter varchar(255) No
c_facebook varchar(255) No
c_tags text No
c_special_1 text No
c_special_2 text No
c_special_3 text No
c_special_4 text No
c_special_5 text No
c_special_6 text No
c_special_7 text No
c_special_8 text No
mgroups - basic mailing group info
Field Type Null Default Comments
mg_id int(8) No
site_id int(6) No
mg_name varchar(255) No
mg_created datetime No
mgroups_criteria - criteria for said mailing groups
Field Type Null Default Comments
mc_id int(8) No
site_id int(6) No
mc_mg_id int(8) No
mc_criteria text No
mgroups_exclude - anyone to exclude from criteria
Field Type Null Default Comments
mex_id int(8) No
site_id int(6) No
mex_c_id int(8) No
mex_mg_id int(8) No
mgroups_explicit - anyone to explicitly add without the use of criteria
Field Type Null Default Comments
me_id int(8) No
site_id int(6) No
me_c_id int(8) No
me_mg_id int(8) No
И индексы/объяснение запроса. Должен признаться, индексы не мои сильные стороны, никаких улучшений?
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY mgroups ALL PRIMARY,mg_id NULL NULL NULL 9 Using temporary; Using filesort
1 PRIMARY mgroups_explicit ref me_mg_id me_mg_id 4 engine_4.mgroups.mg_id 8750
1 PRIMARY contacts ALL PRIMARY,c_id NULL NULL NULL 86012 Using where; Using join buffer
2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const table...
Итак, я получил это в другом месте (Огромное спасибо 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 и сразу заметил, что у вас нет условия соединения между контактами и группами. Это означает, что оптимизатор создаст перекрестное соединение (также называемое декартово), что, вероятно, не то, что вы не хотите делать. Перекрестное соединение (в случае, если вы не знаете) соединяет каждую строку в таблице контактов с каждой строкой в таблице групп. Так что если у вас 50 000 строк в контактах и 20 000 строк в группе, вы получите объединенный набор результатов, содержащий 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);
Предположим, что таблица1 имеет 2 000 000 строк, а таблица2 - 500 000. Для каждой строки внешнего запроса (table1) база данных должна будет выполнить полную проверку внутреннего запроса. Таким образом, чтобы получить результат, база данных будет читать 1000 000 000 000 строк, и нас может интересовать только 1000! Он не будет касаться каких-либо индексов независимо от того, что.
Чтобы обойти это, мы можем использовать левое соединение (также называемое левым внешним соединением) в двух таблицах.
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;
Я не вижу индексов в приведенной выше схеме, у вас есть индексы, не так ли?
запустите объяснение по запросу
EXPLAIN
SELECT COUNT(c_id) FROM
contacts, mgroups LEFT JOIN mgroups_explicit ON mg_id = me_mg_id
WHERE
mgroups.site_id = '10'
AND mg_id = '20'
AND me_c_id = c_id
AND contacts.site_id = '10'
OR (contacts.site_id = '10'
AND ( c_tags LIKE '%tag1%'))
AND c_id NOT IN (SELECT mex_c_id FROM mgroups_exclude WHERE c_id = mex_c_id ) GROUP BY c_id
Это расскажет вам, какие индексы используются, сколько записей нужно сортировать и т.д.
DC