.net 使用SQLBulkCopy—SQL Server 2016中的表比SQL Server 2014中的表大得多

.net 使用SQLBulkCopy—SQL Server 2016中的表比SQL Server 2014中的表大得多,.net,sql-server,sql-server-2014,sql-server-2016,sqlbulkcopy,.net,Sql Server,Sql Server 2014,Sql Server 2016,Sqlbulkcopy,我有一个应用程序,它使用SqlBulkCopy将数据移动到一组表中。最近有消息称,使用SQL2016的用户报告其硬盘驱动器中充满了非常大的数据库(不应该那么大)的问题。此问题在SQL2014中不会出现。经检查,运行TableDataSizes.sql(附脚本)显示未使用的SpaceKB中有大量空间 我想知道a)SQLServer2016中是否存在一些缺陷,或者我们对SQLBulkCopy的使用是否与新功能“冲突”。我注意到SQLServer2016中的页面分配有一些变化。一般来说,这是什么原因造

我有一个应用程序,它使用SqlBulkCopy将数据移动到一组表中。最近有消息称,使用SQL2016的用户报告其硬盘驱动器中充满了非常大的数据库(不应该那么大)的问题。此问题在SQL2014中不会出现。经检查,运行TableDataSizes.sql(附脚本)显示未使用的SpaceKB中有大量空间

我想知道a)SQLServer2016中是否存在一些缺陷,或者我们对SQLBulkCopy的使用是否与新功能“冲突”。我注意到SQLServer2016中的页面分配有一些变化。一般来说,这是什么原因造成的

复制步骤 注意–以下描述了我看到的一种情况,其中删除了非必要信息。实际上,我并没有在数据库表中存储数千个时间戳(其他列已被删除)

  • 在SQL中创建数据库(我的名为TestDB)
  • 在该数据库中创建一个表(使用如下脚本)

  • 在该表上创建索引(使用如下脚本)

  • 开始使用下面提供的代码将记录运行到表中。(这是windows窗体的代码,该窗体上有一个名为btnGo的按钮和一个名为nupRecordsToInsert的数字UPDOWN

    Public Class Form1
    
    Private conStr As String = "Integrated Security=true;Persist Security Info=true;Server=.;Database=TestDB;Pooling=True"
    Dim tableName As String = "TestTable"
    
    Private Sub btnGo_Click(sender As Object, e As EventArgs) Handles btnGo.Click
    
        Dim table as DataTable = GetData(nupRecordsToInsert.Value)
    
        Using conn As SqlConnection = New SqlConnection(conStr)
            conn.Open()
            Using sbc As SqlBulkCopy = New SqlBulkCopy(conStr, SqlBulkCopyOptions.UseInternalTransaction Or SqlBulkCopyOptions.KeepIdentity)
    
                sbc.DestinationTableName = "[" & tableName & "]"
                sbc.BatchSize = 1000
                sbc.WriteToServer(table)
    
            End Using
        End Using
    
        MessageBox.Show($"Records Inserted = {nupRecordsToInsert.Value} into Database - TestDB. Table - {tableName}")
    End Sub
    
    Private Function GetData(numOfRecordsNeeded As Integer) As DataTable
        Dim table As DataTable = New DataTable()
        table.Columns.Add("TimeStamp", GetType(DateTime))   
    
        Dim dtDateTimeToInsert as DateTime = DateTime.Now
    
        For index As Integer = 1 To numOfRecordsNeeded
            dtDateTimeToInsert = dtDateTimeToInsert.AddSeconds(2)
            table.Rows.Add(dtDateTimeToInsert) 
        Next
    
        Return table
    End Function
    
    末级

  • 在某个时刻,数据库表中大约有500条记录,这意味着需要将新记录写入一个新页面。在这一点上,有趣的是,实际结果中概述了这种情况

  • 实际结果 SQL2016中的数据库非常大(这发生在第一个页面被填充并启动第二个页面之后)

    这可以在以下情况下更详细地看到:

  • 运行下面的SQL以了解表的大小。 在数据库中运行的记录越多,在UnusedSpaceKB列中看到的超大数字就越多

    use [TestDB]
    
    SELECT 
       t.NAME AS TableName,
       s.Name AS SchemaName,
       p.rows AS RowCounts,
       SUM(a.total_pages) * 8 AS TotalSpaceKB, 
       SUM(a.used_pages) * 8 AS UsedSpaceKB, 
       (SUM(a.total_pages) - SUM(a.used_pages)) * 8 AS UnusedSpaceKB
    FROM 
       sys.tables t
    INNER JOIN      
       sys.indexes i ON t.OBJECT_ID = i.object_id
    INNER JOIN 
       sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
    INNER JOIN 
       sys.allocation_units a ON p.partition_id = a.container_id
    LEFT OUTER JOIN 
       sys.schemas s ON t.schema_id = s.schema_id
    WHERE 
      t.NAME = 'TestTable'
      AND t.is_ms_shipped = 0
      AND i.OBJECT_ID > 255 
    GROUP BY 
      t.Name, s.Name, p.Rows
    ORDER BY 
      RowCounts desc
    
  • 以未使用的空格KB显示大数字的输出

  • 运行下面的查询显示已分配了许多页面,但每个“8个页面集”中只使用了第一个页面。这使得每8个页面中的最后7个页面未使用,从而造成大量空间浪费

     select * from sys.dm_db_database_page_allocations
     (DB_id() , object_id('[dbo].[TestTable]') , NULL , NULL , 'DETAILED')
    
  • 下面显示了页面分配不连续运行的部分结果。

    SQL2014中的数据库没有显示此问题 1.当运行适当的查询(如上所述)时,我们在UnusedSpaceKB列中没有看到大的值

    use [TestDB]
    
    SELECT 
       t.NAME AS TableName,
       s.Name AS SchemaName,
       p.rows AS RowCounts,
       SUM(a.total_pages) * 8 AS TotalSpaceKB, 
       SUM(a.used_pages) * 8 AS UsedSpaceKB, 
       (SUM(a.total_pages) - SUM(a.used_pages)) * 8 AS UnusedSpaceKB
    FROM 
       sys.tables t
    INNER JOIN      
       sys.indexes i ON t.OBJECT_ID = i.object_id
    INNER JOIN 
       sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
    INNER JOIN 
       sys.allocation_units a ON p.partition_id = a.container_id
    LEFT OUTER JOIN 
       sys.schemas s ON t.schema_id = s.schema_id
    WHERE 
      t.NAME = 'TestTable'
      AND t.is_ms_shipped = 0
      AND i.OBJECT_ID > 255 
    GROUP BY 
      t.Name, s.Name, p.Rows
    ORDER BY 
      RowCounts desc
    
  • 运行另一个查询(查询-dm_db_database_page_allocations)显示已分配了许多页面,但每个页面都按顺序使用。没有间隙-没有7个未使用页面的块
  • 预期结果 我希望SQL2016的行为与SQL2014类似,不会创建非常大的表。特别是我希望页面可以连续分配,并且在分配中没有7个页面间隙

    如果有人想知道我为什么会看到这种差异,那将非常有帮助。

    您需要:

    如果出于任何原因,您无法更改batchsize,或者如果您没有看到默认最小日志记录行为改善了数据加载性能,则可以使用跟踪标志692(…)在SQL Server 2016中禁用快速插入行为。我们预计在正常情况下,客户不需要此跟踪标志

    您需要:

    如果出于任何原因,您无法更改batchsize,或者如果您没有看到默认最小日志记录行为改善了数据加载性能,则可以使用跟踪标志692(…)在SQL Server 2016中禁用快速插入行为。我们预计在正常情况下,客户不需要此跟踪标志


    您是否检查了两台服务器上的服务器填充因子是否相同?您的创建索引没有明确指定它,因此使用了服务器默认值。另外,为什么不在大容量复制之后创建索引?现在这样做,您将永远不会有最小的日志记录。您使用的大容量复制几乎是最低效的方式—您有一个聚集索引o在表中,批量大小为1000且正在使用行锁而不是表锁。您仍将获得流式数据,但操作本身将被完全记录。但是,从SQL Server 2014开始,这一点本身不应改变。这两种情况下的恢复模型是否相同?是否应用了任何自定义跟踪标志?(与跟踪标志610类似,跟踪标志610支持对具有聚集索引的表上的批量插入进行最小日志记录)?数据库的自动增长设置是什么?移动了多少数据?太大是什么意思?这个问题中的步骤太模糊,无法重现任何问题一个范围是8页。看起来每个页面分配都是从一个新的范围完成的。如前所述,尝试增加批处理大小(文章还提到TF 692是一个解决方案,如果您不能的话)。(另外,很高兴知道SQL Server 2016不再需要TF 610来在聚集索引上获得最小日志批量插入。)请注意,对于批量插入来说,批量插入将始终是优化的,对于足够小的批处理,您可以考虑在事务中切换到常规插入,这不会慢得多。您是否检查了两个服务器上的服务器填充因子是否相同?您的创建索引没有明确地指定它,因此使用服务器默认值。为什么不在大容量复制之后创建索引?现在这样做,您将永远不会有最小的日志记录。您正在以几乎最低效的方式使用大容量复制--您的表上有一个聚集索引,批量大小为1000,并且使用行锁而不是表锁。您仍然会获得流式数据,但操作本身将是f完全记录。但是,这本身不应该从SQL Server 2014更改。是否恢复
     select * from sys.dm_db_database_page_allocations
     (DB_id() , object_id('[dbo].[TestTable]') , NULL , NULL , 'DETAILED')