有趣的SQL Server性能调优问题

时间:2009-10-23 09:49:35

标签: sql-server sql-server-2005

我一直在处理存储过程性能问题超过一周,并且与我在Stackoverflow here上的其他帖子有关。我来介绍一些背景信息。

我们有一个夜间进程,它运行并由一个存储过程启动,该存储过程调用许多其他存储过程。很多被调用的存储过程都会调用其他的等等。我已经看过一些被调用的过程,并且存在各种各样令人害怕的复杂内容,例如XML字符串处理,不必要的过度使用游标,NOLOCK提示过度使用,很少使用基于集合的处理等 - 这个列表还在继续,这是非常可怕的。

我们的生产环境中的夜间流程平均需要1:15才能运行。它有时需要2个小时才能运行,这是不可接受的。我在相同的硬件上创建了一个测试环境,然后运行proc。我第一次跑它花了45分钟。如果我将数据库恢复到完全相同的点并再次运行它,则需要更长的时间:实际上,如果我多次重复此操作(恢复并重新运行),则proc会逐渐变长,直到它在2小时左右达到平稳状态。这真让我困惑,因为我每次都将数据库恢复到完全相同的点。服务器上没有其他用户数据库。

我想到了两行调查:

  1. 查询计划和参数欺骗
  2. Tempdb的
  3. 作为测试,我重新启动SQL Server以清除缓存和tempdb,并使用相同的数据库还原重新运行proc。过程花了45分钟。我重复了几次以确保它是可重复的 - 每次花费45分钟。然后我开始进行几项测试,尝试在SQL Server没有重新启动时尝试分离运行时令人费解的增加:

    1. 运行初始存储过程WITH RECOMPILE
    2. 在运行该过程之前,执行DBCC FREEPROCCACHE以清除过程高速缓存
    3. 在运行该过程之前,执行CHECKPOINT,然后执行DBCC DROPCLEANBUFFERS以确保缓存为空且干净
    4. 执行以下脚本以确保标记所有存储过程以进行重新编译:

      DECLARE @proc_schema SYSNAME
      DECLARE @proc_name SYSNAME
      
      DECLARE prcCsr CURSOR local
          FOR SELECT  specific_schema,
                      specific_name
              FROM    INFORMATION_SCHEMA.routines
              WHERE   routine_type = 'PROCEDURE'
      
      OPEN prcCsr
      
      FETCH NEXT FROM prcCsr INTO @proc_schema, @proc_name
      
      DECLARE @stmt NVARCHAR(MAX)
      WHILE @@FETCH_STATUS = 0
          BEGIN
              SET @stmt = N'exec sp_recompile ''[' + @proc_schema + '].['
                  + @proc_name + ']'''
      --        PRINT @stmt   -- DEBUG
              EXEC ( @stmt
                  )
      
              FETCH NEXT FROM prcCsr INTO @proc_schema, @proc_name
          END
      
    5. 在上述所有测试中,使用相同的数据库还原运行该过程所需的时间越来越长。我现在真的不知道该尝试什么。在这一点上查看代码是一种选择,但实际上它需要3-6个月来进行优化,因为那里有很大的改进空间。我真正感兴趣的是,为什么每次执行数据库恢复时,即使已经清理了过程和缓冲区缓存,proc执行时间也会变长?

      我还调查了tempdb,并尝试清除那里的旧表,如我在其他stackoverflow帖子中所述,但是我无法手动清除从表变量手动创建的临时表,它们似乎没有想要自己消失(即使离开它们24小时后)。

      非常感谢任何有关进一步测试的见解或建议。我在Windows 2003 R2 Ent上运行SQL Server 2005 SP3 64位企业版。版集群。

      此致 标记

12 个答案:

答案 0 :(得分:2)

可能导致此问题的一个原因是该进程是否泄漏了XML文档。这将导致SQL Server使用更多内存,其中一部分可能会被写入磁盘上的页面文件,从而导致进程变慢。

