nia*_*her 4 indexing sql-server sql-server-2008
我有下表
CREATE TABLE DiaryEntries
(
[userId] [uniqueidentifier] NOT NULL,
[setOn] [datetime] NOT NULL, -- always set to GETDATE().
[entry] [nvarchar](255) NULL
)
Run Code Online (Sandbox Code Playgroud)
每个用户每天将插入大约 3 个条目。将有大约 1'000'000 名用户。这意味着该表中每天有 3'000'000 条新记录。一旦记录超过 1 个月,我们就会将其删除。
大多数查询都有以下 WHERE 子句:
WHERE userId = @userId AND setOn > @setOn
Run Code Online (Sandbox Code Playgroud)
大多数查询返回不超过 3 行,除了一个返回本月内插入的所有行(最多 90 行)。
插入记录后,日期和用户 ID 不能更改。
现在我的问题是 - 如何最好地安排这张表的索引?我坚持两种选择:
你有什么建议?还有其他选择吗?
PS - 感谢您的时间。
经过2天的思考,我想出了一个不同的解决方案来解决这个问题。
CREATE TABLE MonthlyDiaries
(
[userId] uniqueidentifier NOT NULL,
[setOn] datetime NOT NULL, -- always set to GETDATE().
[entry1_1] bigint NULL, -- FK to the 1st entry of the 1st day of the month.
[entry1_2] bigint NULL, -- FK to the 2nd entry of the 1st day of the month.
[entry1_3] bigint NULL,
[entry2_1] bigint NULL,
[entry2_2] bigint NULL,
[entry2_3] bigint NULL,
...
[entry31_1] bigint NULL,
[entry31_2] bigint NULL,
[entry31_3] bigint NULL,
PRIMARY KEY (userId, setOn)
)
CREATE TABLE DiaryEntries
(
[id] bigint IDENTITY(1,1) PRIMARY KEY CLUSTERED,
[entry] nvarchar(255) NOT NULL
)
Run Code Online (Sandbox Code Playgroud)
基本上我将 31 天分成一行。这意味着我每个用户每月只插入一次新记录。这将页面拆分从每个用户每天 3 次减少到每个用户每月一次。显然有缺点,这里有一些
总的来说,我认为这是一个很好的权衡:从 3 页拆分/天/用户减少到仅 1 页拆分/月/用户,但作为回报,我的搜索速度略慢,因此付出了很小的代价。你怎么认为?
首先在你的表上添加一个默认约束。其次,添加分区方案。第三次重写您最常见的查询。
聚集索引应设置为 setOn,用户 ID。这消除了索引变得碎片化的可能性。您应该使用表分区来拆分表,以便每个月都存储在一个单独的文件中。这将减少维护。可以在网上找一个分区滑动窗口脚本,每个月都可以运行,创建下个月的新表,删除最老的月份,调整分区方案。如果您不关心存储,您还可以将真正旧的月份移动到存档表中。
您的查询 where 子句应采用以下形式:
WHERE setOn > @setOn AND userId = @userId
Run Code Online (Sandbox Code Playgroud)
或者当您整月返回时:
WHERE setOn BETWEEN @setOnBegin AND @setOnEnd AND userId = @userId
Run Code Online (Sandbox Code Playgroud)
没有分区的新架构设计如下所示:
-- Stub table for foreign key
CREATE TABLE Users
(
[userId] [uniqueidentifier] NOT NULL
CONSTRAINT PK_Users PRIMARY KEY NONCLUSTERED
CONSTRAINT DF_Users_userId DEFAULT NEWID(),
[userName] VARCHAR(50) NOT NULL
)
GO
CREATE TABLE DiaryEntries
(
[userId] [uniqueidentifier] NOT NULL
CONSTRAINT FK_DiaryEntries_Users FOREIGN KEY REFERENCES Users,
[setOn] [datetime] NOT NULL
CONSTRAINT DF_DiaryEntries_setOn DEFAULT GETDATE(),
[entry] [nvarchar](255) NULL,
CONSTRAINT PK_DiaryEntries PRIMARY KEY CLUSTERED (setOn, userId)
)
GO
Run Code Online (Sandbox Code Playgroud)
在你开始工作后,你必须添加分区。为此,请从这篇博文开始了解一些理论。然后开始阅读这份 MSDN 白皮书。白皮书是2005年写的,2008年有分区改进我没研究过,所以2008年的解决方案可能会更简单一些。
我假设您有充分的理由使用 guid 作为 id。
碎片主要是扫描的问题,而不是搜索。碎片对预读有很大的影响,并且搜索不使用也不需要预读。具有较差列选择的未碎片索引的性能总是比具有良好、可用列的 99% 碎片索引更差。如果您已经描述了扫描表的 DW 报告样式查询,那么我建议您专注于消除碎片,但是对于您描述的负载,专注于高效(覆盖)搜索和(小)范围扫描更有意义。
鉴于您的访问模式始终由@userId 驱动,这必须是聚集索引中最左侧的列。我还将 setOn 添加为聚集索引中的第二列,因为它在大多数查询中增加了一些边际值(我说边际是因为 @userId 是如此有选择性,最坏的是来自 90 百万的 90 条记录,额外的过滤由@setOn 并不重要)。我不会添加任何非聚集索引,从您描述的查询中不需要任何。
唯一的问题是删除旧记录(保留 30 天)。我建议不要使用二级 NC 索引来满足这一点。我宁愿使用滑动窗口部署每周分区方案,请参阅如何在 SQL Server 2005 上的分区表中实现自动滑动窗口。使用此解决方案,旧记录将通过分区切换删除,这可能是最有效的方式。每日分区方案将更准确地满足 30 天保留要求,也许值得尝试和测试。我犹豫直接推荐 30 个分区,因为您描述了一些查询可能会在每个分区中寻找特定的 @userId 记录,而 31 个分区可能会在重负载下产生性能问题。更好地测试和测量两者。
| 归档时间: |
|
| 查看次数: |
16704 次 |
| 最近记录: |