Sql server 2005 如何将慢速参数化插入更改为快速大容量复制(甚至从内存)

Sql server 2005 如何将慢速参数化插入更改为快速大容量复制(甚至从内存),sql-server-2005,insert,copy,performance,bulk,Sql Server 2005,Insert,Copy,Performance,Bulk,我的代码中有类似的内容(.NET2.0,MS SQL) 当cmdInsert.ExecuteOnQuery()注释掉时,此代码将在不到2秒内执行。执行SQL需要1米20秒。大约有50万条记录。桌子以前是空的。类似功能的SSIS数据流任务大约需要20秒 批量插入不是一个选项(见下文)。在这次进口中,我做了一些奇特的事情 我的测试机器是Core2Duo,带有2GB内存 在任务管理器中查找时,CPU未完全解锁。IO似乎也没有得到充分利用 模式非常简单:一个表的主索引是AutoInt,小于10个整数、

我的代码中有类似的内容(.NET2.0,MS SQL)

当cmdInsert.ExecuteOnQuery()注释掉时,此代码将在不到2秒内执行。执行SQL需要1米20秒。大约有50万条记录。桌子以前是空的。类似功能的SSIS数据流任务大约需要20秒

  • 批量插入不是一个选项(见下文)。在这次进口中,我做了一些奇特的事情
  • 我的测试机器是Core2Duo,带有2GB内存
  • 在任务管理器中查找时,CPU未完全解锁。IO似乎也没有得到充分利用
  • 模式非常简单:一个表的主索引是AutoInt,小于10个整数、极小的整数和字符(10)
在这里回答了一些问题后,我发现可以从内存执行批量复制!我拒绝使用批量复制,因为我认为它必须从文件中完成

现在我使用它,大约需要20秒(就像SSIS任务一样)


交易是否需要?使用事务需要比简单命令多得多的资源


另外,如果您确信插入的值是正确的,那么您可以使用BulkInsert。

对于50万条记录来说,1分钟听起来非常合理。这是每0.00012秒一次的记录


这个表有索引吗?删除这些并在批量插入之后再应用它们将提高插入的性能,如果这是一个选项。

对我来说处理每秒8333条记录是不合理的……您期望什么样的吞吐量?

如果您需要更好的速度,可以考虑实现批量插入:


我假设大约需要58秒的时间来物理插入500000条记录,因此每秒大约插入10000条记录。如果不知道数据库服务器机器的规格(我知道您使用的是localhost,所以网络延迟不应该是问题),很难说这是好的、坏的还是糟糕的


我想看看您的数据库模式——表上是否有一组索引必须在每次插入后更新?这可能来自其他表,其中外键引用了您正在处理的表。SQL Server中内置了SQL分析工具和性能监视工具,但我从未使用过它们。但是它们可能会出现锁之类的问题。

首先在所有记录上对数据进行处理。然后批量插入它们


