优化和扩展mysql结构+;查询大型邮件组
所以我有一个存储联系人的系统,允许他们分组。这些组可以通过标准(姓氏为“smith”的每个人)定义,也可以通过明确添加/排除人员来定义 我遇到的问题是,当我列出邮件组时,我需要计算每个组中有多少联系人。在联系人表中添加/删除联系人时,此号码可能会更改。对于小组/数量的触点,这很好,但是使用50k ish触点会遇到问题 我使用的查询示例如下:优化和扩展mysql结构+;查询大型邮件组,mysql,optimization,scaling,mailing-list,Mysql,Optimization,Scaling,Mailing List,所以我有一个存储联系人的系统,允许他们分组。这些组可以通过标准(姓氏为“smith”的每个人)定义,也可以通过明确添加/排除人员来定义 我遇到的问题是,当我列出邮件组时,我需要计算每个组中有多少联系人。在联系人表中添加/删除联系人时,此号码可能会更改。对于小组/数量的触点,这很好,但是使用50k ish触点会遇到问题 我使用的查询示例如下: SELECT COUNT(c_id) FROM contacts, mgroups LEFT JOIN mgroups_explicit ON mg_id
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
此查询中没有criteria表,因为当显式创建大型组而不是使用条件时,问题就会出现。这是必需的,因为在修改联系人时,基于标准的组会动态增长或收缩,而“明确”通常是一成不变的。因此,在本例中,如果显式地将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...
我在上面的模式中没有看到任何索引,您确实有索引,不是吗 对查询运行解释
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没错,所以我在其他地方得到了答案(非常感谢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';
它更简洁、更容易读取哪些位是连接条件,哪些位是where子句。最好养成使用ANSI样式的习惯,因为旧样式的支持可能(在某个时候)会停止
此外,在使用点符号和/或别名时也要尽量保持一致。同样,它使大型查询更容易阅读
回到您的问题查询,我开始将其转换为ANSI样式,并立即注意到您在contacts和Mgroup之间没有连接条件。这意味着优化器将创建一个交叉连接(也称为笛卡尔积),这可能是您不想做的事情。交叉连接(如果您不知道)将contacts表中的每一行与mgroups表中的每一行连接起来。因此,如果contacts中有50000行,mgroup中有20000行,那么将得到一个包含100000000行的联接结果集
另一件会大大降低查询速度的事情是mgroups\u 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行。对于外部查询(表1)中的每一行,数据库都必须对内部查询进行完整扫描。因此,为了得到结果,数据库将读取10000000000行,我们可能只对1000行感兴趣!无论发生什么,它都不会触及任何索引
为了解决这个问题,我们可以在两个表上使用左连接(也称为左外部连接)
SELECT a.column1
FROM table1 a LEFT JOIN table2 b ON a.id_key = b.fid_key
WHERE b.fid_key IS NULL;
外部联接不要求联接表中的每条记录都有匹配的记录。因此,在上面的示例中,即使表2中没有匹配项,我们也可以从表1中获取所有记录。对于不匹配的记录,数据库返回NULL,我们可以在where子句中进行测试。现在优化器可以扫描两个表id_键字段上的索引(假设有),从而实现更快的查询
所以,总结一下。我会改写你最初的疑问,这样:
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;
很让人困惑,不是吗。我可以马上给出的一条建议是使用点符号,这样你就可以在不同的表中使用相同名称的列,而不必担心冲突,而且更易于阅读,因此c_电子邮件可以称为contacts.email,或者如果你将表名别名为“c”,那么就可以称为c.email,我知道这没有帮助,但它会使您的查询更具可读性。是的,我试图这样做,但从我当时所在的位置登录ssh时遇到问题。必须从phpmyadmin获取这些输出,它似乎只执行“打印视图”抱歉,我误解了,因为您需要一个漂亮的表选项卡视图。同意点表示法,我的badI倾向于使用这种命名约定创建数据库字段,例如所有联系人字段都用c_2;作为前缀,其他表字段如mg_2;、me_2;、mc_2;、mex_2;抱歉,我在c_2;id、mg_2;id、me_2_2;c_2;id上有索引,将用输出更新主帖,不确定还要索引什么,c_tags字段只是一个例子,标准可以基于contacts表中的一个或多个字段添加了一个contacts.site_id索引,这大大改善了情况,