如何将缓慢的参数化插入更改为快速批量复制(甚至从内存中)
我的代码中有这样的内容(.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似乎也没有得到充分利用。
- 模式很简单,就像地狱一样:一个表,其中AutoInt作为主索引,并且少于10个int,tiny int和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();
解决方案
需要交易吗?使用事务比简单的命令需要更多的资源。
另外,如果确定插入的值是corect,则可以使用BulkInsert。
1分钟听起来很合理,可以记录50万条记录。这是每0.00012秒一次的记录。
该表有索引吗?如果可以的话,将其删除并在大容量插入之后重新应用它们可以提高插入的性能。
对我而言,每秒处理8,333条记录似乎不是不合理的……我们期望什么样的吞吐量?
如果需要更高的速度,则可以考虑实现批量插入:
http://msdn.microsoft.com/en-us/library/ms188365.aspx
我假设大约需要58秒的时间是物理插入500,000条记录,因此我们每秒获得大约10,000次插入。在不了解数据库服务器机器规格的情况下(我看到我们使用的是localhost,因此网络延迟不应该成为问题),很难说这是好是坏。
我看一下数据库架构,表上是否有一堆索引,每次插入后都必须更新?这可能来自具有外键的其他表,这些表引用了我们正在处理的表。 SQL Server中内置有SQL分析工具和性能监视工具,但我从未使用过它们。但是它们可能会出现诸如锁之类的问题。
首先在所有记录上对数据进行处理。然后将它们批量插入。
(因为插入后我们没有执行选择操作。。我看不到在BulkInsert之前对数据应用所有操作的问题
如果我不得不猜测,我会寻找的第一件事是tbTrafficLogTTL表上的索引太多或者类型错误。如果不查看表的架构定义,我无法真正说出,但是在以下情况下,我遇到了类似的性能问题:
- 主键是GUID,主索引是CLUSTERED。
- 在一组字段上存在某种UNIQUE索引。
- 表上的索引太多。
当开始为五十万行数据建立索引时,创建和维护索引所花费的时间加起来。
我还将注意到,如果我们可以将"年","月","天","小时","分钟","秒"字段转换为单个datetime2或者"时间戳"字段,则应该这样做。毫无疑问,我们为数据架构增加了很多复杂性。我什至打算使用这样的拆分字段结构的唯一原因是,如果我们要处理的是由于任何原因都无法更改的现有数据库模式。在这种情况下,成为我们真是太糟糕了。
我在上一份合同中遇到了类似的问题。我们要进行500,000次SQL插入记录。为了显着提高性能,我们想研究SQL名称空间中的BulkInsert方法。实现批量导入后,我的"重新加载"过程从2个多小时开始,将十几个表还原到31秒。
最好使用bcp命令之类的方法来完成。如果不可用,那么上面关于使用BULK INSERT的建议是最好的选择。我们要往返数据库500,000次,并将500,000个条目写入日志文件,更不用说需要分配给日志文件,表和索引的任何空间。
如果要插入的顺序与聚簇索引不同,则还必须处理重新组织磁盘上的物理数据所需的时间。这里有很多变量可能会使查询的运行速度比我们希望的慢。
对于来自代码/
而不是单独插入每个记录,请尝试使用SqlBulkCopy类一次批量插入所有记录。
创建一个DataTable并将所有记录添加到DataTable,然后使用SqlBulkCopy.WriteToServer一次批量插入所有数据。
如果某种形式的批量插入不是一种选择,那么另一种方式将是多个线程,每个线程都有自己的数据库连接。
当前系统的问题是我们有500,000次往返数据库的往返,并在开始下一次任何延迟(例如,机器之间的网络)之前等待第一次往返完成,这意味着大多数人花时间等待。
如果我们可以拆分工作(也许使用某种形式的生产者/消费者设置),则可能会发现可以充分利用所有资源。
但是,要做到这一点,我们将不得不丢失一个重要的事务,否则第一个编写器线程将阻塞所有其他事务,直到其事务完成为止。我们仍然可以使用事务,但是必须使用很多小事务,而不是1个大事务。
SSIS将很快,因为它使用批量插入方法首先执行所有复杂的处理,生成要插入的最终数据列表,然后将所有数据同时批量插入。
BULK INSERT =来自权限的bcp
我们可以批处理INSERT,以减少往返次数
SQLDataAdaptor.UpdateBatchSize = 10000进行50次往返
我们仍然有50万个插入内容...
文章
MSDN