4

我有一个包含 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

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

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

我定期使用非常标准的代码将 via SqlBulkCopy(from a DataTable) 插入到此表中:

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

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

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

我对你的问题是:

你能看到任何明显的我做错的事情会导致这种情况吗?

这只是“您的旋转磁盘对于这种大小的数据库来说不够快”的情况吗?

插入此数据时到底发生了什么?因为它是聚集索引,所以在插入时它会重新排列磁盘上的数据页吗?它正在尝试插入本质上是无序的 GUIDS,因此这种“随机插入性质”可能导致读/写标题在磁盘上的不同页面上移动很多?

谢谢你的时间。

4

4 回答 4

6

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

至于为什么它在 SSD 上比在磁驱动器上更快,我不是专家,但由于它组织数据的方式,SSD 上的碎片过程可能更快。I/O 吞吐量会更快,但不会那么快。

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

于 2013-08-08T15:24:37.537 回答
1

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

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

于 2013-08-08T15:29:04.897 回答
1

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

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

您还可以将GUID列保留为(非聚集)主键,并引入一个新INT IDENTITY列用作聚集键。我敢肯定,通过消除非常随机的 GUID 将在聚集索引上造成的严重碎片,仅此一项就已经有很大帮助了。

于 2013-08-08T15:52:49.080 回答
0

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

了解您无法控制 GUID

但是有几个选择。

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

您可以在加载之前对数据进行排序吗?
如果是这样,负载按 PK 排序。
如果您在 DataTable 中有数据,则可以对其进行排序。
您不会使用现有的加载代码,但可以对其进行排序。
TVP 是一种选择。

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

或者作为上述的变体,只需跳过 iden PK。

于 2013-08-08T15:38:42.440 回答