您可以通过检查查询计划来了解索引是如何更新的。考虑这个只有非聚集索引的堆表。
CREATE TABLE dbo.BulkInsertTest(
Column1 int NOT NULL
, Column2 int NOT NULL
, Column3 int NOT NULL
, Column4 int NOT NULL
, Column5 int NOT NULL
);
CREATE INDEX BulkInsertTest_Column1 ON dbo.BulkInsertTest(Column1);
CREATE INDEX BulkInsertTest_Column2 ON dbo.BulkInsertTest(Column2);
CREATE INDEX BulkInsertTest_Column3 ON dbo.BulkInsertTest(Column3);
CREATE INDEX BulkInsertTest_Column4 ON dbo.BulkInsertTest(Column4);
CREATE INDEX BulkInsertTest_Column5 ON dbo.BulkInsertTest(Column5);
GO
下面是单例INSERT的执行计划。
INSERT INTO dbo.BulkInsertTest(Column1, Column2, Column3, Column4, Column5) VALUES
(1, 2, 3, 4, 5);
执行计划仅显示表插入操作符,因此新的非聚集索引行是在表插入操作本身期间插入的。大量的单例 INSERT 语句将为每个插入语句产生相同的计划。
我得到了一个类似的计划,其中包含通过行构造函数指定的大量行的单个 INSERT 语句,唯一的区别是添加了常量扫描运算符来发出行。
INSERT INTO dbo.BulkInsertTest(Column1, Column2, Column3, Column4, Column5) VALUES
(1, 2, 3, 4, 5)
,(1, 2, 3, 4, 5)
,(1, 2, 3, 4, 5)
,...
,(1, 2, 3, 4, 5);
这是 T-SQL BULK INSERT 语句的执行计划(使用虚拟空文件作为源)。通过BULK INSERT,SQL Server 添加了额外的查询计划运算符来优化索引插入。这些行在插入到表中后被假脱机,然后来自假脱机的行被排序并作为批量插入操作分别插入到每个索引中。这种方法减少了大型插入操作的开销。您可能还会看到针对INSERT...SELECT 查询的类似计划。
BULK INSERT dbo.BulkInsertTest
FROM 'c:\Temp\BulkInsertTest.txt';
我通过使用扩展事件跟踪捕获实际计划,验证了SqlBulkCopy 生成的执行计划与 T-SQL BULK INSERT 相同。下面是我使用的跟踪 DDL 和 PowerShell 脚本。
跟踪 DDL:
CREATE EVENT SESSION [SqlBulkCopyTest] ON SERVER
ADD EVENT sqlserver.query_post_execution_showplan(
ACTION(sqlserver.client_app_name,sqlserver.sql_text)
WHERE ([sqlserver].[equal_i_sql_unicode_string]([sqlserver].[client_app_name],N'SqlBulkCopyTest')
AND [sqlserver].[like_i_sql_unicode_string]([sqlserver].[sql_text],N'insert bulk%')
))
ADD TARGET package0.event_file(SET filename=N'SqlBulkCopyTest');
GO
PowerShell 脚本:
$connectionString = "Data Source=.;Initial Catalog=YourUserDatabase;Integrated Security=SSPI;Application Name=SqlBulkCopyTest"
$dt = New-Object System.Data.DataTable;
$null = $dt.Columns.Add("Column1", [System.Type]::GetType("System.Int32"))
$null = $dt.Columns.Add("Column2", [System.Type]::GetType("System.Int32"))
$null = $dt.Columns.Add("Column3", [System.Type]::GetType("System.Int32"))
$null = $dt.Columns.Add("Column4", [System.Type]::GetType("System.Int32"))
$null = $dt.Columns.Add("Column5", [System.Type]::GetType("System.Int32"))
$row = $dt.NewRow()
[void]$dt.Rows.Add($row)
$row["Column1"] = 1
$row["Column2"] = 2
$row["Column3"] = 3
$row["Column4"] = 4
$row["Column5"] = 5
$bcp = New-Object System.Data.SqlClient.SqlBulkCopy($connectionString)
$bcp.DestinationTableName = "dbo.BulkInsertTest"
$bcp.WriteToServer($dt)
编辑
感谢 Vladimir Baranov 提供 this blog article by Microsoft Data Platform MVP Paul White,其中详细介绍了 SQL Server 基于成本的索引维护策略。
编辑 2
我从您修改后的问题中看到,您的实际情况是具有聚集索引而不是堆的表。这些计划将类似于上面的堆示例,当然,除了将使用聚集索引插入运算符而不是表插入来插入数据。
ORDER 提示可以在批量插入操作期间指定到具有聚集索引的表中。当指定的顺序与聚集索引的顺序匹配时,SQL Server 可以消除聚集索引插入之前的排序运算符,因为它假定数据已经按照提示排序。
很遗憾,System.Data.SqlClient.SqlBulkCopy 不支持通过该 API 的 ORDER 提示。正如@benjol 在评论中提到的,较新的Microsoft.Data.SqlClient.SqlBulkCopy 包含一个ColumnOrderHints 属性,可以在其中指定目标表的聚集索引列和排序顺序。