如何告诉 MySQL 优化器在派生表上使用索引?

Posted

技术标签:

【中文标题】如何告诉 MySQL 优化器在派生表上使用索引?【英文标题】:How do I tell the MySQL Optimizer to use the index on a derived table? 【发布时间】:2012-02-13 12:37:36 【问题描述】:

假设您有这样的查询...

SELECT T.TaskID, T.TaskName, TAU.AssignedUsers
FROM `tasks` T
    LEFT OUTER JOIN (
        SELECT TaskID, GROUP_CONCAT(U.FirstName, ' ',
            U.LastName SEPARATOR ', ') AS AssignedUsers
        FROM `tasks_assigned_users` TAU
            INNER JOIN `users` U ON (TAU.UserID=U.UserID)
        GROUP BY TaskID
    ) TAU ON (T.TaskID=TAU.TaskID)

可以将多个人分配给给定任务。此查询的目的是为每个任务显示一行,但分配给该任务的人员在一列中

现在...假设您在tasksuserstasks_assigned_users 上设置了正确的索引。将tasks 加入派生表时,mysql 优化器仍不会使用 TaskID 索引。 WTF?!?!?

所以,我的问题是……如何让这个查询使用 tasks_assigned_users.TaskID 上的索引?临时表很蹩脚,所以如果这是唯一的解决方案...... MySQL 优化器很愚蠢。

使用的索引:

任务 主要 - 任务 ID 用户 主要 - 用户 ID tasks_assigned_users PRIMARY - (TaskID,UserID) 附加索引 UNIQUE - (UserID,TaskID)

编辑:另外,this page 表示派生表在连接发生之前执行/实现。为什么不重复使用密钥来执行连接?

编辑 2: MySQL 优化器不允许您将 index hints 放在派生表上(可能是因为派生表上没有索引)

编辑 3: 这是一篇非常好的博文:http://venublog.com/2010/03/06/how-to-improve-subqueries-derived-tables-performance/ 请注意,案例 #2 是我正在寻找的解决方案,但 MySQL 似乎不支持这次。 :(

编辑 4: 刚刚找到this:“从 MySQL 5.6.3 开始,优化器更有效地处理 FROM 子句中的子查询(即派生表):...查询期间执行时,优化器可能会向派生表添加索引以加快从中检索行的速度。”看起来很有希望...

【问题讨论】:

你也可以添加你正在使用的索引吗?我假设你有一个关于任务的 PK 和一个关于 tasks_assigned_users 的非唯一索引。 @Luis - 为你编辑了问题:) 您有 GROUP BY 任务 ID,这意味着多个人可能正在处理给定的任务,这也意味着一些聚合。您是否希望分配给给定任务的所有人都列在与该任务关联的单个返回列中?或者,您是否真的希望看到每个人都分配了一项任务,而那些未分配的任务则将其留空。甚至可能将任何未分​​配的任务推到列表的顶部(或底部)...... 可以将多个人分配给给定任务。此查询的目的是为每个任务显示一行,但分配给该任务的人员在一列中 类似问题:***.com/questions/1180714/… 此人建议使用临时表,对其进行索引,然后运行查询。这是蹩脚的。 【参考方案1】:

在 MySQL Server 5.6 中有一个解决方案 - 预览版(在撰写本文时)。

http://dev.mysql.com/doc/refman/5.6/en/from-clause-subquery-optimization.html

虽然,我不确定 MySQL 优化器在“向派生表添加索引”时是否会重用已经存在的索引

考虑以下查询:

从 t1 中选择 * JOIN (SELECT * FROM t2) AS derived_t2 ON t1.f1=derived_t2.f1;

文档说:“如果这样做允许使用 ref 访问以实现最低成本的执行计划,那么优化器会在 f1 列上从 derived_t2 构造一个索引。”

好的,这很好,但是优化器会重复使用 t2 中的索引吗?换句话说,如果 t2.f1 存在索引怎么办?该索引是否被重用,或者优化器是否为派生表重新创建该索引?谁知道?

