优化长时间运行的 SQL Server 查询

Posted

技术标签:

【中文标题】优化长时间运行的 SQL Server 查询【英文标题】:Optimise long running SQL Server query 【发布时间】:2012-12-05 11:29:11 【问题描述】:

我有以下疑问:

SELECT fpa.scenario_id,
   fpa.facility_id,
   cge.CostGroupId result_total_id,
   mp_surrogate_id,
   CAST(SUM(fpa.raw_amount * cge.CostSign) AS DECIMAL(25, 13)) 
   result_total_amount         
INTO ADM_FactProfitTotalAmount_1
FROM #tempAmount fpa
JOIN ResultTest cge ON cge.CostId = fpa.process_id 
WHERE fpa.scenario_id = 1
GROUP BY fpa.scenario_id, fpa.facility_id, cge.CostGroupId, fpa.mp_surrogate_id
#tempAmount 我有 2.2 亿行。 在 ResultTest 我有 150 行。

我在#tempAmount上有一个索引:

CREATE NONCLUSTERED INDEX #tempAmount_process_id
ON  #tempAmount(scenario_id, facility_id, mp_surrogate_id, process_id )

执行大约需要 1 小时。可以优化吗?

编辑:

我在 ResultTest 列 CostId 上创建了索引,更改了一些其他索引和查询

    CREATE CLUSTERED INDEX #tempFactAmount_index 
    ON  #tempAmount (process_id ,facility_id, mp_surrogate_id )

    SELECT  ISNULL(CAST(1 as BIGINT), 0) scenario_id,
            fpa.facility_id,
            cge.CostGroupId result_total_id,
            fpa.mp_surrogate_id,
            CAST(SUM(fpa.raw_amount * cge.CostSign) AS DECIMAL(25, 13)) result_total_amount         
    INTO ADM_FactProfitTotalAmount_1
    FROM ResultTest cge
    JOIN #tempAmount fpa ON cge.CostId = fpa.process_id 
    GROUP BY fpa.facility_id, fpa.mp_surrogate_id, cge.CostGroupId

执行计划:

41% 插入 ADM_FactProfitTotalAmount_1

51% 哈希匹配聚合

2% 哈希匹配内连接

【问题讨论】:

你在process_id 上也有索引吗?从 JOIN 条件来看,定义一个似乎是有益的。将process_id 移动到第二个索引列也可能会有所帮助。但是,如果没有查询计划,就很难判断瓶颈在哪里。 关于优化选择的良好网络广播:brentozar.com/archive/2012/10/… 尝试在 #tempAmount 上创建一个 CLUSTERED INDEX 仅在列 scenario_id 上。 fpa.schenario_id = 1 的选择性如何? 我从查询中删除了 fpa.schenario_id = 1,抱歉误导,请参阅下面的查询 【参考方案1】:

在这种情况下,我发现在加入较小的表之前对较大表中的金额求和通常会有所帮助。所以在这种情况下,我会使用以下内容:

;WITH SUMCTE
AS
(
SELECT      fpa.facility_id,
            fpa.mp_surrogate_id,
            fpa.process_id,
            SUM(fpa.raw_amount) AS total_amount         
    FROM #tempAmount fpa 
    GROUP BY fpa.facility_id, fpa.mp_surrogate_id, fpa.process_id
)
SELECT  CAST(1 as BIGINT) AS Scenario_id,
        facility_id,
        cge.CostGroupId result_total_id,
        mp_surrogate_id,
        CAST(SUM(SCT.total_amount * cge.CostSign) AS DECIMAL(25, 13)) result_total_amount         
    INTO ADM_FactProfitTotalAmount_1
    FROM ResultTest cge
    JOIN SUMCTE SCT ON cge.CostId = SCT.process_id 
    GROUP BY fpa.facility_id, fpa.mp_surrogate_id, cge.CostGroupId

如果每个 process_id 在 ResulTest 中只有一行,我将通过以下方式删除外部组来进一步简化:

;WITH SUMCTE
AS
(
SELECT      fpa.facility_id,
            fpa.mp_surrogate_id,
            fpa.process_id,
            SUM(fpa.raw_amount) AS total_amount         
    FROM #tempAmount fpa 
    GROUP BY fpa.facility_id, fpa.mp_surrogate_id, fpa.process_id
)
SELECT  CAST(1 as BIGINT) AS Scenario_id,
        facility_id,
        cge.CostGroupId result_total_id,
        mp_surrogate_id,
        CAST((SCT.total_amount * cge.CostSign) AS DECIMAL(25, 13)) result_total_amount         
    INTO ADM_FactProfitTotalAmount_1
    FROM ResultTest cge
    JOIN SUMCTE SCT ON cge.CostId = SCT.process_id 

【讨论】:

【参考方案2】:

首先,我建议捕获实际的执行计划。如果您从 SQL Server Management Studio (SSMS) 运行查询,请打开“包括实际执行计划”选项。如果此查询是从另一个程序运行的,请运行 SQL Server Profiler 并打开 Showplan Statistics Profile 和/或 Showplan XML Statistics Profile。查看此配置文件并查看查询的行为是否符合您的预期。

您有关于 ResultTest 列 CostId 的索引吗?只有 150 行,对这个表进行索引扫描没什么大不了的。如果你在这个表上没有索引,你可以试试。

我想知道执行计划是否正在执行嵌套循环以加入 ResultTest。如果是这样,那将是 150 X 220,000,000 = 330 亿次操作。如果是这种情况,散列连接或合并连接会执行得更好。您可以使用连接提示 OPTION (HASH JOIN)OPTION (MERGE JOIN) 强制执行特定连接。仅此一项就可以产生巨大的影响。

#tempAmount 上的索引有很多列对于SELECT 查询来说是不必要的。此外,它是一个NONCLUSTERED 索引。是否还有 CLUSTERED 索引?如果没有,您可以尝试将其转换为 CLUSTERED 并删除其他列。这将减小索引的大小并且性能应该更好,因为scenario_id 的所有行都是连续的。

【讨论】:

【参考方案3】:

我建议从检查预计执行计划开始。http://msdn.microsoft.com/en-us/library/ms191194.aspx

多列索引只有在保留前缀时才能使用。 http://dev.mysql.com/doc/refman/5.0/en/multiple-column-indexes.html

所以我建议将process_id移到scenario_id旁边,因为它们用于where和join。

创建非集群索引 #tempAmount_process_id ON #tempAmount(scenario_id, process_id, facility_id, mp_surrogate_id)

最后一个:让操作系统尽可能多地将磁盘块缓存到内存中。 在 linux 中,在一些性能关键的数据库投入生产之前, 执行“cat your_database.store.file > /dev/null”。 大量磁盘读取将从内存缓存中命中。

【讨论】:

以上是关于优化长时间运行的 SQL Server 查询的主要内容,如果未能解决你的问题,请参考以下文章

sql server 2000/2008 查询优化

Sql Server 查询优化技巧

SQL 查询运行了很长时间

请帮助优化查询

使用函数优化 SQL 查询

需要优化SQL查询的帮助