创建XML文档的代码如下所示:

EXEC sp_xml_preparedocument @idoc OUTPUT, @strXML

如果没有相应的话,它会泄漏:

EXEC sp_xml_removedocument @idoc

XML文档是存储在已配置的SQL Server内存之外的COM对象。即使您将SQL Server设置为使用最大5 GB,泄漏的XML文档也会增加内存使用量。

答案 1 :(得分:2)

回顾迄今为止的所有帖子以及您的相关问题,听起来您最强大的领导是这些tempdb对象背后的秘密。一些主要问题:

  • 重新启动后,运行进程后tempdb中有多少个对象?每次重新开始后都是相同的数字吗?
  • “连续”运行后数字是否增长?他们以同样的速度增长吗?
  • 你能确定他们是否占用空间吗?
  • 就此而言,您的tempdb文件随着流程的每次连续运行而增长?

我按照链接,但没有找到任何参考讨论实际问题。您可能希望在Microsoft SQL Technet论坛here上提出问题 - 它们可以很好地处理抽象内容。 (如果所有其他方法都失败了,你可以打开一个MS技术支持的案例。这可能需要几天时间,但很可能他们会解决问题。如果是MS错误,他们会退还你的钱!)

您已经说过重写代码不是一种选择。但是,如果临时表滥用是一个因素,首先识别和重构代码的这些部分可能会有很大帮助。要查找可能的内容,请在进程执行时运行SQL事件探查器。这种工作,唉,主观和高度迭代(意味着你在第一次通过时几乎没有得到正确的计数器组)。一些想法:

  • 从跟踪SP开始:开始,跟踪正在调用的存储过程。
  • SQL事件探查器可用于分组数据;它很尴尬,我不知道如何用纯文本来描述它,但配置得当你会得到一个Profiler显示,显示每个程序的次数。理想情况下,这将显示最频繁调用的过程,您可以根据需要分析它们的临时表滥用和重构。
  • 如果没有任何内容跳出来,您可以跟踪SP:StmtStarting并为单个语句执行相同的操作。这里的问题是,在2 +/-小时的意大利面条代码运行中,您可能会耗尽磁盘空间,并且分析100个MB的跟踪数据可能是一场噩梦。 (提示:将其加载到表中,构建索引,然后小心地删除掉。)再次,目标是识别过度使用/滥用的临时表代码进行重构。

答案 2 :(得分:2)

马克 -

因此完全重写此过程可能需要3-6个月,但这并不意味着您无法进行相对快速的性能优化。

我必须支持的一些例程运行30小时+,我会欣喜若狂让它们在2小时内运行!您对这些例程执行的优化与普通的OLTP数据库略有不同:

  1. 捕获整个过程的跟踪,确保捕获SP:StmtCompleted和SQL:StmtCompleted事件。确保在持续时间(> 10ms或其他)上设置过滤器,以消除所有快速,不重要的陈述。

  2. 将此跟踪拉入表格,并进行一些过滤/排序/分组,重点关注持续时间和读取。您最终可能会遇到以下两种情况之一:

    (A)少数个人查询/陈述负责程序的大部分时间(好消息)

    (B)很多相似的陈述都需要花费很短的时间,但是它们总共需要很长时间。

  3. 在方案(A)中,只需将注意力集中在这些查询上。使用索引或使用其他标准技术优化它们。我强烈推荐Dan Tow的书“SQL Tuning”,它是一种优化查询的强大技术,尤其是具有复杂连接的混乱查询。

    在方案(B)中,退一步,查看整个语句的 set 。它们在某种程度上都相似吗?你能在密钥,公共表上添加一个索引来改进它们吗?你能否消除一个执行10,000个动态查询的循环,而是执行一个基于集合的查询?

    我想还有两种可能性:

    (C)15,000个完全不同的动态SQL语句,每个语句都需要自己的精心优化。在这种情况下,请尝试关注服务器级优化,例如基于I / O的改进,这些改进将使所有人受益。

    (D)使用TempDB或服务器上配置错误的其他奇怪事情。除了找到问题,我在这里说的不多,并修复它!

    希望这有帮助。

