优化NOT IN子查询

时间:2014-03-18 05:28:09

标签: mysql subquery

我正在使用MySQL数据库并尝试优化以下查询:

SELECT * FROM main 
WHERE (name IS NOT NULL AND name != '') 
AND user_id NOT IN
( SELECT user_id FROM process
WHERE sns_id = 1  AND (process.status=1 OR process.status=2) 
AND process.updated_at > 1392335789207) LIMIT 100;

基本上我想确保从user_id中选择行时,process表中的所有main都不包括在内。

我尝试使用EXISTS进行优化,但这似乎没有产生更好的效果。

我该如何优化此查询?

2 个答案:

答案 0 :(得分:1)

你可以使用 查询:

SELECT *
FROM main
WHERE name IS NOT NULL
 AND name != ''
 AND user_id NOT EXISTS
    (SELECT user_id
     FROM process
     WHERE sns_id = 1
       AND process.status in (1,2)
       AND process.updated_at > 1392335789207
       AND process.user_id = main.user_id) 
LIMIT 100

您说没有重复的用户ID,因此您可以尝试使用此查询,这可能是最快的解决方案:

SELECT m.*
FROM main m
LEFT JOIN process p
 ON p.user_id = m.user_id
 AND p.sns_id = 1
 AND p.status in (1,2)
 AND p.updated_at > 1392335789207
WHERE m.name IS NOT NULL
 AND m.name != ''
 AND p.user_id is null
LIMIT 100

答案 1 :(得分:0)

我相信使用NOT IN (subquery),该子查询将针对外部查询返回的每一行进行评估。如果要处理500,000行,则执行相同子查询的500,000行。 process的索引几乎是性能所必需的。

大型集合的(通常)更有效的方法是使用反连接模式。

为此,我们进行OUTER JOIN以从进程中查找匹配的行,然后我们丢弃找到匹配的任何行。我们剩下的是main中没有匹配行的行。相当于NOT IN(子查询),但(通常)对大型集合更有效。

SELECT m.*
  FROM main m
  LEFT
  JOIN ( SELECT p.user_id
           FROM process p
          WHERE p.sns_id = 1
            AND p.status IN (1,2)
            AND p.updated_at > 1392335789207
          GROUP BY p.user_id
       ) r
    ON r.user_id = m.user_id
 WHERE (m.name IS NOT NULL AND m.name != '')
   AND r.user_id IS NULL
 LIMIT 100;

请注意,我们在连接操作上使用LEFT关键字来指定我们希望返回main中的所有行,即使r没有匹配的行也是如此。我们在WHERE子句中包含r.user_id IS NULL作为谓词,以排除与r匹配的行。 (JOIN谓词(r.user_id = m.user_id)中的相等比较符保证来自r的任何匹配行都将具有{NOT}的user_id值。因此{{1}的任何NULL值}必须是未找到r.user_id匹配的结果。

为了提高性能,您可能希望覆盖r表中可用的索引,例如。

process

内联视图查询ON process ('user_id, `updated_at`, `status`, `sns_id`) 中的GROUP BY并非绝对必要,但我们并不需要为同一r返回多行,一个就足够了。对于前导列为user_id的索引,MySQL通常会使用索引操作来执行GROUP BY,从而避免user_id操作。我们也不需要为Using filesort返回NULL值。