MySQL在子查询中没有使用INDEX

时间:2012-09-03 13:29:07

标签: mysql join indexing group-by subquery

我有sqlfiddle中定义的这些表和查询。

首先,我的问题是将人们显示为最近一年的LEFT JOINed访问行。我用子查询解决了。

现在我的问题是该子查询未使用visits表上定义的INDEX。这导致我的查询几乎无限期地运行在每个大约15000行的表上。

这是查询。目标是用访问表中最新(按年)的记录列出每个人一次。

不幸的是,在大型表格上它会变得真实,因为它在子查询中没有使用INDEX。

SELECT *
FROM people
LEFT JOIN (
  SELECT *
  FROM visits
  ORDER BY visits.year DESC
) AS visits
ON people.id = visits.id_people
GROUP BY people.id

有谁知道如何强制MySQL使用已在visits表上定义的INDEX?

2 个答案:

答案 0 :(得分:4)

您的查询:

SELECT *
FROM people
LEFT JOIN (
  SELECT *
  FROM visits
  ORDER BY visits.year DESC
) AS visits
ON people.id = visits.id_people
GROUP BY people.id;
  • 首先,使用非标准SQL语法(SELECT列表中不属于GROUP BY子句的项目,不是聚合函数,不依赖于分组项目)。这可以给出不确定(半随机)的结果。

  • 其次,(为了避免不确定的结果)你在子查询中添加了一个ORDER BY,在MySQL文档中的任何地方都没有记录(非标准或非标准)它应该按预期工作。所以,它现在可能正在工作,但是当你升级到MySQL版本X(优化器能够聪明地理解里面的ORDER BY时,它可能在不太遥远的未来工作派生表是多余的,可以删除。)

尝试使用此查询:

SELECT 
    p.*, v.*
FROM 
    people AS p
  LEFT JOIN 
        ( SELECT 
              id_people
            , MAX(year) AS year
          FROM
              visits
          GROUP BY
              id_people
         ) AS vm
      JOIN
          visits AS v
        ON  v.id_people = vm.id_people
        AND v.year = vm.year 
    ON  v.id_people = p.id;

SQL-fiddle

(id_people, year)上的复合索引有助于提高效率。


一种不同的方法。如果您首先将人员限制在合理的限制(例如30)然后加入visits表格,那么它可以正常工作:

SELECT 
    p.*, v.*
FROM 
    ( SELECT *
      FROM people
      ORDER BY name
        LIMIT 30
    ) AS p
  LEFT JOIN 
    visits AS v
      ON  v.id_people = p.id
      AND v.year =
    ( SELECT 
          year
      FROM
          visits
      WHERE
          id_people = p.id
      ORDER BY
          year DESC
        LIMIT 1
     )  
ORDER BY name ;

答案 1 :(得分:2)

为什么你需要一个子查询才能加入表名?

对我来说,为什么你的查询中有一个GROUP BY子句也不明显。 GROUP BY通常与MAXCOUNT等汇总函数一起使用,但您没有这些函数。

这个怎么样?它可以解决你的问题。

    SELECT people.id, people.name, MAX(visits.year) year
      FROM people
      JOIN visits ON people.id = visits.id_people
  GROUP BY people.id, people.name

如果您需要显示此人,最近一次访问以及最近一次访问中的注释,您将不得不再次将访问表明确地加入到摘要查询(虚拟表)中。 / p>

SELECT a.id, a.name, a.year, v.note
  FROM (
         SELECT people.id, people.name, MAX(visits.year) year
          FROM people
          JOIN visits ON people.id = visits.id_people
      GROUP BY people.id, people.name
  )a
  JOIN visits v ON (a.id = v.id_people and a.year = v.year)

小提琴:http://www.sqlfiddle.com/#!2/d67fc/20/0

如果您需要为从不访问过的人展示某些内容,您应该尝试使用JOIN切换我声明中的LEFT JOIN项。

正如其他人所写,子查询中的ORDER BY子句不是标准的,会产生不可预测的结果。在你的情况下,它使优化器感到困惑。

编辑GROUP BY是个大锤子。除非您需要,否则不要使用它。并且,除非在查询中使用聚合函数,否则不要使用它。

请注意,如果您在一个人和最近一年的访问次数中有多行,则此查询将为该人生成多行,每年一次访问一行。如果你只需要每人一行,并且你不需要访问的注释,那么第一个查询就可以了。如果一年中有一个人访问过多次,而您只需要最新的访问权限,则必须确定哪一行是最新的一行。通常它会是ID号最高的那个,但只有你肯定知道。我在这种情况下向你的小提琴添加了另一个人。 http://www.sqlfiddle.com/#!2/4f644/2/0

这很复杂。但是:如果您的visits.id号码是自动分配的,并且它们始终按时间顺序排列,您只需报告最高访问ID,并保证您将拥有最新的一年。这将是一个非常有效的查询。

SELECT p.id, p.name, v.year, v.note
  FROM (
         SELECT id_people, max(id) id
          FROM visits
      GROUP BY id_people
  )m
  JOIN people p ON (p.id = m.id_people)
  JOIN visits v ON (m.id = v.id)

http://www.sqlfiddle.com/#!2/4f644/1/0但这不是您的示例设置方式。所以你需要另一种方法来消除你最近一次访问的歧义,所以你每人只需要一行。我们可以使用的唯一技巧是使用最大的ID号。

因此,我们需要从您的表中获取这个定义中最新的visit.id数字列表。此查询执行此操作,MAX(年)... GROUP BY(id_people)嵌套在MAX(id)... GROUP BY(id_people)查询中。

  SELECT v.id_people,
         MAX(v.id) id
    FROM (
         SELECT id_people, 
                MAX(year) year
           FROM visits
          GROUP BY id_people
         )p
    JOIN visits v ON (p.id_people = v.id_people AND p.year = v.year)
   GROUP BY v.id_people

整体查询(http://www.sqlfiddle.com/#!2/c2da2/1/0)就是这样。

SELECT p.id, p.name, v.year, v.note
  FROM (
      SELECT v.id_people,
             MAX(v.id) id
        FROM (
             SELECT id_people, 
                    MAX(year) year
               FROM visits
              GROUP BY id_people
             )p
        JOIN visits v ON (     p.id_people = v.id_people 
                           AND p.year = v.year)
       GROUP BY v.id_people
      )m
   JOIN people p ON (m.id_people = p.id)
   JOIN visits v ON (m.id = v.id)

SQL中的消歧是一项棘手的业务,因为需要一些时间来解决DBMS中没有行固有顺序的想法。