奇怪的SQL 2000查询性能问题

时间:2009-03-03 09:26:44

标签: sql sql-server performance

在一些SQL查询性能调优中,我注意到以下查询运行缓慢,但它没有颠覆CPU,似乎没有其他系统瓶颈导致它运行缓慢。事实上,当它运行时,CPU的平均值是15%:

更新 有问题的查询在一个包含800条记录的游标循环中运行:

cursor = SELCT DISTINCT param1,param2, param3, param4 
    FROM t_Table 
    GROUP BY param1,param2, param3, param4 
    ORDER BY param1,param2, param3, param4 DESC 

cursor loop:
  SELECT @maxval1 = max(iField1), 
    @maxval2 = max(iField2), 
    @dtDateMin = Min(dtDate),
    @dtDateMax = Max(dtDate) 
  FROM t_Table 
  WHERE iSomeField1 = @param1 
    AND iSomeField2 = @param2 
    AND iSomeField3 = @param3 
    AND iSomeField4 = @param4
next

(注意:iSomeField1-4有索引设置)

然后我将各个最小/最大部分分成四个查询,以查看服务器如何响应并获得完全吞吐量,CPU达到峰值100%,并且块在2秒内运行,而不是> 5分钟以上。虽然我确实对性能提升感到满意,但我想要一些聪明的DBA解释为什么会这样,以及他们可以对这些类型的查询提供哪些其他提示?

SELECT TOP 1 @maxval1 = iField1 
FROM t_Table 
WHERE iSomeField1 = @param1 
  AND iSomeField2 = @param2 
  AND iSomeField3 = @param3 
  AND iSomeField4 = @param4 
ORDER BY field1 DESC

SELECT TOP 1 @maxval2 = iField2 
FROM t_Table 
WHERE iSomeField1 = @param1 
  AND iSomeField2 = @param2 
  AND iSomeField3 = @param3 
  AND iSomeField4 = @param4 
ORDER BY field2 DESC

SELECT TOP 1 @dtDateMin = dtDate 
FROM t_Table 
WHERE iSomeField1 = @param1 
  AND iSomeField2 = @param2 
  AND iSomeField3 = @param3 
  AND iSomeField4 = @param4 
ORDER BY dtDate ASC

SELECT TOP 1 @dtDateMax = dtDate 
FROM t_Table 
WHERE iSomeField1 = @param1 
  AND iSomeField2 = @param2 
  AND iSomeField3 = @param3 
  AND iSomeField4 = @param4 
ORDER BY dtDate DESC

请注意我是开发人员,而不是DBA,但是想了解更多关于SQL Server如何在上面的查询后面的工作。为了添加下面的一些答案,我知道并且确实使用查询执行计划程序来分析性能问题,令我感到困惑的是,尽管表上有索引,为什么第一个查询在SQL服务器上执行得很糟糕。

更新:在游标循环中运行的查询1的CPU使用情况屏幕截图:

CPU usage for query 1 and query 2 http://img22.imageshack.us/img22/3262/sqlperfodd.png Query 1 EP http://img513.imageshack.us/img513/5681/query1.png Query 2 EP http://img365.imageshack.us/img365/9715/query2.png

架构如下所示(这是我们真实表格的缩减版本,但结构和索引是我们使用的)。

CREATE TABLE [dbo].[t_Table] (
[ID] [int] IDENTITY (1, 1) NOT NULL ,
[dtDate] [datetime] NULL ,
[iField1] [int] NULL ,[iField2] [int] NULL ,
[iSomeField1] [int] NULL ,[iSomeField2] [int] NULL ,
[iSomeField3] [int] NULL ,[iSomeField4] [int] NULL) ON [PRIMARY]

CREATE  CLUSTERED  INDEX [IX_dtDate] ON [dbo].[t_Table]([dtDate], [iField1],
[iSomeField1]) WITH  FILLFACTOR = 90 ON [PRIMARY]

ALTER TABLE [dbo].[t_Table] ADD 
CONSTRAINT [PK_t_Table] PRIMARY KEY  NONCLUSTERED 
([ID]) ON [PRIMARY] 

