在WHERE中使用来自其他表的列时优化JOIN

时间:2015-01-29 03:25:28

标签: mysql sql

虽然有类似名称的问题,但我没有发现任何相同的情况。

当我添加一个使用邻接表中的列的WHERE子句时,我无法使JOIN正常工作。例如:

SELECT a.*, b.unixTimestamp
FROM alpha a
LEFT JOIN beta b ON a.id = b.id
WHERE a.categoryId IN (1, 2, 3) AND b.unixTimestamp >= ?

它比实践中的要复杂一些,但一般形式是相同的。此查询大约需要1.5秒。但是,如果我从查询中删除AND b.unixTimestamp >= ?,它将在大约1毫秒内运行。

为了科学,我在beta添加了三个唯一键:

  • PRIMARY (id)
  • one (id, unixTimestamp)
  • two (unixTimestamp, id)

EXPLAIN显示MySQL选择主键,我希望它使用one。提供关键提示似乎不会对性能产生影响。

如何在WHERE子句中使用两个连接表中的字段?

注意:由于构建我正在使用的框架的方式,我无法轻松地将unixTimestamp上的测试移动到ON子句。如果我被迫这样做,它有时会很复杂,因为unixTimestamp测试对于每个categoryId来说不一定相同;例如:

ON a.id = b.id AND (
    (a.categoryId IN (1, 2) AND b.unixTimestamp >= ?)
    OR (a.categoryId IN (3, 4) AND b.unixTimestamp >= ?)
    OR (a.categoryId IN (5, 6) AND b.unixTimestamp >= ?)
)

更新

看起来ON子句无效。起初我以为它确实如此,但我忘了将其改为内部联接。不幸的是,实际查询中还有其他复杂性需要我从alpha添加多个列到ON子句。我仍然无法让指数排成一行。例如,这需要大约1.5秒:

SELECT alpha.*, b.unixTimestamp
FROM alpha a
INNER JOIN beta b ON (a.id = b.id AND (b.unixTimestamp >= ? or b.userId = ?))
WHERE a.categoryId IN (1, 2, 3)

我真的无法避免WHERE子句中的beta列和ON子句中alpha的多列;它是一个或另一个。

1 个答案:

答案 0 :(得分:1)

我不确定你在查询中有多大的灵活性(不熟悉XenForo),但是你可以尝试使用带有索引提示的派生表(尽管人们希望提示没有必要)在加入之前强制过滤您的Beta表结果:

SELECT a.*, b.unixTimestamp
FROM alpha a
LEFT JOIN (
  SELECT id, unixTimestamp
  FROM beta 
  USE INDEX (two)
  WHERE unixTimestamp >= ?
) b ON a.id = b.id
WHERE a.categoryId IN (1, 2, 3) 

在实现派生表时,子查询中没有连接,因此在timestamp列上使用索引不应该有任何问题。仅在unixTimestamp上添加索引(如果它们不必要,则删除索引1和2)可以改进此查询,但当然这可能不是您实际数据库中的选项。但是,如果从beta获取的数据量太大而无法保留在内存中,则此查询将无法执行,因为派生表将被推送到磁盘。

优化器在id上为派生表添加索引,因为它将首先分析alpha表(以确定是否需要实现派生表),等等应该能够告诉派生表的连接是否符合ref(因为它是两个索引列的相等比较)。因此,不应在此处使用派生表来销毁您的连接性能。请参阅8.2.1.18.3

另一方面,不使用时间戳索引可能是因为它们根本没用,所以优化器正在进行正确的调用。也许您为where子句选择的时间戳值 - 或时间戳+ ID数据本身 - 不具有选择性。在做出这些决定时,优化器通常比人类好得多,因此如果没有更多非常详细的信息,很难说出来。

顺便说一下,如果可能的话,您可以考虑使用EXISTS重写它来优化该IN子句。看看8.2.1.18.4的想法。