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

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

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

Пример запроса, который я использую для этого, выглядит следующим образом:

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 

Таблица критериев неособенность в этом запросе, так как проблема возникает, когда большие группы создаются явно, а не с критериями.Это необходимо по мере того, как группы на основе критериев растут или уменьшаются на лету, когда вы изменяете свои контакты, где явное, как правило, устанавливается в камне.Таким образом, в этом случае, если вы явно добавляете 20 тыс. Контактов в группу, он добавляет 20 тыс. Строк в таблицу, помеченную этим 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...

Ответы [ 2 ]

1 голос
/ 20 января 2011

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

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

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 голосов
/ 01 февраля 2011

Правильно, я получил ответ в другом месте (Огромное спасибо 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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...