我有一个应用程序一次将多行插入SQL Server。
我使用SqlBulkCopy
类或自编代码生成一个巨大的insert into table_name(...) values (...)
语句。
我的表有几个索引和一个聚簇索引。
问题是:这些索引是如何更新的?对于我插入的每一行?对于每笔交易?
有点奇怪的问题 - 这个场景有一个通用术语,例如'批量插入索引行为&#39 ;?我尝试谷歌几个关键字组合,没有找到任何东西。我问的原因是因为我有时会与Postgres合作并且也想知道它的行为。
我一直试图找几篇关于这个主题的文章,没有任何运气。
如果您可以向我指出任何文档,文章或带有相关章节的书籍,那就太棒了
答案 0 :(得分:4)
您可以通过检查查询计划来查看如何更新索引。考虑这个只有非聚集索引的堆表。
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语句获得了一个类似的计划,该语句具有通过行构造函数指定的大量行,唯一的区别是添加了一个常量扫描运算符来发出行。
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脚本。
Drace 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可以在聚簇索引插入之前取消排序运算符,因为它假定数据已经按提示进行了排序。不幸的是,SqlBulkCopy
不支持ORDER
提示。
答案 1 :(得分:0)
My table has several indexes except clustered one
这意味着该表仅包含non clustered index
。
这也意味着该表为HEAP
。
当插入数据(单个或批量)时,数据总是写入表末尾或下一页。
删除数据后,页面之间将变为空闲状态,但不会回收,因为始终将数据写入此端。
因此,堆表中的碎片多于聚簇索引表。
因为表也有several Non Clusetered index
。
提交后将自动重建索引。
由于索引是有序的,所以将有Index page split
。
因此,如果对诸如varchar(100),varchar(500) etc
之类的繁重数据类型进行索引,则索引页拆分将非常频繁地发生。
答案 2 :(得分:0)
问题是:这些索引如何更新?为每一行插入? 对于每笔交易?
从低层次的角度来看,索引总是逐行更新,这是索引内部数据结构的结果。 SQL Server索引是B +树。没有一种算法可以一次更新B +树索引中的几行,您需要一次更新一次,因为您无法事先知道行将更新到哪一行,或者插入之前的行。
但是,从事务的角度来看,索引是一次全部更新的,这是因为SQL Server实现了事务语义。在默认隔离级别READ COMMITTED上,在提交事务之前,另一个事务将看不到您在批量插入操作中插入的行(索引行或表行)。因此,当所有行一次插入时就出现了。