需要减少查询时间

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 中运行此代码

以上是关于需要减少查询时间的主要内容,如果未能解决你的问题,请参考以下文章

我想减少 MYSQL 查询执行时间。我的查询需要很长时间?

如何减少简单选择查询的响应时间?

如何使用 InnoDB 引擎减少此查询的查询时间

优化 SQL 查询以减少执行时间

减少大型查询的执行时间

如何在 SQL SERVER 中将内联 SQL 查询转换为 JOINS 以减少加载时间