mysql 从UNION构建的CTE运行非常慢

cngwdvgl  于 2023-08-02  发布在  Mysql
关注(0)|答案(1)|浏览(96)

我整理了一个我正在纠结的查询的简化版本:

WITH cte AS (
    SELECT 
        num_serves_p1 AS num_serves, player_id_p1 AS player_id, location, date_
    FROM
        table1 
    UNION ALL SELECT 
        num_serves_p2 AS num_serves, player_id_p2 AS player_id, location, date_
    FROM
        table1
)
SELECT 
    id_, SUM(cte.num_serves)
FROM
    table1 AS t
        JOIN
    cte ON cte.player_id = t.player_id_p1
        AND cte.location = t.location
        AND cte.date_ < t.date_
WHERE 
    t.id_ = 12345

字符串
目前,这需要一分钟以上的时间来运行大约一百万行。
如果我去掉UNION_ALL的第二部分,给予:

WITH cte AS (
    SELECT 
        num_serves_p1 AS num_serves, player_id_p1 AS player_id, location, date_
    FROM
        table1 
)
SELECT 
    id_, SUM(cte.num_serves)
FROM
    table1 AS t
        JOIN
    cte ON cte.player_id = t.player_id_p1
        AND cte.location = t.location
        AND cte.date_ < t.date_
WHERE 
    t.id_ = 12345


或者UNION_ALL的第一部分给予我:

WITH cte AS (
    SELECT 
        num_serves_p2 AS num_serves, player_id_p2 AS player_id, location, date_
    FROM
        table1 
)
SELECT 
    id_, SUM(cte.num_serves)
FROM
    table1 AS t
        JOIN
    cte ON cte.player_id = t.player_id_p1
        AND cte.location = t.location
        AND cte.date_ < t.date_
WHERE 
    t.id_ = 12345


然后这些查询中的任何一个都在0.1秒内运行。
所以我认为我所有的索引都很好,但问题在于使用UNION_ALL。考虑到性能上的巨大差异,我的直觉是,在第一个查询中,CTE加载了table1(x2)中的所有行,而在第二个和第三个查询中,CTE只加载了连接所需的行。
我意识到我可以创建两个CTE并简单地将结果相加,但是在某些时候,我将计算依赖于日期排序UNION_ALL CTE的字段。
为什么我看到第一个查询的性能很差,我能做些什么呢?
最后一点:我没有包括所有常见的表描述等,因为这实际上是一个伪查询。我认为有足够的东西可以继续下去,因为我已经将UNION_ALL的使用隔离为可能的问题。我也不知道如何生成足够的数据来说明这个问题。手指交叉在上面就足够了。

ryevplcw

ryevplcw1#

优化器对于联合、联接、CTE不够智能,无法将“id=”一路传播。相反,它正在评估UNION的每个部分,创建一个包含所有这些行的临时表,并可能构建一个索引以帮助下一步。
如果对每个表副本执行SUM操作,然后将它们加在一起,则运行速度可能会更快。(我甚至不能判断这是否是一个有效的转换,优化器甚至不会尝试它。

更多

AND cte.date_ < t.date_
WHERE 
    t.id_ = 12345

字符串
-->

AND cte.date < ( SELECT date_ FROM t WHERE id_ = 12345 )


或者,这样可能更好:

SELECT ...
    FROM ( SELECT date_ FROM t WHERE id_ = 12345 ) AS d
    JOIN ...  (( the other stuff ))
    WHERE cte.date_ < d.date_

**更多 *

SELECT  
    (
        SELECT  SUM(num_serves)
            FROM  table1
            WHERE  player_id = this.p1_id 
    ) AS p1_prev_serves, 
    (
        SELECT  SUM(num_serves)
            FROM  table1
            WHERE  player_id = this.p2_id 
    ) AS p2_prev_serves
    FROM  table1 AS this
    WHERE  player_id IN (this.p1_id, this.p2_id)
      AND  this.game_id = 12345;
SELECT  SUM(num_serves) AS prev_serves
    FROM  table1 AS all

相关问题