2011-01-19 1 views
0

У меня есть система, которая хранит контакты и позволяет их группировать. Эти группы могут быть определены по критериям (все с фамилией «кузнец») или путем явного добавления/исключения людей.оптимизация и масштабирование структуры mysql + запросы для больших групп рассылки

Проблема, с которой я столкнулась, заключается в том, что когда я перечислил группы рассылки, мне нужно подсчитать, сколько контактов в каждом из них. Это число может меняться по мере добавления/удаления контактов из таблицы контактов. На малых групп/количество контактов это прекрасно, однако, используя 50k иш контакты сталкивается с проблемами

Пример запроса Я использую для этого заключается в следующем:

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... 
+0

Скорее смущает, не так ли. Одним из советов, которые я могу дать сразу, является использование точечной нотации, таким образом, вы можете иметь столбцы с одним и тем же именем в разных таблицах, не опасаясь конфликтов, и их легче читать, поэтому c_email можно было бы называть contact.email или if вы также называете имя таблицы «c», а затем c.email, я знаю, что это не помогает, но это сделает ваши запросы более читабельными. – DeveloperChris

+0

Да, он пытался это сделать, но у меня возникали проблемы с входом в ssh с того места, где я был в то время. Должен был получить эти выходы из phpmyadmin, который, кажется, только делает «вид печати» – Horse

+0

извините неправильно, подумал, что вам нужен приятный вид с таблицами таблиц. согласен с точечной нотацией, мой плохой – Horse

ответ

0

правой, так что я получил это в другом месте (Огромное спасибо 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 имеет 2000000 строк и имеет таблица2 500000. Для каждой строки внешнего запроса (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; 
1

Я не вижу индексов в схеме выше, у вас есть индексы, не так ли?

запустить объяснить на запрос

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

+0

К сожалению, у меня есть индексы на c_id, mg_id, me_mg_id, me_c_id, обновит основную запись с выходом, thx – Horse

+0

не уверен, что еще нужно индексировать, поле c_tags является просто примером, критерии могут быть основаны на одном или многих из полей таблицы контактов – Horse

+0

добавил индекс к contacts.site_id, который много улучшил, – Horse

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