如何将慢参数化插入更改为快速批量复制(甚至从内存中)

时间:2008-09-24 13:34:45

标签: sql-server-2005 insert copy performance bulk

我的代码中有这样的东西(.Net 2.0,MS SQL)

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();

使用 cmdInsert.ExecuteNonQuery()注释掉此代码的执行时间不到2秒。使用SQL执行需要1分20秒。有大约0.5百万的记录。表之前已清空。类似功能的SSIS数据流任务大约需要20秒。

  • 批量插入 不是一个选项(见下文)。在导入过程中我做了一些奇特的事情。
  • 我的测试机器是Core 2 Duo,内存为2 GB。
  • 查看任务管理器时,CPU尚未完全直至。 IO似乎也没有得到充分利用。
  • Schema很简单,就像地狱一样:一个表以AutoInt作为主要索引,少于10个整数,微小的整数和字符(10)。

在这里得到一些答案后,我发现可以从内存中执行批量复制!我拒绝使用批量复制,因为我认为必须从文件中完成...

现在我使用它,它需要20秒(如SSIS任务)

  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();

12 个答案:

答案 0 :(得分:9)

请尝试使用SqlBulkCopy类一次批量插入所有记录,而不是单独插入每条记录。

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

答案 1 :(得分:3)

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

此外,如果您确定插入的值是corect,则可以使用BulkInsert。

答案 2 :(得分:2)

对于50万条记录,1分钟听起来很合理。这是每0.00012秒的记录。

表中是否有任何索引?如果可以选择,在批量插入后删除这些并重新应用它们可以提高插入的性能。

答案 3 :(得分:1)

对我来说,每秒处理8,333条记录似乎并不合理......您期望达到什么样的吞吐量?

答案 4 :(得分:1)

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

http://msdn.microsoft.com/en-us/library/ms188365.aspx

答案 5 :(得分:1)

如果某种形式的批量插入不是一个选项,另一种方式是多个线程,每个线程都有自己与数据库的连接。

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

如果您可以分割作业,可能使用某种形式的生产者/消费者设置,您可能会发现可以更多地利用所有资源。

然而,要做到这一点,你将不得不失去一个伟大的事务 - 否则第一个作者线程将阻止所有其他事务直到其事务完成。你仍然可以使用交易,但你必须使用很多小的而不是1个大的。

SSIS将很快,因为它使用批量插入方法 - 首先执行所有复杂的处理,生成要插入的最终数据列表,并同时将其全部放入批量插入。

答案 6 :(得分:0)

我认为花费大约58秒的时间是物理插入500,000条记录 - 所以每秒大约需要10,000次插入。在不知道数据库服务器机器的规格的情况下(我发现你使用的是localhost,因此网络延迟不应该成为问题),很难说这是好的,坏的还是糟糕的。

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

答案 7 :(得分:0)

首先在所有记录上对数据做一些奇特的事情。然后批量插入它们。

(因为你在插入后没有做选择..我没有看到在BulkInsert之前对数据应用所有操作的问题

答案 8 :(得分:0)

如果我不得不猜测,我要寻找的第一件事是tbTrafficLogTTL表上的索引太多或错误。在没有查看表的模式定义的情况下,我不能说,但在以下情况下我遇到了类似的性能问题:

  1. 主键是GUID,主索引是CLUSTERED。
  2. 在一组字段上有某种UNIQUE索引。
  3. 桌子上有太多索引。
  4. 当您开始索引50万行数据时,创建和维护索引所花费的时间就会增加。

    我还要注意,如果您有任何选项可以将年,月,日,小时,分钟,第二个字段转换为单个datetime2或时间戳字段,您应该这样做。您为数据架构增加了很多复杂性,但没有任何好处。我甚至考虑使用像这样的分割字段结构的唯一原因是,如果您正在处理由于任何原因无法更改的预先存在的数据库模式。在这种情况下,它很难成为你。

答案 9 :(得分:0)

我上次的合同中遇到了类似的问题。您正在进行500,000次SQL访问以插入数据。为了显着提高性能,您需要调查SQL命名空间中的BulkInsert方法。我实施批量导入后,我已经“重新加载”了2个多小时的过程,将几十个表恢复到31秒。

答案 10 :(得分:0)

最好使用类似bcp命令的方法来完成。如果没有,那么上面关于使用BULK INSERT的建议是最好的选择。您正在向数据库进行500,000次往返,并将500,000个条目写入日志文件,更不用说需要分配给日志文件,表和索引的任何空间。

如果您插入的命令与聚簇索引不同,则还必须处理重新组织磁盘上物理数据所需的时间。这里有很多变量可能会使你的查询运行速度比你想要的慢。

对于从代码/

往返的单个插入,每秒约10,000次事务并不可怕

答案 11 :(得分:0)

BULK INSERT =来自权限的bcp

您可以批量插入INSERT以减少往返 SQLDataAdaptor.UpdateBatchSize = 10000提供50次往返

你仍然有500k插入......

Article

MSDN