2011-01-19 48 views
0

所以我有一个存储联系人并允许它们分组的系统。这些群体可以通过标准来定义(每个人都有姓氏'smith'),或者通过明确添加/排除人员来定义。优化和缩放mysql结构+针对大型邮件群组的查询

我遇到的问题是,当我列出邮件组时,我需要统计每个联系人的数量。这个号码可以在联系人添加/删除联系人表时改变。在小团体/联系人数量是正常,但是使用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的表作为外键。

这基本上需要年龄/次数/得到错误的数字/一般不能很好地工作。我需要找出一个更高效的查询,或找出更好的方法来存储所有内容。

任何想法?查询

组成数据库

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 

而且指数法的5个主要表/解释。必须承认,索引不是我的强项,有什么改进?

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可能会被称为contacts.email或if你把表名改为'c',然后c.email,我知道这并没有帮助,但它会让你的查询更具可读性。 – DeveloperChris 2011-01-19 23:31:34

+0

耶是试图做到这一点,但有问题登录到ssh从我当时的地方。必须从phpmyadmin获得这些输出,这似乎只是一个'打印视图' – Horse 2011-01-20 14:39:57

回答

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样式,并立即注意到您在联系人和mgroups之间没有连接条件。这意味着优化器将创建一个交叉连接(也称为笛卡尔产品),这可能是您不想做的事情。交叉连接(如果您不知道)会将联系人表中的每一行与mgroups表中的每一行进行连接。因此,如果您在联系人中有50,000行,在mgroup中有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具有200万行和表2具有500,000。对于外部查询(table1)中的每一行,数据库将必须对内部查询执行完整扫描。因此,为了得到结果,数据库将读取1,000,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; 

外连接不要求连接表中的每条记录都有匹配的记录。因此,上面的示例中,即使table2上没有匹配,我们也会从table1中获取所有记录。对于不匹配的记录,数据库返回一个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