编辑:在 MySQL 5.6 之前的最佳解决方案是创建一个临时表,在该表上创建一个索引,然后在该临时表上运行 SELECT 查询。

【讨论】:

MariaDB 10 上的同样愚蠢的情况(多年后):即使由于显式排序,派生中的 group by 确实加快了查询速度(可笑的 select v1 from t group by v1),但最好的结果是如果您在主查询之前创建所有派生表并明确添加所需的索引。优化器 sux【参考方案2】:

我看到的问题是通过执行子查询没有基础索引表。 如果你有表演,我会在最后进行分组,如下所示:

SELECT T.TaskID, T.TaskName, GROUP_CONCAT(U.FirstName, ' ', U.LastName SEPARATOR ', ') AS AssignedUsers
FROM `tasks` T
    LEFT OUTER JOIN  `tasks_assigned_users` TAU ON (T.TaskID=TAU.TaskID)
    INNER JOIN `users` U ON (TAU.UserID=U.UserID)
GROUP BY T.TaskID, T.TaskName

【讨论】:

这行得通...但由于它是完全相同的结果集,我不知道为什么 MySQL 不能为我做这个优化。另外,我的 actual 查询有大约 20 列;我必须告诉 MySQL 将它们全部分组?我真的只想对 TaskID 进行分组,而不是 TaskID 和 TaskName...当您按两列分组时,MySQL 会做额外的工作。你知道我的意思吗? 试讲不讲;如果需要它们,则不声明它们是错误的,因此您会很容易意识到(我不知道是否需要它们)。我不知道为什么它不能做优化;我的猜测是子查询是外部查询的黑匣子,但我不知道。通常我会因为这样的性能问题而避免子查询。 MySQL 是否足够聪明,可以知道 GROUP BY T.TaskID, T.TaskName 与 GROUP BY T.TaskID 相同,因为 T.TaskID 是 PRIMARY 键? 不,不是。如果您SET SESSION sql_mode = CONCAT("ONLY_FULL_GROUP_BY,",@@sql_mode),您可以自己尝试。 MySQL 会抱怨列不在 GROUP BY 子句中,即使它们是由 SQL 定义的功能依赖。当您使用 AS 为列命名时,它甚至会抱怨。 我真的不想重新编写大量查询......而且我不能 100% 确定在几乎每一列上使用 GROUP BY 的性能,所以我只是使用了 CREATE TEMPORARY TABLE foo 派生查询在这里后跟一个 ALTER TABLE ADD PRIMARY KEY ... 等等。感谢您的回答,@Luis!【参考方案3】:

恐怕是not possible。您必须创建一个临时表或视图才能使用索引。

【讨论】:

那篇文章是 2006 年的。从那以后没有任何变化? 我不这么认为,有两个原因。 1)它是MySQL。 2) 例如,2010 年还有其他关于此问题的帖子 (planet.mysql.com/entry/?id=23769)。顺便说一句,你能用视图吗? 我不知道...我读过视图有类似的问题,但我现在就试一试... 具有讽刺意味的是,出于性能原因,这里有一篇 MSSQL(!) 建议完全相反(用派生表替换临时表):sql-server-performance.com/2002/derived-temp-tables ;) 除非您可以重用另一个表的索引,否则创建索引可能效率不高。创建索引是 O(n log n)。如果您反转搜索并且可以使用 Tasks PK,那么您只需进行 O(n) 迭代,每个迭代都有 O(1) 搜索。

以上是关于如何告诉 MySQL 优化器在派生表上使用索引?的主要内容,如果未能解决你的问题,请参考以下文章

带有子查询的 CTE 查询在小型索引表上很慢;如何在 MySQL 上进行优化?

表上999个非聚集索引——你怎么看?

mysql索引的类型和优缺点

SQL SERVER全面优化-------索引有多重要?

MySQL调优索引优化

MySQL调优执行计划