(因为你没有在插入后进行选择。)我不认为在BulkInsert之前对数据应用所有操作有什么问题,如果我不得不猜测的话,我首先要查找的是tbTrafficLogTTL表上的索引太多或类型错误。如果不查看表的模式定义,我真的不能说,但我ave在以下情况下遇到类似的性能问题:

  • 主键是GUID,主索引是聚集的
  • 在一组字段上有某种唯一的索引
  • 表上的索引太多
  • 当您开始索引50万行数据时,创建和维护索引所花费的时间就加起来了


    我还将注意到,如果您可以选择将年、月、日、小时、分钟、秒字段转换为单个datetime2或时间戳字段,那么您应该这样做。您给数据体系结构增加了很多复杂性,但没有任何好处。我甚至会考虑使用这样的拆分字段结构的唯一原因是,如果您正在处理由于任何原因都无法更改的预先存在的数据库架构。在这种情况下,您很糟糕。

    我在上一份合同中遇到了类似的问题。您要使用500000次SQL来插入数据。为了显著提高性能,您需要研究SQL命名空间中的BulkInsert方法。我有“reload”在我实现大容量导入后,从2个多小时恢复几十个表到31秒的过程。

    这可以最好地使用bcp命令之类的命令来完成。如果不可用,上面关于使用大容量插入的建议是最好的选择。您将进行500000次数据库往返和写操作将500000个条目添加到日志文件中,更不用说需要分配给日志文件、表和索引的任何空间了

    如果插入的顺序与聚集索引不同,则还必须处理重新组织磁盘上的物理数据所需的时间。此处有许多变量可能会使查询运行速度比您希望的慢


    每秒10000个事务对于从代码往返的单个插入来说并不可怕/

    与其单独插入每条记录,不如尝试使用该类一次性批量插入所有记录


    创建一个DataTable并将所有记录添加到DataTable中,然后使用。一次大容量插入所有数据。

    如果不能选择某种形式的大容量插入,另一种方法是多个线程,每个线程都有自己的数据库连接

    当前系统的问题是,您有500000次到数据库的往返,并且正在等待第一次往返完成,然后再开始下一次-任何类型的延迟(即机器之间的网络)都意味着您的大部分时间都花在等待上

    如果你能把工作分成几个部分,也许可以使用某种形式的生产者/消费者设置,你可能会发现你可以更充分地利用所有资源

    但是,要做到这一点,您必须丢失一个伟大的事务,否则第一个writer线程将阻止所有其他线程,直到其事务完成。您仍然可以使用事务,但您必须使用许多小事务,而不是一个大事务

    SSI将很快,因为它使用批量插入方法
    SqlConnection connection = new SqlConnection(@"Data Source=localhost;Initial
    Catalog=DataBase;Integrated Security=True");
      connection.Open();
    
      SqlCommand cmdInsert = connection.CreateCommand();
      SqlTransaction sqlTran = connection.BeginTransaction();
      cmdInsert.Transaction = sqlTran;
    
      cmdInsert.CommandText =
         @"INSERT INTO MyDestinationTable" +
          "(Year, Month, Day, Hour,  ...) " +
          "VALUES " +
          "(@Year, @Month, @Day, @Hour, ...) ";
    
      cmdInsert.Parameters.Add("@Year", SqlDbType.SmallInt);
      cmdInsert.Parameters.Add("@Month", SqlDbType.TinyInt);
      cmdInsert.Parameters.Add("@Day", SqlDbType.TinyInt);
      // more fields here
      cmdInsert.Prepare();
    
      Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
    
      StreamReader reader = new StreamReader(stream);
      char[] delimeter = new char[] {' '};
      String[] records;
      while (!reader.EndOfStream)
      {
        records = reader.ReadLine().Split(delimeter, StringSplitOptions.None);
    
        cmdInsert.Parameters["@Year"].Value = Int32.Parse(records[0].Substring(0, 4));
        cmdInsert.Parameters["@Month"].Value = Int32.Parse(records[0].Substring(5, 2));
        cmdInsert.Parameters["@Day"].Value = Int32.Parse(records[0].Substring(8, 2));
        // more here complicated stuff here
        cmdInsert.ExecuteNonQuery()
      }
      sqlTran.Commit();
      connection.Close();
    
      DataTable dataTable = new DataTable();
    
      dataTable.Columns.Add(new DataColumn("ixMyIndex", System.Type.GetType("System.Int32")));   
      dataTable.Columns.Add(new DataColumn("Year", System.Type.GetType("System.Int32")));   
      dataTable.Columns.Add(new DataColumn("Month", System.Type.GetType("System.Int32")));
      dataTable.Columns.Add(new DataColumn("Day", System.Type.GetType("System.Int32")));
     // ... and more to go
    
      DataRow dataRow;
      object[] objectRow = new object[dataTable.Columns.Count];
    
      Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
    
      StreamReader reader = new StreamReader(stream);
      char[] delimeter = new char[] { ' ' };
      String[] records;
      int recordCount = 0;
      while (!reader.EndOfStream)
      {
        records = reader.ReadLine().Split(delimeter, StringSplitOptions.None);
    
        dataRow = dataTable.NewRow();
        objectRow[0] = null; 
        objectRow[1] = Int32.Parse(records[0].Substring(0, 4));
        objectRow[2] = Int32.Parse(records[0].Substring(5, 2));
        objectRow[3] = Int32.Parse(records[0].Substring(8, 2));
        // my fancy stuf goes here
    
        dataRow.ItemArray = objectRow;         
        dataTable.Rows.Add(dataRow);
    
        recordCount++;
      }
    
      SqlBulkCopy bulkTask = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, null);
      bulkTask.DestinationTableName = "MyDestinationTable"; 
      bulkTask.BatchSize = dataTable.Rows.Count;
      bulkTask.WriteToServer(dataTable);
      bulkTask.Close();