CREATE  INDEX [idx_field1234] ON [dbo].[t_Table]([iSomeField1], [iSomeField2],
[iSomeField3], [iSomeField4]) ON [PRIMARY]

CREATE  INDEX [idx_field1] ON [dbo].[t_Table]([iSomeField1]) [PRIMARY]

4 个答案:

答案 0 :(得分:1)

我查看了您的查询并通过它运行一些快速测试。正如Diego所说,查询分析器中的执行计划是一个很好的解决此类问题的工具。

首先关于索引的注释 - 当SQL收到查询时它将运行一系列步骤 - 为了简化此过程,这里的主要任务之一是确定检索数据的最有效方法是什么。 SQL将查看表结构,统计信息和索引,以确定它认为最佳路径。它最终将选择一个索引用于检索数据(从不多个索引)。

索引基本上是对数据存储在表中的位置的查找(或者在聚集索引的情况下,它实际上定义数据如何存储在磁盘上)。如果您的查询使用了多个列(在查询,where或order子句中),则查询将需要检索这些列以便它可以返回您的查询。 SQL将查看索引,然后查看检索索引所需的顺序。理想情况下,SQL将能够“直接”搜索您请求的数据(最常见的替代方法是“扫描”,这基本上意味着已扫描整个索引/结构的数据)。这些术语用于上述执行计划中。上面有很多额外的复杂性(例如,在某些查询中,您可能会看到书签查找,其中SQL使用索引查找行,然后执行查找以获取任何关联数据)但这应该是一个很好的开始步骤

另外,我会提到一个我认为非常适合SQL性能信息的网站 - www.sql-server-performance.com

通过以上操作并应用您的信息,我们可以看到您的主查询正在执行聚簇索引“扫描” - 即它搜索整个表以检索您请求的信息。这意味着没有找到任何可以直接查找所需信息的索引。要执行此查询,SQL需要对字段1 - 4执行过滤,然后在字段和日期列上聚合信息(MAX查询)。

作为一个极端的例子,这个索引允许搜索:

CREATE INDEX [idx_field1234567] ON [dbo].[t_Table](
   [iSomeField1], 
   [iSomeField2], 
   [iSomeField3], 
   [iSomeField4], 
   [dtDate], 
   [iField1],
   [iField2]
) 
WITH FILLFACTOR = 90, PAD_INDEX  ON [PRIMARY]

但是您应该注意创建索引,并且通常应该避免向索引添加太多列,并且应该避免在表上使用太多索引。在SQL 2000中,索引调优向导可以很好地提供合适的索引基线。

正如迭戈所说,上述情况可能看起来令人生畏,但上面的网站对我来说是一个很好的参考。

祝你好运!

答案 1 :(得分:1)

这取决于。桌上有哪些索引?表格中有多少行? 我刚刚创建了一个表现良好的样本,但它可能与您的场景非常不同。通常,如果优化器出现问题,则需要简化查询。你做了什么也许是需要的。这取决于。这是我敲了一下的SQL,看看我是否可以在show执行计划中找到任何明显的内容。

上设置nocount

GO

如果object_id('tempdb ..#MaxMinExample')不是空丢弃表#MaxMinExample

GO

create table #MaxMinExample([key] int identity(1,1)primary key clustered,iField1 int,iField2 int,dtDate datetime,iSomeField1 int,iSomeField2 int,iSomeField3 int,iSomeField4 int)

GO

- 我们将笛卡尔式的初始数据集

插入#MaxMinExample(iField1,iField2,dtDate,iSomeField1,iSomeField2,iSomeField3,iSomeField4) 值(1,2,getdate(),1,2,3,4) 插入#MaxMinExample(iField1,iField2,dtDate,iSomeField1,iSomeField2,iSomeField3,iSomeField4) 值(2,3,getdate()+ 1,4,5,6,7) 插入#MaxMinExample(iField1,iField2,dtDate,iSomeField1,iSomeField2,iSomeField3,iSomeField4) 值(3,4,getdate()+ 2,5,6,7,8) 插入#MaxMinExample(iField1,iField2,dtDate,iSomeField1,iSomeField2,iSomeField3,iSomeField4) 值(5,6,getdate()+ 3,6,7,8,9) 插入#MaxMinExample(iField1,iField2,dtDate,iSomeField1,iSomeField2,iSomeField3,iSomeField4) 值(6,7,getdate()+ 4,7,8,9,10)

