更新性能






4.20/5 (2投票s)
本项目通过四种不同的变体测试将行插入数据库的性能。
引言
本项目比较了在一个两层应用程序中不同数据访问层实现的性能。表示层是一个Windows Forms应用程序,而数据访问层有四种不同的实现方式。
SQLDataAccess
:不使用事务的类,使用SqlDataAdapter
。SqlDataAccessTrans
:使用事务的类,使用SqlDataAdapter
和SqlTransaction
。SqlDataAccessComPlus
:使用事务的COM+类,使用SqlDataAdapter
和ContextUtil
。SqlDataAccessWS
:使用事务的Web服务类,使用SqlDataAdapter
和TransactionScope
。
每个测试将10,000条记录插入到包含6个字段的Items
表中。测试通过修改以下参数来运行:
- 批量大小:一次发送到数据访问层的记录数。10,000条记录被分成大小相等的批次,每个批次单独发送到数据访问层。
- 适配器批量大小:根据.NET文档,
UpdateBatchSize
属性用于获取或设置每次往返服务器时处理的行数。 - 线程数:向数据访问层发送数据的并发线程数。
本项目试图回答以下问题:
- 性能如何依赖于上述参数?
- 哪种数据访问层实现性能最好?
代码描述
项目
解决方案UpdatePerformance.sln包含3个项目:
UpdatePerformace.UI
:提供用户界面。- Form1.vb:用于定义和运行测试用例的Windows窗体。
- DBAccess.vb:不同数据访问层的封装类。
在构造函数中获取要使用的数据访问层的类型。
- ItemsData.xsd:“
Items
”表的类型化DataSet
。 - ModSettings.vb:用于将控件值保存到用户设置并从用户设置加载的代码。
- ModSql.vb:一些辅助函数,主要用于创建SQL语句。
- app.config:定义数据库连接字符串以及Web服务的访问。
UpdatePerformance.DA
:提供数据访问层实现。- IInit.vb:定义
Init
方法的接口,该方法由所有数据访问层实现。Init
方法用于传递连接字符串。 SQLDataAccess
:不使用事务的类,使用SqlDataAdapter
。SqlDataAccessTrans
:使用事务的类,使用SqlDataAdapter
和SqlTransaction
。SqlDataAccessComPlus
:使用事务的COM+类,使用SqlDataAdapter
和ContextUtil
。
- IInit.vb:定义
UpdatePerformance.WS
:提供一个具有数据访问方法的Web服务。- ISqlDataAccessWS.vb:Web服务实现的接口。
- SqlDataAccessWS.vb:使用
SqlDataAdapter
和TransactionScope
的Web服务类。 - App.config:Web服务的配置。
当Web服务部署到IIS时,该文件会自动重命名为web.config。
并行化
RunTest
RunTest
方法为给定的批量大小构建多个DataSets
并用随机数填充它们。
ReDim mDataSets(nrDataRows \ saveBatchSize - 1)
For j As Integer = 0 To UBound(mDataSets)
mDataSets(j) = New ItemsData
AddRandoms(mDataSets(j), saveBatchSize)
Next
调用RunParallelTest
方法,该方法将并行方法(ParallelFor
或ParallelThreads
)和线程数作为参数。
RunParallelTest
RunParallelTest
方法启动秒表,调用并行方法(ParallelFor
或ParallelThreads
)并停止秒表。
mNrThreads = nrThreads
mStopWatch.Restart()
m.Invoke(Me, New Object() {nrThreads})
mStopWatch.Stop()
ParallelFor
ParallelFor
方法使用.NET的Parallel.For
方法启动指定数量的线程。每个线程运行RunTestLoop
方法。
Dim p As New ParallelOptions() With {.MaxDegreeOfParallelism = nrThreads}
Parallel.For(0, nrThreads, p, AddressOf RunTestLoop)
ParallelThreads
Parallelthreads
方法创建指定数量的线程,启动线程并等待所有线程完成。每个线程运行RunTestLoop
方法。
Dim threads(nrThreads - 1) As Threading.Thread
For i = 0 To UBound(threads)
threads(i) = New Threading.Thread(AddressOf RunTestLoop)
Next
For i = 0 To UBound(threads)
threads(i).Start(i)
Next
For i = 0 To UBound(threads)
threads(i).Join()
Next
RunTestLoop
RunTestLoop
方法执行将DataSets
发送到数据库的实际工作。如果有5个线程,第一个线程会依次获取第1、6、11个批次,第二个线程会依次获取第2、7、12个批次,其余线程也以类似方式工作。
While Not mCancel AndAlso batchNr < mDataSets.Length
Dim ds As DataSet = db.Update(mDataSets(batchNr), _
mDataSets(batchNr).Tables(0).TableName, mAdapterBatchSize)
batchNr += mNrThreads
End While
必备组件
本项目已在Visual Studio 2010和SQL Server 2008 R2上进行了测试。这些产品的更高版本也应该可以工作。必须存在一个SQL Server数据库,并且其用户是db_datareader
、db_datawriter
和db_ddladmin
角色的成员。
编译和启动
- 在Visual Studio中,打开解决方案UpdatePerformance.sln。
- 在UpdatePerformance.UI/app.config/ConnectionStrings/DB1中,将
connectionString
属性设置为一个现有的SQL Server数据库。 - 右键单击解决方案,然后单击“生成解决方案”。
- 右键单击项目
UpdatePerformance.WS
,然后单击“发布”。将出现以下窗体:单击“发布”按钮,Web服务将被部署到本地IIS。在浏览器中访问URL https:///UpdatePerformance.WS/UpdatePerformance.WS.SqlDataAccessWS.svc,以验证Web服务是否正常运行。
- 启动可执行文件UI/bin/Release/UpdatePerformance.UI.exe。
测试窗体
启动可执行文件后,主窗体将出现,如文章开头所示。
字段说明
- 保存批量大小步长:保存批量大小将以此值递增,直到下一个总行数除数。例如:总行数=100,保存批量大小步长=1,则批量大小将取1、2、4、5、10、20、25、50、100。如果保存批量大小步长=2,则批量大小将取2、4、10、20、50、100。
- 适配器批量大小:不同的
SqlDataAdapter.UpdateBatchSize
值,用“;”分隔。例如:1;10;100 - 线程数:线程数将从1开始迭代加倍,直到此值。例如,对于值为8,测试将分别有1、2、4和8个线程。
- 总行数:每次测试要保存的行数。这些行将由不同的线程分批保存。
- 并行方法:要使用的并行化方法,.NET的
Parallel.For
或创建线程。 - 数据访问类:用于保存的数据对象。
首先要做的是在数据库中创建“Items
”表。
这可以通过选择菜单“文件/重新创建表”来完成。“从数据库加载
”按钮从“Items”表中检索数据并将其显示在网格上。“清空数据库
”按钮会清空“Items
”表,这意味着所有数据都将被删除。窗体上的所有值在窗体关闭时将保存到用户设置,并在下次打开窗体时从用户设置加载。通过按“重置”按钮,窗体上的所有值将恢复到原始设置。
为窗体上的字段选择一些值。例如,要重现下面描述的测试用例,请设置以下值:
- 保存批量大小步长:
1
- 适配器批量大小:
1;10;100
- 线程数:
256
- 总行数:
10000
- 并行方法:
ParallelFor
- 数据访问类:
全选
要准备测试用例,请按“准备
”按钮。这将把不同的测试用例显示在网格上,如下图所示:
按“运行
”按钮后,测试用例将逐行开始执行,从第一列开始。完成所有行后,执行将进入下一列。经过的时间将在每个测试用例的相应单元格中显示,精确到毫秒。
使用“暂停
”按钮可以暂停执行,使用“恢复
”按钮可以恢复执行。日志记录使用System.Trace.WriteLine()
。日志输出可以通过DebugView.exe查看。
结果讨论
测试用例在具有Intel Core 2 Quad CPU、2.4GHz、3GB RAM的计算机上运行。表示层、数据访问层、Web服务和数据库都在同一台计算机上运行。
运行了以下测试用例:
1 | ParallelFor | UpdatePerformance.DA.SQLDataAccess | adapter batch size=1 |
2 | ParallelFor | UpdatePerformance.DA.SQLDataAccess | adapter batch size=10 |
3 | ParallelFor | UpdatePerformance.DA.SQLDataAccess | adapter batch size=100 |
4 | ParallelFor | UpdatePerformance.DA.SqlDataAccessTrans | adapter batch size=1 |
5 | ParallelFor | UpdatePerformance.DA.SqlDataAccessTrans | adapter batch size=10 |
6 | ParallelFor | UpdatePerformance.DA.SqlDataAccessTrans | adapter batch size=100 |
7 | ParallelFor | UpdatePerformance.DA.SqlDataAccessComPlus | adapter batch size=1 |
8 | ParallelFor | UpdatePerformance.DA.SqlDataAccessComPlus | adapter batch size=10 |
9 | ParallelFor | UpdatePerformance.DA.SqlDataAccessComPlus | adapter batch size=100 |
10 | ParallelFor | UpdatePerformance.UI.PX.Service1Client | adapter batch size=1 |
11 | ParallelFor | UpdatePerformance.UI.PX.Service1Client | adapter batch size=10 |
12 | ParallelFor | UpdatePerformance.UI.PX.Service1Client | adapter batch size=100 |
结果保存在Excel文件UpdatePerformance.zip中。
以下总体图表显示了在减少变化的批量大小和线程数下的性能。
线条颜色对应于上表中行的颜色。
可以从上述图表中得出以下结论:
- 批量大小为1时,Web服务的速度最慢,其次是COM+类。在许多应用程序中,用户一次插入一条记录,或者一个进程一次处理一条消息并将其插入数据库,都会出现这种情况。
- 从批量大小为10开始,性能得到了显著提高。
- 没有事务的简单类(
SQLDataAccess
)性能不稳定。它在实际应用程序中并不常用,因此不再进一步讨论。 - 尚不清楚性能如何依赖于线程数。这一点将进一步研究。
为了研究线程数的影响,我们需要更仔细地查看一些特定的批量大小。
接下来的三个图表分别显示了批量大小为10、100和1000的性能结果。
可以从上述图表中得出以下结论:
- 使用4个线程时的性能最佳。
- 使用1或2个线程以及32个或更多线程时,性能会下降。
SqlDataAccessTrans
获得了最佳性能,其次是SqlDataAccessComPlus
,最后是SqlDataAccessWS
。- 较高的适配器批量大小对性能的影响几乎可以忽略不计。
总体结论
批量大小约为10条记录或更多,然后更新整个批量是值得的。4到32个线程之间的性能良好,但这肯定取决于可用的核心数和内存。适配器批量大小对性能影响很小。
历史
- 测量不同数据批量大小、适配器批量大小和线程数将数据插入表的性能。