优化长时间运行的 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 查询的主要内容,如果未能解决你的问题,请参考以下文章