答案 3 :(得分:2)

您可以在测试服务器上尝试以下方案:

  1. 在服务器上制作两份数据库:[A]和[B]。 [A]是有问题的数据库,[B]是副本。
  2. 重新启动服务器
  3. 运行您的流程
  4. 删除数据库[A]
  5. 将[B]重命名为[A]
  6. 运行您的流程
  7. 这就像一个热门的数据库交换。如果第二次运行需要更长时间,则会发生服务器级别的某些事情(tempdb,内存,I / O等)。如果第二次运行大约需要相同的时间,则问题出在数据库级别(锁,索引碎片等)。

    祝你好运!

答案 4 :(得分:1)

在测试开始时运行以下脚本,然后在每次迭代后运行:

select sum(single_pages_kb) as sum_bp_kb
  , sum(multi_pages_kb) as sum_va_kb
  , type
from sys.dm_os_memory_clerks
group by type
having sum(single_pages_kb+multi_pages_kb) > 16
order by sum(single_pages_kb+multi_pages_kb) desc

select sum(total_pages), type_desc
from tempdb.sys.allocation_units
group by type_desc;

select * from sys.dm_os_performance_counters
where counter_name in (
  'Log Truncations'
  ,'Log Growths'
  ,'Log Shrinks'
  ,'Data File(s) Size (KB)'
  ,'Log File(s) Size (KB)'
  ,'Active Temp Tables');

如果结果不言自明,你可以将它们发布到某处并在此处放置一个链接,我可以查看它们,看看是否有些奇怪的东西。

答案 5 :(得分:0)

整个过程的作用是什么,正在执行的操作的目的是什么?

我认为执行该过程会导致数据库中的数据修改。是这样的吗?

如果是这种情况,那么每次运行流程时,开始考虑的数据都是不同的,因此不同的执行计划生成是可能的,执行时间也不同。

假设发生了对数据库数据的修改,那么您还应该调查:

  • 更新相关数据库统计信息 每个流程之间运行。
  • 查看索引级别 每个过程之间的碎片化 运行并确定碎片整理是否可以证明是有益的。

答案 6 :(得分:0)

显然你想尝试除了你真正需要做的事情之外的任何东西,这是修复过程。首先摆脱游标。如果现在需要两个小时,没有光标,我敢打赌你可以把它缩短到不到十分钟。

答案 7 :(得分:0)

我会将信息记录到log_table和运行每个步骤所花费的时间......这将有助于您缩小问题范围,并通过一次解决问题来帮助您逐步改进流程(从改进流程开始)最长的。)

最好的方法是简单地插入每个过程的开头和结尾。

答案 8 :(得分:0)

游标不是性能提升者,其他游戏也是如此。 (不是你的决定)

查看临时表使用/管理。它们是全局临时表还是会话/本地临时表?他们闲逛的事实看起来很有趣。创建临时表时,tempdb将被锁定,这可能是问题的一部分。

