如何在 t-sql 上的联合子句上获得更好的性能

Posted

技术标签:

【中文标题】如何在 t-sql 上的联合子句上获得更好的性能【英文标题】:How can I get better performance on union clause on t-sql 【发布时间】:2019-04-24 12:05:53 【问题描述】:

我有三张桌子。每个表包含超过 3M 行。我运行以下代码:

SELECT * FROM 
(
    SELECT col_1, col_2, col_3, [date], 1 as type FROM table_1
    UNION
    SELECT col_1, col_2, col_3, [date], 2 as type FROM table_2 
    UNION
    SELECT col_1, col_2, col_3, [date], 3 as type FROM table_3
) AS tb 
tb.[date] BETWEEN (start_date) AND (end_date)  
ORDER BY [date] DESC OFFSET n ROWS FETCH NEXT m ROWS ONLY

但是当我得到较大的日期间隔时,查询运行速度较慢。例如:当我得到 2019-01-01 和 2019-04-01 间隔时,查询运行大约 13-14 秒:

这个结果很糟糕。我想在 1 秒内得到结果。我能做什么?

【问题讨论】:

您可以尝试在单个查询中添加 where 条件。 对于初学者,由于您的查询结果永远不会重叠(但 SQL Server 不知道这一点),您可以使用UNION ALL 而不是UNION 来保存排序步骤。此外,优化器不能总是有效地将外部条件折叠到内部查询中,因此您可能需要在每个查询中重复 WHERE date BETWEEN 子句。 这个查询还非常需要在date 列的所有表中建立索引,否则不可避免地会出现巨大而低效的表扫描。最后但同样重要的是,OFFSET .. FETCH NEXT 本质上是一种效率低下的结构,OFFSET 越大越慢,因此请确保您没有试图让数据库完成它无法快速完成的事情。跨度> 我写个人在哪里,但我只给我 1 -2 秒的速度。我也使用联合所有条款。但这对我也没有帮助 是的,我在每个表的日期列上创建索引。完成后,执行时间缩短到 13-14 秒。 【参考方案1】:

首先使用UNION ALL 而不是UNION

SELECT *
FROM (SELECT col_1, col_2, col_3, [date], 1 as type FROM table_1
      UNION ALL
      SELECT col_1, col_2, col_3, [date], 2 as type FROM table_2 
      UNION ALL
      SELECT col_1, col_2, col_3, [date], 3 as type FROM table_3
     ) AS tb 
WHERE tb.[date] BETWEEN (start_date) AND (end_date)  
ORDER BY [date] DESC
OFFSET n ROWS FETCH NEXT m ROWS ONLY;

SQL 会产生使用UNION 删除重复项的开销。 UNION ALL 不会产生这种开销。

此外,每个表中date 的索引应该会有所帮助。 SQL Server 有一个很好的优化器,通常将此类条件下推到 @​​987654327@/UNION ALL 子查询中的单个查询。

【讨论】:

此答案假定显示重复项是可以接受或可取的。这可能是也可能不是,这取决于项目的要求。当然,返回更多行也会产生传输开销。 @JosephDoggie:由于子查询中没有重叠(由于types 不同),只有当它们已经存在于基表中时才会显示重复项,然后是 @子查询上的 987654330@ 仍然会比 UNION 更有效地对它们进行排序。 在这种情况下,它仍然必须将三个流组合成排序的日期顺序(可能使用合并连接假设索引在date),因此重复删除不应该是一个非常昂贵的附加无论如何都要走, @JosephDoggie 。 . .由于type 列,没有重复项(至少在表之间)。【参考方案2】:

我建议在每个表上创建一个覆盖索引,类似于:

CREATE INDEX ix1 ON table_1 (date) INCLUDE (column1, column2, column3)

这应该有助于 WHERE 子句。此外,由于所有必需的信息都存在于索引中,因此 SQL Server 不必接触表。


这是另一个尝试。假设OFFSET n ROWS FETCH NEXT m ROWS ONLY 匹配开始日期和结束日期之间的一小部分行,编写如下查询:

WITH cte1 AS (
    -- find the first date after n + m window
    SELECT date
    FROM (
        SELECT date FROM table_1 UNION ALL
        SELECT date FROM table_2 UNION ALL
        SELECT date FROM table_3
    ) AS x
    WHERE date BETWEEN '2019-01-01' AND '2019-04-01'
    ORDER BY date DESC OFFSET (n + m) ROWS FETCH NEXT 1 ROW ONLY
), cte2 AS (
    SELECT date, column_1, column_2, column_3, 1 AS type FROM table_1 UNION ALL
    SELECT date, column_1, column_2, column_3, 1 AS type FROM table_2 UNION ALL
    SELECT date, column_1, column_2, column_3, 1 AS type FROM table_3
)
SELECT *
FROM cte2
WHERE date <= '2019-04-01' AND date > (SELECT date FROM cte1)
ORDER BY date DESC OFFSET n ROWS FETCH NEXT m ROWS ONLY

【讨论】:

或者他们可以考虑将日期作为所有这些表的聚集索引中的前导列,因为它们目前都是堆。根据当前信息无法知道这是否是一个好主意【参考方案3】:

我不确定查询计划器是否足够聪明,可以通过并集之外的 where 子句限制并集的结果,因此请尝试将日期条件移动到并集中的每个查询中,这样您就可以'在条件操作之前不要将三个表合并在一起:

SELECT * FROM 
(
    SELECT col_1, col_2, col_3, [date], 1 as type FROM table_1 where table_1.[date] between (start_date) and (end_date)
    UNION
    SELECT col_1, col_2, col_3, [date], 2 as type FROM table_2 where table_2.[date] between (start_date) and (end_date) 
    UNION
    SELECT col_1, col_2, col_3, [date], 3 as type FROM table_3 where table_3.[date] between (start_date) and (end_date)
) AS tb 
ORDER BY [date] DESC OFFSET n ROWS FETCH NEXT m ROWS ONLY

【讨论】:

SQL 2012 似乎会自动移动 where 子句。

以上是关于如何在 t-sql 上的联合子句上获得更好的性能的主要内容,如果未能解决你的问题,请参考以下文章

在 T-SQL 查询的 SELECT 子句中使用表值函数的“语法不正确”

T-SQL:在 OUTPUT 子句中插入原始值

测试 T-SQL 中的不等式

如何缩短 T-SQL 中的 WHERE 子句

T-SQL - 如何在LIKE子句中转义斜杠/方括号

在t-sql中,子查询只能放在where子句中吗