【发布时间】:2013-10-15 20:33:16
【问题描述】:
我正在使用实时数字化波形的电子设备(每个设备每秒生成大约 1000 512 字节数组 - 我们有 12 个设备)。我已经用 C# 为这些设备编写了一个客户端,它在大多数情况下都可以正常工作并且没有性能问题。
但是,应用程序的要求之一是存档,并且 Microsoft SQL Server 2010 被强制用作存储机制(我无法控制)。数据库布局非常简单:每天每台设备有一个表(“Archive_Dev02_20131015”等)。每个表都有一个Id 列、一个timestamp 列、一个Data 列(varbinary) 和另外20 个带有一些元数据的整数列。 Id 和 timestamp 上有一个聚集主键,timestamp 上有另一个单独的索引。我幼稚的方法是将客户端应用程序中的所有数据排队,然后使用SqlCommand 每隔 5 秒将所有数据插入数据库。
基本机制如下所示:
using (SqlTransaction transaction = connection.BeginTransaction()
{
//Beginning of the insert sql statement...
string sql = "USE [DatabaseName]\r\n" +
"INSERT INTO [dbo].[Archive_Dev02_20131015]\r\n" +
"(\r\n" +
" [Timestamp], \r\n" +
" [Data], \r\n" +
" [IntField1], \r\n" +
" [...], \r\n" +
") \r\n" +
"VALUES \r\n" +
"(\r\n" +
" @timestamp, \r\n" +
" @data, \r\n" +
" @int1, \r\n" +
" @..., \r\n" +
")";
using (SqlCommand cmd = new SqlCommand(sql))
{
cmd.Connection = connection;
cmd.Transaction = transaction;
cmd.Parameters.Add("@timestamp", System.Data.SqlDbType.DateTime);
cmd.Parameters.Add("@data", System.Data.SqlDbType.Binary);
cmd.Parameters.Add("@int1", System.Data.SqlDbType.Int);
foreach (var sample in samples)
{
cmd.Parameters[0].Value = amples.ReceiveDate;
cmd.Parameters[1].Value = samples.Data; //Data is a byte array
cmd.Parameters[1].Size = samples.Data.Length;
cmd.Parameters[2].Value = sample.IntValue1;
...
int affected = cmd.ExecuteNonQuery();
if (affected != 1)
{
throw new Exception("Could not insert sample into the database!");
}
}
}
}
transaction.Commit();
}
总结一下:一批 1 个事务,带有一个生成插入语句并执行它们的循环。
结果证明这种方法非常非常慢。在我的机器上(i5-2400 @ 3.1GHz,8GB RAM,使用 .NET 4.0 和 SQL Server 2008,镜像中有 2 个内部 HD,一切都在本地运行),从 2 个设备保存数据大约需要 2.5 秒,所以每 5 秒保存 12 台设备是不可能的。
为了比较,我编写了一个小的 SQL 脚本(实际上我提取了 C# 使用 sql server profiler 运行的代码),它直接在服务器上执行相同的操作(仍在我自己的机器上运行):
set statistics io on
go
begin transaction
go
declare @i int = 0;
while @i < 24500 begin
SET @i = @i + 1
exec sp_executesql N'USE [DatabaseName]
INSERT INTO [dbo].[Archive_Dev02_20131015]
(
[Timestamp],
[Data],
[int1],
...
[int20]
)
VALUES
(
@timestamp,
@data,
@compressed,
@int1,
...
@int20,
)',N'@timestamp datetime,@data binary(118),@int1 int,...,@int20 int,',
@timestamp='2013-10-14 14:31:12.023',
@data=0xECBD07601C499625262F6DCA7B7F4AF54AD7E074A10880601324D8904010ECC188CDE692EC1D69472329AB2A81CA6556655D661640CCED9DBCF7DE7BEFBDF7DE7BEFBDF7BA3B9D4E27F7DFFF3F5C6664016CF6CE4ADAC99E2180AAC81F3F7E7C1F3F22FEEF5FE347FFFDBFF5BF1FC6F3FF040000FFFF,
@int=0,
...
@int20=0
end
commit transaction
这确实(imo,但我可能错了;))同样的事情,只是这次我使用 24500 次迭代,一次模拟 12 台设备。查询大约需要 2 秒。如果我使用与 C# 版本相同的迭代次数,查询将在不到一秒的时间内运行。
所以我的第一个问题是:为什么它在 SQL Server 上比在 C# 上运行得更快?这和连接(本地tcp)有什么关系吗?
为了让事情变得更加混乱(对我来说),这段代码在生产服务器上的运行速度是原来的两倍(IBM Bladecenter、32GB 内存、与 SAN 的光纤连接……文件系统操作非常快)。我试过查看 sql 活动监视器,写入性能从未超过 2MB/秒,但这也可能是正常的。我是 sql server 的完全新手(实际上与称职的 DBA 截然相反)。
关于如何使 C# 代码更高效的任何想法?
【问题讨论】:
-
您是否尝试为每个命令删除
USE [DatabaseName]? -
大概您使用for循环或foreach循环来遍历每个设备?您可以尝试在单独的线程上运行并行 for 循环(假设您不关心设备运行的顺序)。
-
您可能想查看 SqlBulkCopy - msdn.microsoft.com/en-us/library/…
-
我会调查建立连接所花费的时间。 (你有方便的连接字符串吗)我也会研究 SQL Bulk insert 来聚合插入语句
-
您每秒生成 6 Mbits 的数据(512 字节 * 1000 * 12 个设备),加上开销等,您的 SQL Server 应该至少有 12 Mbit 的网络吞吐量。如果您只想每 5 秒插入 0.5 秒,那么您需要一个“管道”连接到您的 SQL Server,至少为 120 Mbit/Sec可用的网络带宽。 你有吗?请注意,您的 NIC 卡本身并不能确定这一点,它受到客户端和服务器之间最慢/负载最多的设备的限制。
标签: c# sql-server