GO

- 创建大量数据

声明@count int set @ count = 1 while(从#MaxMinExample中选择count(*))< 865830 开始     插入#MaxMinExample(iField1,iField2,dtDate,iSomeField1,iSomeField2,iSomeField3,iSomeField4)     从#MaxMinExample中选择a.iField1 + @ count,a.iField2 + @ count,a.dtDate + @ count,a.iSomeField1 + @ count,a.iSomeField2 + @ count,a.iSomeField3 + @ count,a.iSomeField4 + @ count,交叉连接#MaxMinExample b     设置@count = @count + 1 端

GO

- 创建索引

在#MaxMinExample(iSomeField1)上创建索引MaxMinExample_iSomeField1 在#MaxMinExample(iSomeField2)上创建索引MaxMinExample_iSomeField2 在#MaxMinExample(iSomeField3)上创建索引MaxMinExample_iSomeField3 在#MaxMinExample(iSomeField4)上创建索引MaxMinExample_iSomeField4 在#MaxMinExample(dtDate)上创建索引MaxMinExample_dtDate

GO

声明@ maxval1 int,@ maxval2 int,@ dtDateMin datetime,@ dtDateMax datetime,@ param1 int,@ param2 int,@ param3 int,@ param4 int

选择@ param1 = 4,@ param2 = 5,@ param3 = 6,@ param4 = 7

从#MaxMinExample选择@ maxval1 = max(iField1),@ maxval2 = max(iField2),@ ddDateMin = Min(dtDate),@ dtDateMax = Max(dtDate) 其中iSomeField1 = @ param1和iSomeField2 = @ param2和iSomeField3 = @ param3和iSomeField4 = @ param4

从#MaxMinExample中选择前1 @ maxval1 = iField1,其中iSomeField1 = @ param1和iSomeField2 = @ param2和iSomeField3 = @ param3和iSomeField4 = @ param4 by iField1 DESC 从#MaxMinExample中选择top 1 @ maxval2 = iField2,其中iSomeField1 = @ param1和iSomeField2 = @ param2和iSomeField3 = @ param3和iSomeField4 = @ param4 by iField2 DESC 从#MaxMinExample中选择前1个@dtDateMin = dtDate,其中iSomeField1 = @ param1和iSomeField2 = @ param2和iSomeField3 = @ param3和iSomeField4 = @ param4 order by dtDate ASC 从#MaxMinExample中选择前1个@dtDateMax = dtDate,其中iSomeField1 = @ param1和iSomeField2 = @ param2和iSomeField3 = @ param3和iSomeField4 = @ param4 order by dtDate DESC

Mark Ba​​ekdal - www.dbghost.com - SQL Developer

答案 2 :(得分:0)

不幸的是,您提供的信息不足以为您提供准确的答案,但我想我可以给您一个有用的提示。 SQL Server允许您查看它用于访问数据的查询计划;此类计划将详细告诉您访问的内容,时间,方式以及每个步骤中处理的行数。它还告诉您每个步骤消耗多少时间/资源,让您轻松找到瓶颈。

要在查询分析器中显示执行计划,请打开查询菜单,然后单击“显示执行计划”。然后运行您的第一个查询并检查计划;在另一个窗口中,运行第二个查询并再次检查计划。通过这种方式,您可以看到它们之间的区别,使用了哪些索引(如果有)并更好地理解SQL Server。

一个提示:如果一开始看起来很复杂,不要气馁,这只是慢慢来的问题。

最后,SQL Server(当然不是MSDN)的有用资源是www.sqlservercentral.com,您可以在其中找到用户和专家的答案。我希望这会有所帮助。

答案 3 :(得分:0)

最后,项目转移到基于MicroStrategy的新DWH和BI堆栈,因此这成为一个无问题,新DWH有一个不同的架构,并在SQL 2008中完成(但我从来没有到底: /)