使用大量数据时插入速度慢(SQL Server/C#)

使用大量数据时插入速度慢(SQL Server/C#),c#,sql-server,C#,Sql Server,我正在使用实时数字化波形的电子设备(每个设备每秒生成约1000个512字节的阵列,我们有12个设备)。我已经用C语言为这些设备编写了一个客户端,在大多数情况下工作正常,没有性能问题 但是,应用程序的一个要求是存档,Microsoft SQL Server 2010被强制作为存储机制(我无法控制)。数据库布局非常简单:每天每个设备有一个表(“Archive\u Dev02\u 20131015”等)。每个表都有一个Id列、一个timestamp列、一个Data列(varbinary)和另外20个包

我正在使用实时数字化波形的电子设备(每个设备每秒生成约1000个512字节的阵列,我们有12个设备)。我已经用C语言为这些设备编写了一个客户端,在大多数情况下工作正常,没有性能问题

但是,应用程序的一个要求是存档,Microsoft SQL Server 2010被强制作为存储机制(我无法控制)。数据库布局非常简单:每天每个设备有一个表(“Archive\u Dev02\u 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个事务,带有一个生成insert语句并执行它们的循环

这个方法非常非常慢。在我的机器上(i5-2400@3.1GHz,8GB RAM,使用.NET 4.0和SQL Server 2008,镜像中有两个内部HDs,所有东西都在本地运行),保存两个设备的数据大约需要2,5秒,因此每5秒保存12个设备是不可能的

相比之下,我编写了一个小的SQL脚本(实际上我提取了使用SQL server profiler运行的代码),它直接在服务器上执行相同的操作(仍在我自己的机器上运行):

打开统计信息io
去
开始交易
去
声明@i int=0;
当@i<24500开始时
设置@i=@i+1
exec sp_executesql N'USE[数据库名称]
插入[dbo]。[Archive\u Dev02\u 20131015]
(                                                                                      
[时间戳],
[数据],
[int1],
...                                                    
[int20]
)                                                                                      
价值观
(                                                                                      
@时间戳,
@数据,
@压缩的,
@int1,
...                                                                  
@int20,
)“,N'@timestamp datetime,@data binary(118),@int1 int,…,@int20 int,”,
@时间戳='2013-10-1414:31:12.023',
@数据=0xECBD07601C4996252626F6DCA7B7F4AF54AD7E074A108080601324D8904010ECC188CDE692EC1D69472329AB2A81CA6556655D661640CCED9DBCF7DE7BEFB7DE7BEFB7BA3B9D4E27F7DFFF3F5C66640CF6CE4ADAC99E2180AAC81F3F7E7CF7C1F22FEF53477FFF5FFF5F5F5BF1FF04000FFFF,
@int=0,
...
@int20=0
结束
提交事务
这是(依我看,但我可能错了;))同样的事情,只是这次我使用24500次迭代,一次模拟12个设备。查询大约需要2秒钟。如果我使用与C#版本相同的迭代次数,查询将在不到一秒钟的时间内运行

所以我的第一个问题是:为什么它在SQL server上的运行速度比在C#上快得多?这与连接(本地tcp)有关吗

(对我来说)这段代码在生产服务器上的运行速度是原来的两倍(IBM bladecenter、32GB ram、到SAN的光纤连接……文件系统操作非常快)。我试过查看sql活动监视器,写入性能从未超过2MB/秒,但这可能是正常的。我对sql server是一个完全的新手(事实上,与称职的DBA相反)


关于如何使C#代码的性能更高,有什么想法吗?

到目前为止,加载此类数据的最佳方法是使用表值参数和接收数据的存储过程。表类型和使用它的过程的一个非常简单的示例是:

CREATE TYPE [dbo].[StringTable]
AS TABLE ([Value] [nvarchar] (MAX) NOT NULL)
GO

CREATE PROCEDURE [dbo].[InsertStrings]
  @Paths [dbo].[StringTable] READONLY
AS
INSERT INTO [dbo].[MyTable] ([Value])
SELECT [Value] FROM @Paths
GO
那么C#代码应该是这样的(请记住,我已经在S/O编辑器中输入了这个代码,所以可能会有打字错误):

私有静态IEnumerable TransformStringList(ICollection源)
{
if(source==null | | source.Count==0)
{
返回null;
}
返回GetRecords(源,
()=>新的SqlDataRecord(新的SqlMetaData(“Value”,SqlDbType.NVarChar,-1)),
(记录,值)=>record.SetString(0,值));
}
私有静态IEnumerable GetRecords(IEnumerable源、函数工厂、操作或)
{
SqlDataRecord=factory();
foreach(源中的var值)
{
水合器(数据记录、数值);
收益返回数据记录;
}
}
私有InsertStrings(ICollection字符串、SqlConnection连接)
{
使用(var transaction=connection.BeginTransaction())
{
使用(var cmd=newsqlcommand(“dbo.InsertStrings”))
{
cmd.Connection=连接;
cmd.Transaction=Transaction;
cmd.CommandType=CommandType.storedProcess;
cmd.Parameters.Add(新的sqlparameter
CREATE TYPE [dbo].[StringTable]
AS TABLE ([Value] [nvarchar] (MAX) NOT NULL)
GO

CREATE PROCEDURE [dbo].[InsertStrings]
  @Paths [dbo].[StringTable] READONLY
AS
INSERT INTO [dbo].[MyTable] ([Value])
SELECT [Value] FROM @Paths
GO
private static IEnumerable<SqlDataRecord> TransformStringList(ICollection<string> source)
{
     if (source == null || source.Count == 0)
     {
         return null;
     }
     return GetRecords(source, 
                       () => new SqlDataRecord(new SqlMetaData("Value", SqlDbType.NVarChar, -1)), 
                       (record, value) => record.SetString(0, value));
}

private static IEnumerable<SqlDataRecord> GetRecords<T>(IEnumerable<T> source, Func<SqlDataRecord> factory, Action<SqlDataRecord, T> hydrator)
{
    SqlDataRecord dataRecord = factory();
    foreach (var value in source)
    {
        hydrator(dataRecord, value);
        yield return dataRecord;
    }
}

private InsertStrings(ICollection<string> strings, SqlConnection connection)
{
    using (var transaction = connection.BeginTransaction())
    {
        using (var cmd = new SqlCommand("dbo.InsertStrings"))
        {
            cmd.Connection = connection;
            cmd.Transaction = transaction;
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add(new SqlParameter("@Paths", SqlDbType.Structured) { Value = TransformStringList(strings) };
            cmd.ExecuteNonQuery();
        }
    }
}
CREATE TYPE [dbo].[ArchiveData]
AS TABLE (
    [Timestamp] [DateTime] NOT NULL,
    [Data] [VarBinary](MAX) NOT NULL,
    [IntField1] [Int] NOT NULL,
    [...] [Int] NOT NULL,
    [IntField20] NOT NULL)
GO
// The insert sql statement.
string sql =
@"INSERT INTO [dbo].[Archive_Dev02_20131015] (
    [Timestamp],
    [Data],
    [IntField1],
    [...],                         
    [IntField20])
 SELECT * FROM @data;";

using (SqlCommand cmd = new SqlCommand(sql))
{
    using (SqlTransaction transaction = connection.BeginTransaction()
    {
        cmd.Connection = connection;
        cmd.Transaction = transaction;
        cmd.Parameters.Add(new SqlParameter("@data", SqlDbType.Structured)
            {
                Value = TransformSamples(samples);
            });

        int affected = cmd.ExecuteNonQuery();
        transaction.Commit();
    }
}

...

private static IEnumerable<SqlDataRecord> TransformSamples(
        {YourSampleType} samples)
{
    var schema = new[]
    {
        new SqlMetaData("Timestamp", SqlDbType.DateTime),
        new SqlMetaData("Timestamp", SqlDbType.VarBinary, -1),
        new SqlMetaData("IntField1", SqlDbType.Int),
        new SqlMetaData("...", SqlDbType.Int),
        new SqlMetaData("IntField20", SqlDbType.Int)
    };

    foreach (var sample in samples)
    {
        var row = new SqlDataRecord(schema);
        row.SetSqlDate(0, sample.ReceiveDate);
        row.SetSqlBinary(1, sample.Data);
        row.SetSqlInt(2, sample.Data.Length);
        row.SetSqlInt(..., ...);
        row.SetSqlInt(24, sample.IntValue19);
        yield return row;
    }
}