一个简单的 VB.NET SQL Server 2000 压力测试工具






4.47/5 (19投票s)
2004 年 4 月 4 日
7分钟阅读

232851

1150
本文介绍了一个 VB.NET 工具,用于测试 Microsoft SQL Server 2000 用户数据库的性能。
引言
正如任何开发人员所知,任何数据访问应用程序开发的调优阶段都涉及到数据库响应时间的分析,可能随后会细化一些数据库设计方面(例如,审查一些编写不佳的存储过程或采用的索引策略)。
这种优化不能忽视高负载场景,因此在“压力”条件下测试应用程序是一个好习惯。例如,ASP.NET Web 应用程序通常会使用压力测试工具进行测试,如 Microsoft Application Center Test(也称为 ACT),该工具允许您模拟多个用户同时请求网站的 ASPX 页面。
然而,在某些情况下,这些压力测试环境并不适合指出数据库方面的性能瓶颈。例如,使用 ACT,您可以对 Web 服务器的响应时间进行一些测量,按页面进行;但您测量的是Web 服务器的响应时间,其中包括处理/渲染 Web 页面所花费的时间加上从数据库服务器访问数据所花费的时间。有时很难确定性能不佳的原因是 ASPX 页面代码还是涉及的数据库活动。
因此,一种新的需求出现了:衡量纯粹的数据库响应时间。这在 Web 应用程序的开发和测试中尤其如此(当您想测量数据库响应时间,而没有 Web 服务器处理引入的噪音时),在许多其他多层场景中也是如此,您想在排除上层软件层的影响的情况下评估数据库性能。
当然,可以通过 Microsoft SQL Server Profiler 等工具在 Web 应用程序页面被 ACT 模拟的工作负载命中时收集此类数据库方面的测量数据。但是,如果您以这种方式收集测量数据,您需要记住,数据库工作负载始终来自 Web 服务器,这在测试环境和真实环境之间的 Web 基础架构存在差异时可能不现实。举个例子:假设您的生产体系结构包含一个您在测试环境中没有的 Web 服务器场;在观察了测试环境的性能之后,您也许可以从 ASP.NET 的角度想象您的 Web 应用程序在生产环境中将如何扩展,但很难说数据库在生产环境中将如何表现,因为它将暴露于来自多个 Web 服务器的工作负载。
本文介绍的名为 _DBstressUtil_ 的工具是一个简单的数据库压力测试工具,它试图解决这些问题,专注于数据库性能分析,并排除任何可能为数据库响应时间测量增加噪音的其他应用程序层(数据访问、业务逻辑、用户界面组件)。
背景
当我面临模拟工作负载以对 SQL Server 数据库进行压力测试的问题时,有人建议我查看 _Microsoft SQL Server 2000 Resource Kit_。事实上,_Resource Kit_ 包含一个名为 _Database Hammer_ 的 VB6 工具,这是一个用于测试 SQL Server 2000 实例性能的工具:它创建自己的工作表,然后对这些表提交大量的 INSERT
/UPDATE
语句,模拟并发连接到数据库的用户。这样您就可以观察某个 SQL Server 安装的性能。
但我的目标不同:我需要测试的不是 SQL Server 的通用实例,而是特定数据库,包括其索引、存储过程、触发器等。因此,_Database Hammer_ 对我来说没有用,但我喜欢通过 Windows 应用程序模拟并发数据库用户的基本想法。从那时起,构思 _DBstressUtil_ 的想法就顺理成章了。
该实用程序的工作方式
_DBstressUtil_ 是作为一个 Windows Forms VB.NET 应用程序编写的,它设计用于在 SQL Server 2000 数据库上工作(当然,使用 `System.Data.SqlClient` 命名空间中的 SQL Server .NET Managed Provider 类)。
_DBstressUtil_ 模拟多个数据库用户提交 T-SQL 脚本(执行存储过程或简单发送 DML 命令)。这些脚本取自输入文本文件(我们将其称为“测试脚本”),该文件需要提前准备好,使用简单的文本编辑器。
运行 _DBstressUtil_ 时,在主窗体上,您可以配置一些执行选项,例如:
- 要模拟的同时运行的用户数(也称为“客户端实例”)(参见 `txtInstances`),
- 一个用户活动开始和下一个用户活动开始之间的时间(参见 `txtClientShift`),
- 每个模拟用户执行的每个命令之间的间隔(参见 `txtInterval`)。
当您开始压力测试时,会创建多个并发线程,每个线程开始执行从“测试脚本”中获取的 T-SQL 命令。每个命令的执行时间(以毫秒为单位)都会被测量并记录在 CSV 输出文件中,以便于对收集到的数据进行后续处理。在测试期间,您可以监视当前正在运行的模拟客户端实例的数量(参见 `txtRunningInstances`)以及已执行命令的总数(参见 `txtDoneCommands`)。
测试运行的结束标志是已执行命令的总数等于客户端实例数乘以“测试脚本”中包含的命令数。此时,您可以检查 _DBstressUtil_ 生成的 CSV 日志文件;该输出文件将包含由以下字段组成的记录:
- 特定客户端实例执行某个命令的开始日期和时间;
- 执行命令的模拟客户端实例的 ID(范围从 1 到 `txtInstances` 值);
- 已执行命令的 ID(范围从 1 到“测试脚本”中包含的总命令数);
- 与该特定客户端实例提交的该特定命令相关的执行时间(以毫秒为单位)。
现在,聊聊代码……
每个模拟客户端实例都是 `ClientProc` 类的实例。该类的 `StartClientProcess` 方法负责单个客户端的所有模拟过程,它由主程序为 `ClientProc` 的每个实例在新线程中调用,如下所示:
Dim i As Integer ' Counter of Client process instances
Dim x() As ClientProc ' Array of Client process instances
...
x(i) = New ClientProc
x(i).ConnectString = ...
x(i).WaitInterval = Convert.ToInt32(txtInterval.Text)
x(i).CommandFile = txtCommandFile.Text
x(i).LogFile = txtLogFile.Text
...
Dim t As New Thread(AddressOf x(i).StartClientProcess)
t.Start()
`StartClientProcess` 方法的步骤是:
- 开始时,增加“当前运行实例”计数器;
- 将“测试脚本”加载到字符串数组中(参见私有方法 `LoadCommandFile()`);
- 循环遍历“测试脚本”中的命令,对于每个命令:
- 通过 `SqlCommand.ExecuteNonQuery()` 方法执行它;
- 将其执行时间记录在 CSV 输出文件中;
- 增加“已执行命令总数”计数器;
- 在执行下一个命令之前等待指定的暂停间隔;
- 结束时,减少“当前运行实例”计数器。
所有访问多个客户端实例共享的资源的 \{\{代码\}\} 都受 `Mutex` 保护(例如:输出文件访问,或 `MainForm` UI 元素的更新)。
最后,请记住……
- “测试脚本”的每一行都必须是一个有效的 T-SQL 脚本;因此,“测试脚本”中的一行可以是以下形式:
- 一个单独的 `EXEC stored_procedure @param=value` 命令;
- 一个单独的
SELECT
/INSERT
/UPDATE
/DELETE
命令; - 一个更复杂的 T-SQL 脚本,由多个 DML 命令组成。
- 在“测试脚本”中,您可以包含注释行,这些注释行使您的脚本更具可读性,并且 _DBstressUtil_ 会忽略它们(在隐式编号的提交命令中也是如此);注释行是以下划线 "--"(两个连字符)开头的行;所有其他行都被视为要提交到 SQL Server 数据库的有效命令(或完整的 T-SQL 脚本);
- _DBstressUtil_ 对每个模拟客户端实例使用相同的连接字符串:因此,在评估您的测量数据时,您需要考虑数据库连接池的影响(如果启用了该功能);
- _DBstressUtil_ 目前不处理数据库错误:因此,在评估您的测量数据时,请记住一个极快的命令可能是一个实际产生 SQL 错误的命令……
- _DBstressUtil_ 目前不支持“平滑”中断测试运行:因此,在配置您的“测试脚本”和执行参数时要小心,以避免由于测试运行时间过长而需要终止应用程序。
- Zip 存档中包含的 `commands.txt` 文件包含在示例 _Northwind_ 数据库上操作的非常简单的命令:当然,由于这些简单的命令和数据库的大小,在 _Northwind_ 数据库上使用 `commands.txt` 文件并不需要 _DBstressUtil_!请在更大的数据库上尝试 _DBstressUtil_,特别是当它们包含大量以复杂 T-SQL 代码编写的业务逻辑时……尽情享受吧!