3

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

  • 批量插入不是一种选择(见下文)。我在这次导入期间做了一些花哨的事情。
  • 我的测试机器是带有 2 GB RAM 的 Core 2 Duo。
  • 在任务管理器中查看 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();
4

12 回答 12

9

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

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

于 2008-09-24T14:43:56.123 回答
3

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

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

于 2008-09-24T13:38:10.913 回答
2

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

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

于 2008-09-24T13:38:24.360 回答
1

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

于 2008-09-24T13:40:29.147 回答
1

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

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

于 2008-09-24T13:41:03.060 回答
1

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

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

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

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

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

于 2008-09-24T14:54:54.163 回答
0

我假设大约需要 58 秒的是 500,000 条记录的物理插入 - 所以你每秒插入大约 10,000 条。在不知道您的数据库服务器机器的规格的情况下(我看到您使用的是 localhost,因此网络延迟应该不是问题),很难说这是好、坏还是糟糕透顶。

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

于 2008-09-24T13:41:21.420 回答
0

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

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

于 2008-09-24T13:53:33.233 回答
0

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

  1. 主键是 GUID,主索引是 CLUSTERED。
  2. 一组字段上有某种 UNIQUE 索引。
  3. 表上的索引太多。

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

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

于 2008-09-24T13:53:54.590 回答
0

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

于 2008-09-24T13:58:46.933 回答
0

这最好使用类似 bcp 命令的东西来完成。如果这不可用,上面关于使用 BULK INSERT 的建议是您最好的选择。您对数据库进行了 500,000 次往返,并将 500,000 个条目写入日志文件,更不用说需要为日志文件、表和索引分配的任何空间。

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

每秒约 10,000 个事务对于来自代码往返的单个插入来说并不可怕/

于 2008-09-24T14:19:00.473 回答
0

BULK INSERT = 来自权限的 bcp

您可以批量插入以减少往返 SQLDataAdaptor.UpdateBatchSize = 10000 给出 50 次往返

不过,您仍然有 500k 插入...

文章

MSDN

于 2008-10-14T18:56:10.980 回答