当会话超出范围时,本地临时表(#mytable语法)应该消失,但是你应该放弃这些(早期发布)以释放资源。

在事务中使用本地临时表然后在没有COMMIT / ROLLBACK的情况下取消会增加tempdb中的锁定,从而导致性能问题。 说到事务 - 如果在事务中创建临时表,这将导致对syscolumns,sysindexes等的锁定 - 因此阻止其他执行使用相同的查询。

通过调用被调用过程中的过程创建的临时表的使用指向逻辑需求 - 重新考虑并尝试使用关系结构。

如果您需要临时表(以消除游标:),请避免使用SELECT INTO - 以避免系统对象锁定。

应避免使用全局临时表(## myglobaltable语法),因为多个会话访问可以发生(表格会一直挂起,直到所有会话都清除),至少对我来说,没有任何附加的逻辑价值主张(看起来转而使用永久表)。问题是否全球化,是否存在阻止程序?

是否存在大量稀疏临时表(使用大数据增长,但其中包含较小的数据集?)

Microsoft SQL Server联机丛书, “考虑使用表变量而不是临时表。如果需要在其上显式创建索引,或者需要跨多个存储过程或函数显示表值,则临时表非常有用。通常,表变量有助于提高查询处理效率。“

当然,如果临时表需要索引,则表格变量不是一个选项。

答案 9 :(得分:0)

我没有答案,但我会采取一些措施来解决这类问题。

首先,我会在每次执行之前和之后拍摄sys.dm_os_wait_stats的快照。您减去2个快照(获得增量)并查看每次运行时是否有任何特定的WAIT突出或变得更糟。计算增量的简单方法是将sys.dm_os_wait_stats值复制到Excel工作表中,并使用VLOOKUP()减去相应的值。我已经使用了这种调查技术数百次次。你不知道SQL Server挂在哪个方面?!让SQL Server通过sys.dm_os_wait_stats“告诉”您!

我可能尝试的另一件事是调整循环的行为,以了解后续较慢的执行是否从头到尾显示所有记录的常量吞吐量,或者它是否仅在INFORMATION_SCHEMA.routines中的特定sproc中减慢...探索这个的2种技巧是:

1)在SQL SELECT中添加“top N”子句,例如“top 100”或“top 1000”(创建一个人工限制),以查看是否所有记录计数方案的后续减速...或..当光标结果集足够大以包含有问题的sproc时,你是否只得到减速。

2)您可以添加更多的打印语句(检测)来计算处理时的吞吐量,而不是添加“前N”。

当然,你可以将两者结合起来。

也许这些诊断会让您更接近根本原因。

编辑添加:顺便说一句,SQL2008有一个新的性能监视器,可以很容易地“注意”sys.dm_os_wait_stats的数量。但是对于SQL2005,您必须通过Excel或脚本手动计算增量。

答案 10 :(得分:0)

这些是远景:

  • 快速查看所有内容 事物的存储过程 不寻常和SQL Server不应该 真的在做,比如发送 发送电子邮件或写入文件等。尝试向不存在的电子邮件服务器发送电子邮件的SQL可能会导致延迟。
  • 要记住的另一件事是 在还原数据库时 每次测试之前可能是你的磁盘 越来越分散(不是 虽然确实如此。所以 这可以解释为什么每次运行时间都会变长,直到它们达到稳定状态。

答案 11 :(得分:0)

首先,感谢大家提供了一些非常好的帮助。我非常感谢您帮助我解决这个非常奇怪的问题的时间和专业知识。我有更新。

我启动了一个服务器端跟踪来尝试隔离迭代之间运行较慢的存储过程。我发现让我感到惊讶。该过程涉及96个存储过程。大多数这些存储过程第二次运行速度较慢 - 大约50个。其余的都很快运行,并没有影响整体时间,事实上其中一些运行得更快(正如预期的那样)。

我将数据库实例故障转移到我的集群中的另一个节点并在那里运行完全相同的结果 - 因此我可以排除集群节点之间的任何操作系统差异 - 在构建集群时我非常有意识地构建它们

1100个临时表在过程中创建并在完成后保持 - 这些都是表变量,我找到了删除它们的方法。在数据库中的每个proc和函数上运行sp_recompile会导致所有临时表被清除。但是根本没有改善运行时间。唯一有助于运行时的是重启SQL Server服务。不幸的是,我现在没时间对此进行进一步调查 - 我还有其他工作要做,但我想坚持下去。如果我有空闲的几个小时,也许以后我会回来。然而,与此同时,我不得不承认没有解决方案而且没有赏金给予失败。

再次感谢大家。