gdo*_*ica 2 sql-server clustered-index identity fragmentation sql-server-2014
我有一个简单的表:
CREATE TABLE [dbo].[UserTestGroups](
[UserTestGroupId] [int] IDENTITY(1,1) NOT NULL,
[Token] [nvarchar](100) NOT NULL,
[TestId] [bigint] NOT NULL,
[Group] [tinyint] NOT NULL,
[InsertDate] [datetime] NOT NULL)
Run Code Online (Sandbox Code Playgroud)
该表将有相对大量的插入 - 最初 10,000 个会话,每个会话 8 行,每天总共 80,000 个插入。我们预计在不久的将来会显着增加。无论如何,我们努力使我们的平台尽可能具有弹性,而不仅仅是适应当前的负载。
该表将用于报告,可将其视为次要要求。
由于我们使用的是实体框架(Microsoft 的 ORM),因此所有写入的表都需要一个逻辑主键,因此我添加了一个我直接忽略的 Identity 列。我不喜欢使用复合键,它们往往会使 ORM 成为噩梦,所以除非我绝对必须这样做,否则我会添加另一个标识列并将其设为 PK。
所有 BI 报告的查询都将基于令牌,即sessionId - GUID,因此我在该token
列上创建了聚集索引。
我们的 DBA 是 SQL Server MVP,他告诉我在 GUID 列上使用聚集索引会导致碎片,我应该在IDENTITY
列上创建聚集索引,并为列创建非聚集索引Token
。
不明白,非聚集索引会不会有同样的碎片问题?为什么将数据复制到新索引中比使用聚集索引更好?
token-Guid 不是 PK,PK 是所有列的组合(包括InsertDate
)。
目前(因为我真的不知道是什么原因)所有 GUID 值都存储为NVARCHAR(100)
,我不知道这样做的历史原因是什么,但这就是我们所拥有的。
我们的DBA在海外,我们的沟通是一条推特长,所以在他回来之前我无法得到正确的答案
由于您表示插入性能是主要问题,因此我会采纳 DBA 的建议并将集群键设为标识列,因为它是一个唯一的、单调递增的数字,保证(几乎)永远不会导致页面拆分在桌子上。
另外,不要将 GUID 存储在nvarchar(100)
列中,而是使用为它们设计的数据类型,即uniqueidentifier
.
在将索引添加到此表之前,请仔细查看报告要求。您可能会发现您根本不需要 GUID 列上的索引。如果您确定需要在 GUID 列上建立索引,您可能会发现索引的前导列不是 GUID 是可取的,也许它是Group
orTestID
列?
如果您已通过严格的设计过程确定针对此表的大多数报告查询将使用会话 ID 作为连接或 where 子句的主要组件进行,您实际上可能会受益于在会话上聚集的表ID。您可能希望将页面填充因子设置为低于 100% 的某个点,也许从 93% 开始并监视碎片级别。此外,值得指出的是,如果将此表存储在 SSD 上,碎片可能不是一个大问题。会话 ID 会在其他相关表中传播吗?如果是这样,您可能还需要在那些其他表中的列上使用非聚集索引,从而加剧碎片问题。
IF OBJECT_ID('dbo.SessionSurrogate') IS NOT NULL
DROP TABLE dbo.SessionSurrogate;
CREATE TABLE dbo.SessionSurrogate
(
SessionID int NOT NULL
CONSTRAINT PK_SessionSurrogate
PRIMARY KEY NONCLUSTERED
IDENTITY(1,1)
, SessionGUID uniqueidentifier NOT NULL
);
CREATE UNIQUE INDEX CI_SessionSurrogate --could be non-unique if required
ON dbo.SessionSurrogate(SessionGUID);
IF OBJECT_ID('dbo.UserTestGroups') IS NOT NULL
DROP TABLE dbo.UserTestGroups;
CREATE TABLE dbo.UserTestGroups
(
UserTestGroupsID int NOT NULL
CONSTRAINT PK_UserTestGroups
PRIMARY KEY CLUSTERED
IDENTITY(1,1)
, SessionID int NOT NULL
, TestID bigint NOT NULL
, [Group] tinyint NOT NULL
, InsertDate datetime NOT NULL
);
GO
CREATE PROCEDURE dbo.InsertSession
(
@SessionGUID uniqueidentifier
, @TestID bigint
, @Group tinyint
)
AS
BEGIN
INSERT INTO dbo.SessionSurrogate (SessionGUID)
OUTPUT INSERTED.SessionID, @TestID, @Group, GETDATE()
INTO dbo.UserTestGroups (SessionID, TestID, [Group], InsertDate)
OUTPUT INSERTED.SessionID
VALUES (@SessionGUID);
END;
GO
Run Code Online (Sandbox Code Playgroud)
然后你会插入数据,如:
DECLARE @SessGUID uniqueidentifier = NEWID();
DECLARE @TestID bigint = 42;
DECLARE @Group tinyint = 6;
EXEC dbo.InsertSession @SessGUID, @TestID, @Group;
Run Code Online (Sandbox Code Playgroud)
这里的好处是您只会在行宽非常窄(20 字节)的单个表上造成碎片,每页提供大约 400 行。上面的存储过程非常紧凑int
,如果需要,输出生成供您以后使用。