任务
从一组大表中归档除滚动 13 个月之外的所有时间段。归档数据必须存储在另一个数据库中。
- 数据库处于简单恢复模式
- 这些表有 5000 万行到数十亿行,在某些情况下每个占用数百 GB。
- 表当前未分区
- 每个表在不断增加的日期列上都有一个聚集索引
- 每个表还有一个非聚集索引
- 对表的所有数据更改都是插入
- 目标是最大程度地减少主数据库的停机时间。
- 服务器是 2008 R2 Enterprise
“存档”表将有大约 11 亿行,“实时”表大约有 4 亿行。显然存档表会随着时间的推移而增加,但我希望实时表也会迅速增加。至少在未来几年内达到 50%。
我曾考虑过 Azure 延伸数据库,但不幸的是,我们处于 2008 R2 并且可能会在那里停留一段时间。
当前计划
- 创建一个新数据库
- 在新数据库中创建按月分区的新表(使用修改后的日期)。
- 将最近 12-13 个月的数据移动到分区表中。
- 对两个数据库进行重命名交换
- 从现在的“存档”数据库中删除移动的数据。
- 对“归档”数据库中的每个表进行分区。
- 将来使用分区交换来归档数据。
- 我确实意识到我必须换出要存档的数据,将该表复制到存档数据库,然后将其换入存档表。这是可以接受的。
问题: 我正在尝试将数据移动到初始分区表中(实际上我仍在对其进行概念验证)。我正在尝试使用 TF 610(根据Data Loading Performance Guide)和一个INSERT...SELECT
声明来移动数据,最初认为它会被最小化记录。不幸的是,每次我尝试它都已完全记录。
在这一点上,我认为我最好的选择可能是使用 SSIS 包移动数据。我试图避免这种情况,因为我正在处理 200 个表以及我可以通过脚本执行的任何操作,我可以轻松地生成和运行。
我的总体计划中是否遗漏了什么,SSIS 是我快速移动数据并最大限度地减少日志使用(空间问题)的最佳选择吗?
没有数据的演示代码
-- Existing structure
USE [Audit]
GO
CREATE TABLE [dbo].[AuditTable](
[Col1] [bigint] NULL,
[Col2] [int] NULL,
[Col3] [int] NULL,
[Col4] [int] NULL,
[Col5] [int] NULL,
[Col6] [money] NULL,
[Modified] [datetime] NULL,
[ModifiedBy] [varchar](50) NULL,
[ModifiedType] [char](1) NULL
);
-- ~1.4 bill rows, ~20% in the last year
CREATE CLUSTERED INDEX [AuditTable_Modified] ON [dbo].[AuditTable]
( [Modified] ASC )
GO
-- New DB & Code
USE Audit_New
GO
CREATE PARTITION FUNCTION ThirteenMonthPartFunction (datetime)
AS RANGE RIGHT FOR VALUES ('20150701', '20150801', '20150901', '20151001', '20151101', '20151201',
'20160101', '20160201', '20160301', '20160401', '20160501', '20160601',
'20160701')
CREATE PARTITION SCHEME ThirteenMonthPartScheme AS PARTITION ThirteenMonthPartFunction
ALL TO ( [PRIMARY] );
CREATE TABLE [dbo].[AuditTable](
[Col1] [bigint] NULL,
[Col2] [int] NULL,
[Col3] [int] NULL,
[Col4] [int] NULL,
[Col5] [int] NULL,
[Col6] [money] NULL,
[Modified] [datetime] NULL,
[ModifiedBy] [varchar](50) NULL,
[ModifiedType] [char](1) NULL
) ON ThirteenMonthPartScheme (Modified)
GO
CREATE CLUSTERED INDEX [AuditTable_Modified] ON [dbo].[AuditTable]
(
[Modified] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON ThirteenMonthPartScheme (Modified)
GO
CREATE NONCLUSTERED INDEX [AuditTable_Col1_Col2_Col3_Col4_Modified] ON [dbo].[AuditTable]
(
[Col1] ASC,
[Col2] ASC,
[Col3] ASC,
[Col4] ASC,
[Modified] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON ThirteenMonthPartScheme (Modified)
GO
移动代码
USE Audit_New
GO
DBCC TRACEON(610);
INSERT INTO AuditTable
SELECT * FROM Audit.dbo.AuditTable
WHERE Modified >= '6/1/2015'
ORDER BY Modified
为什么你没有得到最少的日志记录?
我发现您引用的Data Loading Performance Guide是非常有价值的资源。但是,它也不是 100% 全面的,而且我怀疑网格已经足够复杂,以至于作者没有添加列
Table Partitioning
来根据接收插入的表是否分区来打破行为差异。正如我们稍后将看到的,表已经分区的事实似乎抑制了最少的日志记录。推荐的方法
基于数据加载性能指南(包括“批量加载分区表”部分)中的建议以及加载具有数百亿行的分区表的丰富经验,这是我推荐的方法:
与原始方法相比的差异:
TABLOCK
如果您一次将一个月加载到一个堆中,使用分区切换将数据放入分区表中,那么移动最近 12-13 个月的数据的方法将更加有效。DELETE
清除旧表将被完全记录。也许您可以TRUNCATE
或者删除该表并创建一个新的存档表。移动最近一年数据的方法比较
为了在我的机器上在合理的时间内比较方法,我使用了一个
100MM row
我生成的测试数据集,它遵循你的模式。从下面的结果中可以看出,通过使用
TABLOCK
提示将数据加载到堆中,可以大大提高性能并减少日志写入。如果一次完成一个分区,还有一个额外的好处。还值得注意的是,如果一次运行多个分区,一次一个分区的方法可以很容易地进一步并行化。根据您的硬件,这可能会产生不错的提升;我们通常在服务器级硬件上一次加载至少四个分区。这是完整的测试脚本。
最后的笔记
所有这些结果在某种程度上取决于您的硬件。但是,我的测试是在带有旋转磁盘驱动器的标准四核笔记本电脑上进行的。如果您使用的是在执行此过程时没有很多其他负载的体面服务器,则数据加载可能会快得多。
例如,我在一个实际的开发服务器(Dell R720)上运行了推荐的方法,并看到了减少到
76 seconds
(从156 seconds
我的笔记本电脑上)。有趣的是,插入分区表的原始方法没有经历同样的改进,并且仍然12 minutes
在开发服务器上接管了。这大概是因为这种模式产生了一个串行执行计划,并且我笔记本电脑上的单个处理器可以匹配开发服务器上的单个处理器。这可能是 Biml 的一个很好的候选者。一种方法是创建一个可重复使用的模板,该模板将使用 For Each 容器在小日期范围内迁移单个表的数据。Biml 将遍历您的表集合,为每个符合条件的表创建相同的包。安迪伦纳德在他的楼梯系列中有一个介绍。
也许,不是创建新数据库,而是将真实数据库恢复到新数据库并删除最新的 12-13 个月数据。然后在您的真实数据库中删除刚刚创建的存档区域中不包含的数据。如果大删除是一个问题,也许您可以通过脚本删除 10K 或更大的集合来执行此操作。
您的分区任务似乎不会受到干扰,并且在您删除后似乎适用于任一数据库。