在 WHERE 子句中使用连接列时,Mysql 未在 LEFT JOIN 中使用索引

Posted

技术标签:

【中文标题】在 WHERE 子句中使用连接列时,Mysql 未在 LEFT JOIN 中使用索引【英文标题】:Mysql not using index in LEFT JOIN when joined column used in WHERE clause 【发布时间】:2012-05-18 21:23:25 【问题描述】:

我已经为 mysql 5.0.51a 中的这个问题困惑了很长时间:

当使用 LEFT JOIN 连接表 AND 在 WHERE 子句中使用连接表的列时,mySQL 无法在 JOIN 中使用连接表的主索引,甚至 FORCE INDEX (PRIMARY) 失败。

如果 WHERE 子句中没有连接表的列,则一切正常。 如果删除了 GROUP BY,也会使用索引。

但我两个都需要。

错误: (在我的特殊情况下最多 1000 秒的执行时间)

SELECT *
FROM tbl_contract co
LEFT JOIN tbl_customer cu ON cu.customer_id = co.customer_id
WHERE cu.marketing_allowed = 1 AND co.marketing_allowed = 1
GROUP BY cu.id
ORDER BY cu.name ASC

工作,但没有解决我的问题:

SELECT *
FROM tbl_contract co
LEFT JOIN tbl_customer cu ON cu.customer_id = co.customer_id
GROUP BY co.id

表结构(转录,因为真实的表更复杂)

tbl_contract: 
id: INT(11) PRIMARY
customer_id: INT(11)
marketing_allowed: TINYINT(1)

tbl_customer:
customer_id: INT(11) PRIMARY
marketing_allowed: TINYINT(1)

mySQL EXPLAIN 在加入时注意到 PRIMARY 作为可能的键,但不使用它。

有一个解决方案:

SELECT (...)
HAVING cu.marketing_allowed = 1

解决了问题但是我们在其他上下文中使用查询,我们只能在整个语句中选择一列,但 HAVING 需要在 SELECT-Statement 中选择marketing_allowed 列。

我还注意到,在所需的表上运行 ANALYZE TABLE 将使我的本地系统上的 mySQL 5.5.8 做正确的事情,但我不能总是保证 ANALYZE 已经在语句之前运行。无论如何,这个解决方案在我们的生产服务器上的 mySQL 5.0.51a 下不起作用。 :(

mySQL 中是否有我没有注意到的特殊规则?如果列出现在 WHERE 子句中,为什么不使用 LEFT JOIN 索引?为什么我不能强迫他们?

提前谢谢,

勒内

[编辑]

感谢一些回复,我可以使用 INNER JOIN 优化查询,但不幸的是,虽然看起来绝对没问题,但当我发现使用 ORDER BY 子句时,mySQL 仍然拒绝使用索引:

SELECT *
FROM tbl_contract co
INNER JOIN tbl_customer cu ON cu.customer_id = co.customer_id AND cu.marketing_allowed = 1
WHERE cu.marketing_allowed = 1
ORDER BY cu.name ASC

如果您不使用 ORDER BY,mySQL 将正确使用索引。 我已经删除了 GROUP BY,因为它在示例中没有相关性。

[编辑2]

强制索引也无济于事。所以,问题是:为什么 mySQL 不使用索引来加入,因为 ORDER BY 是在加入之后执行的,并通过 WHERE 子句减少结果集?这通常不会影响加入...

【问题讨论】:

怎么知道“mySQL使用主索引失败”?你检查过EXPLAIN 的输出吗?你能把它包括在你的问题中吗? 请分享表结构。 嗨,是的,我大量使用EXPLAIN,午休后我会修改我的请求;) 刚刚编辑它...显然,我使用的 GROUP BY 会影响行为,之前没有注意到...谢谢您的帮助。 我刚刚发现我最后的 ORDER BY cu.customer_name ASC 也是阻止它的一个因素。但是 ORDER BY 总是在 WHERE 之后执行,因此不应该影响在连接上使用索引,不是吗? 【参考方案1】:

我不确定我是否明白你在问什么,但是

