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

如何在t-sql的union子句上获得更好的性能,sql,sql-server,tsql,query-performance,Sql,Sql Server,Tsql,Query Performance,我有三张桌子。每个表包含超过300万行。我运行以下代码: 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 ta

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

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秒内得到结果。我能做什么?

首先使用UNION ALL而不是UNION:

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


此外,每个表中的日期索引也会有所帮助。SQL Server有一个很好的优化器,它通常将这些条件下推到UNION/UNION ALL子查询中的各个查询。

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

CREATE INDEX ix1 ON table_1 (date) INCLUDE (column1, column2, column3)
这应该有助于WHERE子句。另外,SQL Server不必接触这些表,因为所有必需的信息都存在于索引中

这是另一种尝试。假设偏移量n行FETCH NEXT m行只匹配开始日期和结束日期之间非常小百分比的行,请编写如下查询:

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

我不确定查询计划器是否足够聪明,是否能够通过联合外部的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

您可以尝试在单个查询中添加where条件。对于初学者,由于查询的结果从不重叠,但SQL Server不知道这一点,您可以使用UNION ALL而不是UNION来保存排序步骤。此外,优化器无法始终有效地将外部条件折叠到内部查询中,因此您可能希望在每个查询中重复WHERE date BEVERY子句。此查询还非常需要在date列的所有表中建立索引,否则不可避免地会进行大量低效的表扫描。最后但并非最不重要的一点是,抵消。。FETCH NEXT是一种固有的低效构造,偏移量越大,它的速度越慢,因此请确保您没有试图让数据库做它无法快速完成的事情。我在其中编写了单独的代码,但我只比它快1-2秒。我还使用了union all子句。但这并没有帮助我是的,我在每个表的日期列上创建了索引。完成后,执行时间下降到13-14秒。这个答案假设显示副本是可以接受的或可取的。这可能是也可能不是,取决于项目的要求。当然,还有更多返回行的传输开销。@JosephDoggie:由于子查询中没有由于不同类型而产生的重叠,重复项只有在基表中已经存在时才会出现,然后,子查询上的一个DISTINCT将仍然比UNION更有效地对它们进行排序。在这种情况下,它仍然必须将三个流合并为排序的日期顺序,很可能合并连接假定索引在日期上,因此重复删除不应是一个昂贵的额外步骤,@JosephDoggie。由于类型列,没有重复(至少在表之间)。或者他们可以考虑在所有这些表中聚集索引中的最前面列,因为它们现在都是堆。对于当前的信息,不可能知道这是否是一个好主意。SQL 2012似乎自动移动了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