0

所以我有一个系统来存储联系人并允许他们分组。这些组可以通过标准(姓氏为“smith”的每个人)或通过明确添加/排除人员来定义。

我遇到的问题是,当我列出邮件组时,我需要计算每个组中有多少联系人。随着联系人从联系人表中添加/删除,此数字可能会发生变化。在小组/联系人数量上很好,但是使用 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...
4

2 回答 2

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

这将告诉您正在使用哪些索引,它必须对多少记录进行排序等。

直流

于 2011-01-19T23:41:50.177 回答
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';

它更简洁,更容易阅读哪些位是连接条件,哪些是 where 子句。最好养成使用 ANSI 样式的习惯,因为旧样式支持可能(在某些时候)会停止。

还要尝试在使用点表示法和/或别名时保持一致。它再次使大查询更易于阅读。

回到您的问题查询,我首先开始将其转换为 ANSI 样式,并立即注意到您在联系人和 mgroup 之间没有连接条件。这意味着优化器将创建一个交叉连接(也称为笛卡尔积),这可能是您不想做的事情。交叉连接(如果您不知道)将联系人表中的每一行与 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;
于 2011-02-01T10:58:35.443 回答