SQL Server 缓存刷新和磁盘 I/O

And*_*sch 11 performance sql-server service-broker

我们正忙于对我们在 .NET 4.0 中开发并在后台运行 SQL Server 2008 R2 的 OLTP 系统进行负载测试。系统使用 SQL Server Service Broker 队列,它们的性能非常好,但我们在处理时遇到了一个特殊的趋势。

SQL Server 以极快的速度处理请求 1 分钟,然后增加约 20 秒的磁盘写入活动。下图说明了这个问题。

SQL OLTP 系统 - 性能计数器

Yellow = Transactions per second
Blue   = Total CPU usage
Red    = Sqlsrv Disk Write Bytes/s
Green  = Sqlsrv Disk Read Bytes/s
Run Code Online (Sandbox Code Playgroud)

在故障排除期间,我们尝试了以下方法,但模式没有任何重大变化:

  • 已停止 SQL Server 代理。
  • 杀死了几乎所有其他正在运行的进程(无 A/V、SSMS、VS、Windows 资源管理器等)
  • 删除了所有其他数据库。
  • 禁用所有对话计时器(我们不使用任何触发器)。
  • 从消息队列驱动的方法转向简单/粗略的表监控设计。
  • 使用从轻到重的不同负载。
  • 修复了所有死锁。

似乎 SQL Server 可能正在建立其缓存并以特定的时间间隔将其写入磁盘,但我无法在网上找到任何支持该理论的内容。

接下来,我计划将解决方案转移到我们的专用测试环境中,看看我是否可以复制该问题。在此期间的任何帮助将不胜感激。

更新 1 根据要求,附上一张图表,其中包括Checkpoint Pages/SecPage Life Expectancy和一些磁盘延迟计数器。

SQL OLTP 系统 - 性能计数器 - 检查点

看起来好像检查点(浅蓝线)是我们观察到的性能下降(黄线)的原因。^

磁盘延迟在处理过程中保持相对一致,页面预期寿命似乎没有任何明显影响。我们还调整了 SQL Server 可用的 ram 量,这也没有太大影响。将恢复模型从 更改SIMPLEFULL也没什么区别。

更新 2 通过如下更改“恢复间隔”,我们设法减少了检查点发生的间隔:

EXEC sp_configure 'show advanced options',1
GO 

RECONFIGURE
GO

EXEC sp_configure 'recovery interval', '30'
GO

RECONFIGURE 
GO

EXEC sp_configure 'show advanced options',0
GO
RECONFIGURE
Run Code Online (Sandbox Code Playgroud)

我不确定这是否是不好的做法?

Rem*_*anu 11

其他人已经指出了罪魁祸首:SQL Server 在内存中(在缓冲池中)累积更新,并且只定期(在检查点)刷新它们。建议的两个选项(-k 和检查点间隔)是互补的:

但我没有回应只是为了反驳你收到的好评:)

不幸的是,您看到的是排队处理的一种非常典型的行为。无论您使用 Service Broker 队列还是选择使用表作为队列方法,系统都非常容易出现这种行为。这是因为基于队列的处理写入量很大,甚至比 OLTP 处理写入量更大。入出队原语都是写操作,几乎没有读操作。简而言之,与任何其他工作负载,甚至是 OLTP(即TPC-C 之类的工作负载)相比,队列处理将生成最多的写入(= 最多的脏页和最多的日志)。

非常重要的是,队列工作负载的写入遵循插入/删除模式:插入的每一行都很快被删除。这对于区分插入繁重 (ETL) 工作负载的仅附加模式很重要。您基本上是在为幽灵清理任务提供一顿丰盛的饭菜,而且您可以轻松超越它。想想这意味着什么:

  • enqueue 是一个插入,它会创建一个脏页
  • dequeue 是一个删除,它会再次弄脏同一个页面(可能很幸运并且在检查点之前捕获页面,因此它会避免双刷新,但前提是幸运)
  • 鬼清理将清理页面,使其再次变脏

是的,这确实意味着您最终可能会在三个不同的 IO 请求中,针对您处理的每条消息(最坏的情况)将页面写入磁盘 3 次。这也意味着检查点的随机 IO 将是真正随机的,因为页面的写入点将在两个检查点之间再次被那些移动磁头访问(相比许多 OLTP 工作负载倾向于将写入分组到一些“热点”,不是排队……)。

所以你有这三个写点,竞相一次又一次地将同一个页面标记为脏。那是在我们考虑任何页面拆分之前,由于插入键顺序,队列处理也可能容易发生。相比之下,“典型”的 OLTP 工作负载具有更加平衡的读/写比率,并且 OLTP 写入分布在插入/更新/删除之间,通常更新(“状态”更改)和插入占据大部分份额。队列处理写入专门插入/删除,根据定义,50/50 拆分。

一些后果如下:

  • Checkpoint 成为一个非常热门的问题(对你来说不再是一个惊喜)
  • 您会看到严重的碎片(碎片本身并不重要,因为您不会进行范围扫描,但是您的 IO 效率会受到影响,并且幽灵清理需要更多工作,从而进一步减慢速度)
  • 您的 MDF 存储随机 IO 吞吐量将成为您的瓶颈

我的建议有 3 个字母:S、S 和 D。将您的 MDF 移动到可以处理快速随机 IO 的存储中。固态硬盘。如果你有钱,Fusion-IO。不幸的是,这是使用更便宜的 RAM 无法解决的症状之一......

编辑:

正如 Mark 指出的那样,您有两个逻辑磁盘由一个物理磁盘支持。也许您尝试遵循最佳实践并拆分 D: 上的日志和 C: 上的数据,但可惜无济于事,C 和 D 是同一个磁盘。在检查点之间,您实现了连续吞吐量,但一旦检查点启动,磁盘磁头就开始移动,您的日志吞吐量会崩溃,从而降低整个应用程序的吞吐量。确保将 DB 日志分开,以免受到数据 IO 的影响(单独的磁盘)。

  • 查看列出的 perfmon 计数器,我怀疑您可能对同一驱动器或阵列上的数据和日志是正确的。 (3认同)
  • 顺便说一句,知道*为什么*检查点驱动的 IO 会对应用程序计数器产生如此巨大的影响会很有趣。理想情况下,应用程序应该在检查点工作时继续前进。当然,我假设您不共享 LDF 和 MDF 存储访问路径(如果您这样做,那么您应得的......)。也许您在应用程序中有一些不必要的争用点。 (2认同)