English 中文(简体)
如何将慢速参数化插入更改为快速批量复制(甚至从内存)
原标题:
  • 时间:2008-09-24 13:34:44
  •  标签:

我的代码中有这样的东西(.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秒。大约有50万条记录。桌子之前是空的。具有类似功能的SSIS数据流任务大约需要20秒。

  • Bulk Insert was not an option (see below). I did some fancy stuff during this import.
  • My test machine is Core 2 Duo with 2 GB RAM.
  • When looking in Task Manager CPU was not fully untilized. IO seemed also not to be fully utilized.
  • Schema is simple like hell: one table with AutoInt as primary index and less than 10 ints, tiny ints and chars(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();
最佳回答

请尝试使用SqlBulkCopy类,以便一次大容量插入所有记录。

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

问题回答

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

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

对于50万张唱片来说,1分钟听起来相当合理。这是每0.00012秒的记录。

这张表有索引吗?如果可以选择的话,删除这些并在大容量插入后重新应用它们将提高插入的性能。

对我来说,每秒处理8333条记录似乎并不不合理。。。您期望的吞吐量是多少?

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

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

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

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

如果你可以将工作分解,也许可以使用某种形式的生产者/消费者设置,你可能会发现你可以更多地利用所有资源。

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

SSIS将很快,因为它使用的是大容量插入方法——首先完成所有复杂的处理,生成要插入的数据的最终列表,同时将所有数据进行大容量插入。

我假设大约58秒的时间是50万条记录的物理插入,所以每秒大约有10000条插入。在不知道数据库服务器机器规格的情况下(我看到您使用的是localhost,所以网络延迟不应该是个问题),很难说这是好的、坏的还是糟糕的。

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

先对数据和所有记录做一些花哨的处理。然后批量插入。

(因为你不是在插入后进行选择。我认为在BulkInsert之前对数据应用所有操作没有问题

如果非要我猜测的话,我首先要查找的是tbTrafficLogTTL表上的索引太多或类型错误。如果不看表的模式定义,我真的不能说,但我在以下情况下也遇到过类似的性能问题:

  1. The primary key is a GUID and the primary index is CLUSTERED.
  2. There s some sort of UNIQUE index on a set of fields.
  3. There are too many indexes on the table.

当您开始对50万行数据进行索引时,创建和维护索引所花费的时间就会增加。

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

我在上一份合同中也遇到了类似的问题。您正在进行500000次SQL访问以插入数据。为了显著提高性能,您需要研究SQL命名空间中的BulkInsert方法。我有“重新加载”过程,从2个多小时恢复几十个表,到实现批量导入后的31秒。

最好使用类似bcp命令的方法来实现这一点。如果这不可用,那么上面关于使用BULK INSERT的建议是最好的选择。您将对数据库进行500000次往返访问,并向日志文件写入500000个条目,更不用说需要分配给日志文件、表和索引的任何空间了。

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

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

BULK INSERT=来自权限的bcp

You could batch the INSERTs to reduce roundtrips SQLDataAdaptor.UpdateBatchSize = 10000 gives 50 round trips

尽管如此,您仍有50万个插件。。。

文章

MSDN





相关问题
热门标签