在 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 行的比较:
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 子句的多个计数