оптимизация и масштабирование структуры mysql + запросы для больших групп рассылки

0

Итак, у меня есть система, которая хранит контакты и позволяет их помещать в группы. Эти группы могут быть определены по критериям (все с фамилией "кузнец" ) или путем явного добавления/исключения людей.

Проблема, с которой я столкнулась, заключается в том, что когда я перечисляю группы рассылки, мне нужно подсчитать, сколько контактов в каждом из них. Это число может меняться по мере добавления/удаления контактов из таблицы контактов. В небольших группах/количествах контактов это нормально, однако использование контактов 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...
  • 0
    Скорее сбивает с толку, не так ли? Один совет, который я могу дать сразу же, - это использовать точечную нотацию, чтобы вы могли иметь столбцы с одинаковыми именами в разных таблицах, не боясь конфликта, и его было легче читать, поэтому c_email можно было бы назвать inste3ad как contacts.email или вы называете имя таблицы 'c', а затем c.email, я знаю, что это не поможет, но это сделает ваши запросы более читабельными.
  • 0
    да, пытался сделать это, но у меня были проблемы со входом в SSH, где я был в то время. Должен был получить эти выходные данные от phpmyadmin, который, кажется, только делает «просмотр печати»
Показать ещё 2 комментария
Теги:
optimization
scaling
mailing-list

2 ответа

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 имеет 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;
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
  • 0
    не уверен, что еще нужно индексировать, поле c_tags является лишь примером, критерии могут быть основаны на одном или нескольких полях в таблице контактов
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню