SQL Server缓慢插入-SqlBulkCopy性能

本文关键字:-SqlBulkCopy 性能 插入 缓慢 Server SQL | 更新日期: 2023-09-27 18:27:05

我有一个有3000万行的数据库。PK聚集索引是代码生成的GUID

下表如下:

CREATE TABLE [dbo].[events](
    [imageEventGUID] [uniqueidentifier] NOT NULL,
    [imageSHAID] [nvarchar](256) NOT NULL,
    [queryGUID] [uniqueidentifier] NOT NULL,
    [eventType] [int] NOT NULL,
    [eventValue] [nvarchar](2050) NULL,
    [dateOfEvent] [datetime] NOT NULL,
 CONSTRAINT [PK_store_image_event] PRIMARY KEY CLUSTERED 
(
    [imageEventGUID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

简单地说,它是一个图像搜索引擎。

  • CCD_ 2是代码唯一标识符
  • imageSHAID是图像URL的SHA256
  • queryGUID是代码生成的FK(为简洁起见,已从create语句中排除)
  • eventType是分配给事件类型的数字
  • eventValue通常是图像的URI,例如"http://mywebpage.com/images/image123456789.jpg"

我定期通过SqlBulkCopy(从DataTable)使用漂亮的标准代码插入到这个表中

using (SqlBulkCopy bulk = new SqlBulkCopy(storeConn, SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.KeepNulls, null))
{
    bulk.DestinationTableName = "[dbo].[events]";
    bulk.WriteToServer(myeventsDataTable);
}

我通常尝试在一个大容量插入中插入5k到10k行。我从这个批量复制中得到了糟糕的插入结果。我曾经在SSD上运行这个DB(只连接了SATA 1),速度非常快(不到500毫秒)。我在SSD上的空间用完了,所以我把DB换成了1TB 7200高速缓存旋转磁盘,从那时起,完成时间超过120秒(120000毫秒)。当大容量插入运行时,我可以看到大约1MB/秒的磁盘活动,CPU使用率很低。

除了PK,我在这张表上没有其他索引。

我想问你的问题是:

你能看出我做错了什么会导致这种情况吗?

这只是"你旋转的磁盘不够快,无法容纳这么大的数据库"的情况吗?

插入这些数据时究竟发生了什么?因为它是聚集索引,所以在进行插入时,它是否会重新排列磁盘上的数据页?它试图插入GUID,这些GUID本质上是无序的,因此这种"随机插入性质"可能会导致读/写标头在磁盘上的不同页面上移动很多?

谢谢你抽出时间。

SQL Server缓慢插入-SqlBulkCopy性能

我的猜测是,主要问题是您对聚集索引的选择。聚集索引确定表中的物理顺序或记录。由于您的PK是一个Guid(我假设它是随机生成的,而不是顺序生成的),数据库必须将每一行插入正确的位置,这可能位于两个现有记录之间,可能会导致页面拆分、碎片等。

至于为什么SSD上的速度比磁盘驱动器更快,我不是专家,但SSD上的碎片处理过程可能更快,因为它是如何组织数据的。I/O吞吐量将更快,但幅度不会那么大。

如果您可以使用数字自动递增主键而不是GUID,那么大容量插入应该快得多。您仍然可以在GUID列上创建唯一索引,以加快查询速度。

尝试在imageEventGUID列上使用带有newsequentialid()的默认约束。

它将按正确的顺序插入GUID,因此SQL Server不必在每次插入时重新排列表。

GUID作为集群主键本身就是一个非常糟糕的设计选择-请参阅Kim Tripp的博客文章GUIDs as primary KEYs和/或集群键以了解解释。使用随机(客户端生成)GUID将导致非常高(通常为99%或更多)的碎片,并且在批量插入大量行的过程中,它将导致大量页面拆分,这是非常昂贵的操作。

如果你不能改变这一点,你可以至少确保每晚都在重建具有可怕碎片值的聚集索引,如果你负担得起的话,甚至可以更频繁地重建。

您还可以保留GUID列作为(非集群)主键,并引入一个新的INT IDENTITY列用作集群键。我相信,单凭这一点就已经有了很大的帮助,消除了非常随机的GUID会在聚集索引上造成的令人愤慨的碎片。

您可以禁用其他索引,但不能禁用集群PK。
Well可以禁用集群PK,但这会禁用表
如果数据没有按照PK的顺序加载,那么您将获得快速索引碎片
随着碎片的增加,插入速度随之降低。

了解您无法控制GUID

但也有一些选择。

在[PK_store_image_event]上使用类似50、20或10的填充因子
这为插入留下了空间,但代价是磁盘上的索引大小更大
定期重建索引-至少每晚重建一次。

你能在加载之前对数据进行排序吗
如果是,则按PK排序加载。
如果数据表中有数据,则可以对其进行排序。
您不需要使用现有的加载代码,但可以对其进行排序。
TVP是一种选择。

请为[imageEventGUID]上的PK和唯一索引使用iden
如果它具有唯一索引,则可以是FK。
禁用该索引,加载,然后重新生成
如果有重复,则重建将失败。

或者作为上面的变体,只需跳过iden PK.