SELECT *
FROM tbl_contract co
LEFT JOIN tbl_customer cu ON cu.customer_id = co.customer_id
WHERE cu.marketing_allowed = 1 AND co.marketing_allowed = 1

不会进行外连接(因为cu.marketing_allowed = 1)。

您可能打算使用:

SELECT *
FROM tbl_contract co
   LEFT JOIN tbl_customer cu 
        ON cu.customer_id = co.customer_id
       AND cu.marketing_allowed = 1 
WHERE co.marketing_allowed = 1

【讨论】:

如另一篇文章中所述,我在整理没有客户关联的合同时遇到了问题。 WHERE cu.customer_id IS NOT NULL 会导致和mySQL不使用索引一样的问题 @RenéHerzog:显然您不想要外连接,那么为什么要使用外连接? 好吧,我只知道这个,我已经在研究中,什么样的join最好? @RenéHerzog:显然你只想要JOIN(这是一个内部连接)。 LEFT JOIN 是外连接 嗨,是的,谢谢。我发现了这一点并现在使用 INNER JOIN,不能使用 WHERE 子句扩展。不幸的是,这使得 mySQL 不使用可能的索引(我为 cu.marketing_allowed 和 cu.customer_id 创建了一个组合索引 - 解释显示使用 where;使用临时;使用文件排序...【参考方案2】:

我也遇到了同样的问题。 MySQL 优化器在使用带条件的 JOIN 时不使用索引。我将我的 SQL 语句从 JOIN 更改为子查询:

SELECT
    t1.field1,
    t1.field2,
    ...
    (SELECT
        t2.field3
        FROM table2 t2
        WHERE t2.fieldX=t1.fieldX
    ) AS field3,
    (SELECT
        t2.field4
        FROM table2 t2
        WHERE t2.fieldX=t1.fieldX
    ) AS field4,
FROM table1 t1
WHERE t1.fieldZ='valueZ'
ORDER BY t1.sortedField

这个请求要复杂得多,但由于使用了索引,它也快得多。

您也可以使用STRAIGHT_JOIN,但上述查询的性能更好。这是 DB 与 table1 中的 100k 行和 table2 中的 20k 行的比较:

0.00s 使用上述查询 0.10s 使用STRAIGHT_JOIN 0.30 使用JOIN

【讨论】:

【参考方案3】:

您是否在 JOIN 子句上尝试过多个条件?

SELECT *
FROM tbl_contract co
LEFT JOIN tbl_customer cu ON cu.customer_id = co.customer_id AND cu.marketing_allowed = 1
WHERE co.marketing_allowed = 1

【讨论】:

嗨,是的。这是我的第一个想法。实际上,我必须先整理出没有加入客户的线路,因为他已禁止获取营销信息。 - 这会导致 WHERE ... AND cu.customer_id IS NOT NULL 这也会产生未使用 INDEX 的问题。 :( 恐怕我没有得到“整理没有加入客户的行”如果你的意思是消除,我相信你会使用 INNER JOIN 而不是 LEFT JOIN .. 请解释一下多一点 也许我走错了路。也许不同的 JOIN 类型可以解决我的问题。我会检查并报告,谢谢。 我现在使用 INNER JOIN 并且可以阻止 WHERE 子句部分,但不幸的是 mySQL 仍然拒绝使用索引。我只是不知道在这里做什么:( 我刚刚发现我最后的 ORDER BY cu.customer_name ASC 也是阻止它的一个因素。但是 ORDER BY 总是在 WHERE 之后执行,因此不应该影响在连接上使用索引,不是吗?

以上是关于在 WHERE 子句中使用连接列时,Mysql 未在 LEFT JOIN 中使用索引的主要内容,如果未能解决你的问题,请参考以下文章

在MySQL数据库中使用多个WHERE子句值更新多个列值时出现错误。

mysql 查询 - 使用左连接和 where 子句的多个计数

Order by

在连接中使用 Where 子句,以及 Group by 和 Order By

左连接的where子句中的Mysql查询错误

MYSQL CONCAT 停止使用 WHERE 子句处理连接表 [重复]