需要减少查询时间
Posted
技术标签:
【中文标题】需要减少查询时间【英文标题】:Need to reduce Query Time 【发布时间】:2012-04-25 15:12:03 【问题描述】:我的问题是下面的查询需要 38 秒才能完成, 我需要尽可能地减少这个时间。 当我查看执行计划时:Dim_Customers 索引扫描上的 %54 成本支出。 任何建议将不胜感激。谢谢
DECLARE @SalesPersonCode NVARCHAR(4)
DECLARE @StartDate DATETIME
DECLARE @EndDate DATETIME
SET @SalesPersonCode = 'AC';
SET @StartDate = '03/01/2012';
SET @endDate = '03/31/2012';
SELECT AA_FactSalesOrderDetails.Salesperson
, Dim_SalesOrganisation.[Salesperson name]
, AA_FactSalesOrderDetails.[Order Date]
, Dim_Customers.[Customer number]
, Dim_Customers.[Customer name]
, Dim_Customers.[Area/state]
, Dim_Customers.country
, Dim_Customers.[Customer stop] AS [Customer Block]
, AA_FactSalesOrderDetails.[Customer order stop] AS [Co Stop]
, AA_FactSalesOrderDetails.[First delivery date Header]
, AA_FactSalesOrderDetails.[Last delivery date Header]
, Dim_Customers.[User-defined field 6 - customer]
, Dim_Customers.[Customer group name]
, AA_FactSalesOrderDetails.[Contact Method]
, AA_FactSalesOrderDetails.[Customer order number]
, AA_FactSalesOrderDetails.[Price Level]
, AA_FactSalesOrderDetails.[Item number]
, Dim_Items.[Product group description] AS [Item name]
, AA_FactSalesOrderDetails.[Ordered quantity - basic U/M] AS [Quantity Ordered]
, AA_FactSalesOrderDetails.[Ordered quantity - basic U/M] * AA_FactSalesOrderDetails.[Net price] AS [Order Line Total ]
FROM AA_FactSalesOrderDetails
LEFT JOIN
Dim_SalesOrganisation
ON
AA_FactSalesOrderDetails.Salesperson = Dim_SalesOrganisation.Salesperson
LEFT JOIN
Dim_Customers
ON
AA_FactSalesOrderDetails.Dim_Customers_dKey = Dim_Customers.Dim_Customers_dKey
LEFT JOIN
Dim_Items
ON
AA_FactSalesOrderDetails.[Item number] = Dim_Items.[Item number]
LEFT JOIN
Dim_CustomerOrderTypes
ON
AA_FactSalesOrderDetails.[Customer order type] = Dim_CustomerOrderTypes.[Customer order type]
WHERE AA_FactSalesOrderDetails.[Order Date]
BETWEEN
dbo.fnc_M3_sql_datetime_to_M3_date(@StartDate) /* !!!Procedural Approach!!! */
AND
dbo.fnc_M3_sql_datetime_to_M3_date(@EndDate) /* !!!Procedural Approach!!! */
AND
AA_FactSalesOrderDetails.Salesperson = @SalesPersonCode
【问题讨论】:
dbo.fnc_M3_sql_datetime_to_M3_date()
是做什么的?如果代码不是很长,您可以发布它吗?
除了@Yuck 的问题,表格有多少行?此查询返回多少行? [Order Date] 的数据类型和函数的返回数据类型是什么? [Order Date]、Salesperson 或连接条件中的任何列是否有索引?您是否查看了执行计划以了解时间都花在了哪些地方?
Dim_Customers.Dim_Customers_dKey
是否有一个聚集索引覆盖它?另外,AA_FactSalesOrderDetails.Dim_Customers_dKey
是带有覆盖索引的 Dim_Customers.Dim_Customers_dKey
的外键吗?
感谢您的关注 Yuck:代码太长了,大约 700 个字符 Aaron Bertrand:返回 3037 行,大约 100 个表,我不允许创建聚集索引,在 Index Seek 中花费的时间昏暗的客户,其他人低于 %20 Jesse C. Slicer:不幸的是,我不允许创建聚集索引,如果可能,我必须更改查询方式谢谢大家
如果这是返回正确逻辑的查询,您不能神奇地“修复”查询或告诉它更快,除非可能删除那些函数调用(我们无法告诉您如何这样做,因为我们不知道函数做什么或它返回什么数据类型,但它们当然可以从查询中移出)。通常,您“修复”慢查询的方式是改进您的索引策略、确保逻辑正确、删除不必要的连接或列、更新统计信息、验证执行计划(以及它是否被缓存)等。
【参考方案1】:
由于 fnc_M3_sql_datetime_to_M3_date 采用在整个查询执行过程中保持不变的值,请将这两个调用(一个带有 startDate 的一个和一个带有 endDate 的调用移到查询的顶部,并将返回的值分配给声明的变量。然后参考那些在下面声明的变量,而不是在 where 子句中调用函数。这可能会有所帮助。函数有时会抑制良好查询计划的制定。
这里稍微讲一下 Why do SQL Server Scalar-valued functions get slower? 这也是 http://strictlysql.blogspot.com/2010/06/scalar-functions-on-where-clause.html
declare @m3StartDate Numeric(8,0)
Set @m3StartDate = fnc_M3_sql_datetime_to_M3_date(@StartDate)
declare @m3EndDate Numeric(8,0)
Set @m3EndDate = fnc_M3_sql_datetime_to_M3_date(@EndDate)
...
WHERE AA_FactSalesOrderDetails.[Order Date]
BETWEEN @m3StartDate AND @m3EndDate
AND
AA_FactSalesOrderDetails.Salesperson = @SalesPersonCode
这两个@m3--vars的类型应该与AA_FactSalesOrderDetails.[Order Date]完全相同。
我还将检查 Dim_Customers 上获取扫描而不是查找的键的定义,并确保 Dim_Customers 以一种对您有所帮助的方式编入索引(如果还没有的话)。 http://blog.sqlauthority.com/2009/08/24/sql-server-index-seek-vs-index-scan-diffefence-and-usage-a-simple-note/
【讨论】:
+1 但您必须使用分析器验证函数调用是否实际发生多次。在很多情况下(每个版本都会变得更好)SQL Server 非常擅长优化这些调用 - 取决于函数的性质和它们的使用位置。无论如何,我仍然同意将它们移出主查询。 我不能肯定地说它会提高性能。有时 SQL Server 可以很好地处理函数,但我们看不到函数中的内容。但是考虑到发帖人提到的限制,这是一个简单的尝试,而且他的查询相当简单,所以我没有看到很多其他选项。 对不起,如果我说的是无关紧要的方式,但是如果我用 UNION ALL 关键字划分这个查询是否可以减少时间?顺便问一下,你能告诉我我该怎么做吗?建议? 如何将您的查询与 UNION ALL 分开?似乎您的 WHERE 子句仅由针对连接中一个表的条件决定,并且您的所有连接都由一个连接条件决定,因此我不确定您如何将其分解为单独的查询。 事情是这样的:当我删除该功能时,新的时间是 35 秒【参考方案2】:虽然@hatchet 避免在WHERE
子句上使用函数是正确的,但我想在这种情况下这不是问题,因为它用于标量值(只能通过实际查询计划确定)。
当然,您可以删除对表Dim_CustomerOrderTypes
的引用,即不过滤也不返回任何数据。而且我相信这个查询应该使用以下索引来提高性能:
-- to seek on [Salesperson] and scan on [Order Date]
CREATE CLUSTERED INDEX IDXC ON AA_FactSalesOrderDetails([Salesperson], [Order Date]);
-- to seek on key
CREATE CLUSTERED INDEX IDXC ON Dim_Customers([Dim_Customers_dKey]);
-- to seek only this index instead of reading from table
CREATE INDEX IDX0 ON Dim_SalesOrganisation([Salesperson], [Salesperson name]);
-- to seek only this index instead of reading from table
CREATE INDEX IDX0 ON Dim_Items ([Item number], [Product group description])
希望这些建议对你有所帮助。
【讨论】:
【参考方案3】:我愿意赌这个版本的运行速度超过 35 秒。
现在,可能还有其他可能的优化(例如创建或改进索引,我们不看计划就无法知道),但我认为我已经清理了您查询中应该有助于提高性能的几个问题。
EDIT 进行了一些编辑,因为即使问题被标记为 2008...
-- make sure you don't have an implicit conversion between varchar and nvarchar
DECLARE
@SalesPersonCode NVARCHAR(4),
@StartDate DATETIME,
@EndDate DATETIME;
SELECT
@SalesPersonCode = N'AC', -- nvarchar needs N prefix!
-- get rid of the function call, I am guessing it just removes time
-- in which case, use the DATE data type instead.
@StartDate = '20120301',
@EndDate = '20120331';
-- since a salesperson can only have one code, and you are only pulling the name into the
-- SELECT list (it will be the same for every row), use a constant and eliminate the join.
DECLARE @SalesPersonName NVARCHAR(255);
SELECT @SalesPersonName = SalesPerson_Name
FROM dbo.Dim_SalesOrganisation
WHERE SalesPerson = @SalesPersonCode;
-- I've added table aliases which make the query MUCH, MUCH easier to read
SELECT f.Salesperson
, Salesperson_name = @SalesPersonName
, f.[Order Date]
, c.[Customer number]
, c.[Customer name]
, c.[Area/state]
, c.country
, c.[Customer stop] AS [Customer Block]
, f.[Customer order stop] AS [Co Stop]
, f.[First delivery date Header]
, f.[Last delivery date Header]
, c.[User-defined field 6 - customer]
, c.[Customer group name]
, f.[Contact Method]
, f.[Customer order number]
, f.[Price Level]
, f.[Item number]
, i.[Product group description] AS [Item name]
, f.[Ordered quantity - basic U/M] AS [Quantity Ordered]
, f.[Ordered quantity - basic U/M] * f.[Net price] AS [Order Line Total ]
-- I've also added schema prefix. See below *
FROM
dbo.AA_FactSalesOrderDetails AS f
-- I've removed the join to Dim_SalesOrganisation as per above
LEFT OUTER JOIN dbo.Dim_Customers AS c
ON f.c_dKey = c.Dim_Customers_dKey
LEFT OUTER JOIN dbo.Dim_Items AS i
ON f.[Item number] = i.[Item number]
-- I've removed the join to Dim_CustomerOrderTypes since it is never used
WHERE
-- in case [Order Date] is DATETIME and includes time information. See below **
f.[Order Date] >= @StartDate
AND f.[Order Date] < DATEADD(DAY, 1, @EndDate)
-- still need to restrict it to the stated salesperson
AND f.SalesPerson = @SalesPersonCode;
*
Bad habits to kick : avoiding the schema prefix
**
Bad habits to kick : mis-handling date / range queries
【讨论】:
Msg 139,级别 15,状态 1,行 0 无法为局部变量分配默认值。消息 137,级别 15,状态 2,第 16 行 必须声明标量变量“@SalesPersonCode”。 Msg 137, Level 15, State 2, Line 52 必须声明标量变量“@StartDate”。 SQL 2000?只需更改顶部的变量声明部分,这样您就不会立即设置默认值,然后在声明后立即设置这些值。 您使用的是 SQL Server 2000?为什么问题标记为 SQL Server 2008? 我已经更新了我的答案,以便它可以在 SQL Server 2000 上运行。当你用特定版本标记你的问题时请准确 - 我们不太关心你的 Management Studio 版本重新运行,我们关心SELECT SERVERPROPERTY('ProductVersion');
说什么。
不,我在 MS SQL 2008 R2 Developer Edition 9.00.4035.00 中运行此代码以上是关于需要减少查询时间的主要内容,如果未能解决你的问题,请参考以下文章