所以我有一个存储联系人的系统,允许将它们分组。这些组可以通过标准(姓氏为“史密斯”的每个人)或明确添加/排除人来定义。
我遇到的问题是,当我列出邮件群组时,我需要计算每个群组中的联系人数量。在联系人表格中添加/删除联系人时,此号码可能会发生变化。在小组/联系人数量上它是好的,但使用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个联系人,它会将标有该mg_id的表添加20k行作为外键。
这基本上需要年龄/次数/得到错误的数字/通常不能很好地工作。我要么想要找出更有效的查询,要么想出更好的存储方法。
有什么想法吗?
构成数据库的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 :(得分: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
答案 1 :(得分: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样式,并且直接注意到您在联系人和mgroup之间没有连接条件。这意味着优化器将创建一个交叉连接(也称为笛卡尔积),这可能是您不想做的事情。交叉连接(如果你不知道的话)将contacts表中的每一行与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);
假设table1有2,000,000行,table2有500,000行。对于外部查询(table1)中的每一行,数据库将必须对内部查询执行完全扫描。因此,要获得结果,数据库将读取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;
外连接不要求连接表中的每个记录都有匹配的记录。所以上面的例子我们得到table1中的所有记录,即使table2上没有匹配。对于不匹配的记录,数据库返回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;