我是否应该避免向具有高吞吐量的表添加聚集键

Dra*_*mmy 6 index database-design sql-server primary-key

我们有一个 SQL Server 解决方案,它有一个表dsStaging.Audit,用于存储由第三方事务数据库创建的审计记录。我们使用这些审计将来自第三方系统的 CRUD 操作同步到我们的 SQL 数据库中。

CREATE TABLE [dsStaging].[Audit](
    [SyncExecutionId] [bigint] NOT NULL,
    [AuditDataGuid] [nvarchar](56) NOT NULL,
    [AuditDate] [datetime] NOT NULL,
    [AuditDateTimeZone] [datetimeoffset](3) NULL,
    [AuditEventGroup] [nvarchar](56) NOT NULL,
    [TransactionId] [bigint] NOT NULL,
    [TransactionSequence] [int] NOT NULL,
    .
    ...
    .
 CONSTRAINT [PK_Audit] PRIMARY KEY CLUSTERED 
(
    [SyncExecutionId] ASC,
    [TransactionId] ASC,
    [TransactionSequence] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
    IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
Run Code Online (Sandbox Code Playgroud)

处理审计后,我想将审计记录移动到一个单独的表中Processed.Audit,准备在 x 天后删除。

CREATE TABLE [Processed].[Audit](
    [SyncExecutionId] [bigint] NOT NULL,
    [AuditDataGuid] [nvarchar](56) NOT NULL,
    [AuditDate] [datetime] NOT NULL,
    [AuditEventGroup] [nvarchar](56) NOT NULL,
    [TransactionId] [bigint] NOT NULL,
    [TransactionSequence] [int] NOT NULL,
    .
    ...
    .
 CONSTRAINT [PK_Processed_Audit] PRIMARY KEY NONCLUSTERED 
(
    [AuditDate] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
    IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
Run Code Online (Sandbox Code Playgroud)

我将审计从暂存阶段转移到处理阶段的主要目标是性能。我需要确保临时表被锁定的时间尽可能短,以便可以尽快处理任何未处理的审计(临时表中的审计较少 = 处理速度更快)。

我们正在寻找周围1.5米审计记录通过这个过程会每隔一小时约10,000批次。

移动审计的过程大约每 20-30 秒触发一次。删除Processed.Audit记录的过程将每小时执行一次,并从 X 天前(通常大约 7 天)删除 1 小时的审计。

  • 我应该把Processed.Audit表变成聚集索引吗?

支持的最低版本:SQL Server 2012 标准版

Eri*_*ing 12

在这种情况下我想要聚集索引的主要原因是这一行:

删除 process.Audit 记录的过程将每小时执行一次,并从 x 天前(通常大约 7 天)删除一个小时的审计

当您从 HEAP 中删除行时,除非删除获得表锁,或者您向WITH (TABLOCK)删除查询提供提示,否则数据页可能不会被释放。不过,您可能可以想象这对并发有什么影响。不好。

请注意,TABLOCK如果您使用 RCSI 或快照隔离,提示将不会有此行为。

这是一个快速示例。加载一个小表:

USE tempdb;
SET NOCOUNT ON;

CREATE TABLE dbo.heap
(
    id INT PRIMARY KEY NONCLUSTERED,
    junk VARCHAR(1000)
);

INSERT dbo.heap (
    id, junk )
SELECT TOP 1000 x.n, REPLICATE('A', x.n % 1000)
FROM   (
           SELECT ROW_NUMBER() OVER ( ORDER BY @@ROWCOUNT ) AS n
           FROM   sys.messages AS m ) AS x;
Run Code Online (Sandbox Code Playgroud)

运行健全性检查查询以确定分配给堆和非集群 PK 的页面数量:

SELECT   OBJECT_NAME(i.object_id) AS table_name,
         i.name AS index_name,
         MAX(a.used_pages) AS leaf_me_alone
FROM     sys.indexes AS i
JOIN     sys.partitions AS p
ON p.object_id = i.object_id
   AND p.index_id = i.index_id
JOIN     sys.allocation_units AS a
ON a.container_id = p.partition_id
WHERE OBJECT_NAME(i.object_id) = 'heap'
GROUP BY i.object_id, i.index_id, i.name
ORDER BY OBJECT_NAME(i.object_id), i.index_id;
Run Code Online (Sandbox Code Playgroud)

结果如下:

table_name  index_name  leaf_me_alone
heap        NULL        74
heap        PK__heap__  7
Run Code Online (Sandbox Code Playgroud)

因此,堆中有 74 页,NC PK 中有 7 页。

做一些单例删除来清除表:

DECLARE @i INT = 1;

WHILE @i < 1000
    BEGIN

        DELETE h
        FROM  dbo.heap AS h
        WHERE h.id = @i;

        SET @i += 1;

        PRINT @i;

    END;
Run Code Online (Sandbox Code Playgroud)

如果您重新运行健全性检查查询,您将获得相同的结果。

更糟糕的是,如果您现在查询该表,SQL 将读取所有这些空白页

SET STATISTICS TIME, IO ON 
SELECT *
FROM   dbo.heap AS h;
Run Code Online (Sandbox Code Playgroud)

表'堆'。扫描计数 1,逻辑读取 67

所以现在不仅我们的表人为地变大了,而且 SQL 现在在磁盘、内存、备份和 DBCC CHECKDB 中都有一堆空白页……好吧,你明白了。

我们正在查看每小时大约有 150 万条审核记录通过此流程

嘿嘿嘿!没有乐趣。

从堆中释放页面的其他选项是:

TRUNCATE TABLE dbo.heap
Run Code Online (Sandbox Code Playgroud)

这对您不起作用,因为您需要批量删除数据。

ALTER TABLE dbo.heap REBUILD;
Run Code Online (Sandbox Code Playgroud)

在那个表大小下这对您来说会很痛苦,因为它会同时重建表上的所有非聚集索引。

表格会重用页面吗?有时可能有点。

DECLARE @id_max INT = (SELECT MAX(id) FROM dbo.heap AS h);

INSERT dbo.heap (
    id, junk )
SELECT TOP 5000 x.n + @id_max, REPLICATE('A', x.n % 1000)
FROM   (
           SELECT ROW_NUMBER() OVER ( ORDER BY @@ROWCOUNT ) AS n
           FROM   sys.messages AS m ) AS x;
Run Code Online (Sandbox Code Playgroud)

完整性检查:

table_name  index_name  leaf_me_alone
heap        NULL        400
heap        PK__heap__  20
Run Code Online (Sandbox Code Playgroud)

SELECT * 查询:

表'堆'。扫描计数 1,逻辑读取 392

希望这可以帮助!