MySQL 未在子查询中使用 INDEX
Posted
技术标签:
【中文标题】MySQL 未在子查询中使用 INDEX【英文标题】:MySQL is not using INDEX in subquery 【发布时间】:2012-08-28 05:59:18 【问题描述】:我有sqlfiddle 中定义的这些表和查询。
首先我的问题是将显示 LEFT JOINed 访问行的人与最新年份分组。我使用子查询解决了。
现在我的问题是该子查询没有使用在visits
表上定义的索引。这导致我的查询几乎无限期地在每个大约有 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?
【问题讨论】:
【参考方案1】:当您只需要一个用于连接的表名时,为什么还要有一个子查询?
我也不清楚为什么您的查询中包含GROUP BY
子句。 GROUP BY
通常与 MAX
或 COUNT
等聚合函数一起使用,但您没有这些。
这个怎么样?它可能会解决您的问题。
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
如果您需要显示此人、最近一次访问以及最近一次访问的注释,您将不得不再次将访问表显式连接到摘要查询(虚拟表),如下所示。
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(year)...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 中的行没有固有顺序这一想法。
【讨论】:
那是因为我需要加入最近一年的访问。 MySQL GROUP BY 在找到的第一个行下进行分组。 您的查询工作正常。我什至不必创建另一个索引。困扰我的一件事是,有些人的行是两次结果。我最终通过在查询末尾添加一个额外的 GROUP BY people_id 来消除它,但这似乎会增加 CPU 负载。 检查我的编辑。对于某些人来说,您显然在同一年多次访问。【参考方案2】:您的查询:
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 ;
【讨论】:
这似乎适用于 JOIN,但我需要使用 LEFT JOIN 来列出没有访问的人。如果我这样做,p
表将获得 NULL 键,我将永远等待。
仍然很慢,抱歉。不过,@Ollie Jones 的解决方案似乎运行良好。
是的。我已经在visits.id_people 甚至(id_people,year)上尝试过INDEX。没有任何帮助。
表是 MyISAM 还是 InnoDB?
让我们continue this discussion in chat以上是关于MySQL 未在子查询中使用 INDEX的主要内容,如果未能解决你的问题,请参考以下文章