每天添加和删除数百万条记录

时间:2015-11-04 16:06:35

标签: sql-server-2008-r2

使用SQL Server 2008 R2网络版。我的桌子有30天的数据。每天包含1200万条记录。每天,我需要导入1200万条新记录并删除1200万条最旧的记录。该表有两个索引,没有键。我正在使用BCP导入新数据。这需要0.5到2.5小时才能运行。我正在批量删除。删除需要20多个小时。删除和重新创建索引似乎没有帮助。什么是最快的解决方案?

    col1 varchar(10)
    col2 varchar(10)
    col3 int
    col4 date

    index1 (col1, col2, col4)
    index2 (col4)

2 个答案:

答案 0 :(得分:1)

  

更新:这不适用于网络版,但由于它在这里,我想我会留在这里。它对其他人有用。

模拟分区的一种方法是在视图中使用union all时创建和截断每日表。

它可能有助于添加几个快速SSD并特别用于日志文件和tempdb。

  

分区approch

目标是仅通过更新元数据来减少事务日志的数量。

当数据从分区移动或移除时,它会插入和删除行,这将导致事务日志中的(吨)LOB_INSERT_ROWSLOB_DELETE_ROW

唯一的选择是截断分区,但此选项不存在。 我们可以通过在空分区上使用MergeSplit来避免它。

在下面的示例中,我会缩短时间并仅创建过去3个月(即8月,9月和10月)的数据,但您可以轻松地将其扩展到X个月或X天。一旦数据开始添加到11月,8月将被删除,依此类推9月和12月......

  

文件和文件组

我首先创建6个文件和文件组[Part_0]到[Part_5]:

Alter Database [Test] Add Filegroup [Part_0];
...
Alter Database [Test] Add Filegroup [Part_5];
Alter Database [Test] Add File( NAME = N'Part_0', FILENAME = N'...\Part_0.ndf' , SIZE = 100MB , FILEGROWTH = 100MB ) TO Filegroup [Part_0];
...
Alter Database [Test] Add File( NAME = N'Part_5', FILENAME = N'...\Part_5.ndf' , SIZE = 100MB , FILEGROWTH = 100MB ) TO Filegroup [Part_5];
  

功能和方案

Create Partition Function [DateKeyPartFunction] (datetime2)
as Range Right For Values ('20150801', '20150901', '20151001', '20151101', '20151201');
Create Partition Scheme [DateKeyPartScheme] as Partition [DateKeyPartFunction]
To ([Part_0], [Part_1], [Part_2], [Part_3], [Part_4], [Part_5]);

同样有6个分区。这将在后面解释,但这主要是因为需要有空分区。

  

因为我不知道你桌子的确切设计,所以我将使用这张桌子:

Create Table dbo.DataPart(id int identity(0, 1), name char(1000), name_date datetime2);

Clustered Index

Create Clustered Index IDX_Part On dbo.DataPart(name_date) On DateKeyPartScheme(name_date);
  

虚拟数据

此代码在10月至8月(现在)每6秒创建一系列虚拟日期中的超过一百万条记录:

With inc(n) as(
    Select ROW_NUMBER() over(order by (select 1))-1 From (
        Select 1 From (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x1(n)
        Cross Join (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x2(n)
        Cross Join (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x3(n)
        Cross Join (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x4(n)
        Cross Join (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x5(n)
        Cross Join (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x6(n)
    ) as x(n)
)
Insert into dbo.DataPart(name, name_date) 
Select TOP(1000000) '', DATEADD(second, -n*6, getdate()) From inc;
  

分区数据:

这将分配如下:

Id Partition    Left Bound      Right Bound     Row Count
1  [Part_0]                 < '20150801'    0
2  [Part_1] >= '20150801'   < '20150901'    184042
3  [Part_2] >= '20150901'   < '20151001'    432000
4  [Part_3] >= '20151001'   < '20151101'    383958
5  [Part_4] >= '20151101'   < '20151201'    0
6  [Part_5] >= '20151201'                   0
  • [Part_1],[Part_2],[Part_3]包含8月,9月和10月的数据。
  • [Part_0](即&lt;'20150801')仅存在,因为它需要一个空分区才能在11月清空[Part_1](八月)一次。这将是七月及以前的数据,但必须保持空白。
  • 下个月已经有了一个空分区变得更容易了,尽管它会在11月之前保持空白。这是11月份[Part_4]的目的。
  • 分区方案需要为右边界之外的所有内容提供额外的分区。这是12月及以后的[Part_5],它也必须保持空白。
  

十一月

11月一次,新行将转到[Part_4],8月数据可以从[Part_1]中删除。 删除它而不必删除数十万行的唯一方法是将[Part_1]移离表格:

Create Table dbo.DataPart_Temp(id int identity(0, 1), name char(1000), name_date datetime2);
Create Clustered Index IDX_Part_temp On dbo.DataPart_temp(name_date) On [Part_1];
Alter Table DataPart Switch Partition 2 to DataPart_temp Partition 1;
  • DataPart_Temp必须与DataPart(列,索引)相同
  • 由于[Part_1]从DataPart移至DataPart_temp,因此必须在同一文件组上创建DataPart_temp上的聚集索引:[Part_1]
  • [Part_1]是DataPart的第二个分区,并切换到DataPart_temp的第一个也是唯一的分区。所有八月行都在DataPart_temp

该表现在按以下方式分区:

id Partition    Left Bound      Right Bound     Row Count
1  [Part_0]                 < '20150801'    0
2  [Part_1] >= '20150801'   < '20150901'    0
3  [Part_2] >= '20150901'   < '20151001'    432000
4  [Part_3] >= '20151001'   < '20151101'    383958
5  [Part_4] >= '20151101'   < '20151201'    0
6  [Part_5] >= '20151201'                   0
  

合并

[Part_0]和[Part_1]现在为空,可以合并:

Alter Partition Function [DateKeyPartFunction]() Merge Range ('20150801');

[Part_1]已被删除:

id Partition    Left Bound      Right Bound     Row Count
1  [Part_0]                 < '20150901'    0
2  [Part_2] >= '20150901'   < '20151001'    432000
3  [Part_3] >= '20151001'   < '20151101'    383958
4  [Part_4] >= '20151101'   < '20151201'    0
5  [Part_5] >= '20151201'                   0
  

下个月添加

现在不再使用[Part_1],它可以作为下一个可用分区添加到分区方案中:

Alter Partition Scheme [DateKeyPartScheme] Next Used [Part_1];

然后[Part_5](12月,&gt; ='20151201')可以被分割:

Alter Partition Function [DateKeyPartFunction]() Split Range ('20160101');

由于[Part_5]为空,因此无需移动任何东西。 [Part_5]的另一半将转到下一个可用分区[Part_1]:

Id Partition    Left Bound      Right Bound     Row Count
1  [Part_0]                 < '20150901'    0
2  [Part_2] >= '20150901'   < '20151001'    432000
3  [Part_3] >= '20151001'   < '20151101'    383958
4  [Part_4] >= '20151101'   < '20151201'    x rows in November
5  [Part_5] >= '20151201'   < '20160101'    0
6  [Part_1] >= '20160101'                   0
  

清理

DataPart_temp现在可以被截断然后删除(或者至少删除它的Clustered Index)。

  

几个月后

12月,分区ID 2必须移开([Part_2]),与分区1合并,然后在1月拆分之前加回。

为了自动完成此过程,您必须查找分区2的文件组名称:

然后创建动态SQL:

  • 在[Part_X]上将聚集索引添加到DataPart_temp(类似于DataPart)
  • 将[Part_X]从DataPart切换到DataPart_temp
  • 合并分区1和2
  • 将[Part_X]添加到DataPart
  • 拆分最后一个分区
  • 截断DataPart_temp
  • 删除DataPart_temp或其聚集索引

如果使用fn_dblog,您应该看到事务日志很少。

答案 1 :(得分:0)

Web版可能是用于您正在进行的操作类型的错误SKU。这是某种数据仓库解决方案吗?从解决方案视图来看,分区是正确的方法,但是由于这是不可行的,因此一种方法是拥有表的两个副本 - 一个包含当前数据,另一个包含最新数据。如果要直接访问表,请在准备切换时适当地重命名表。如果您通过一些存储过程访问该表,则只需更改过程,使它们指向新表。这一切都可以轻松实现自动化。

当您准备好进行第二天的操作时,您可以开始准备表 - bcp输出您需要的数据,然后重新输入bcp,然后bcp in new data。不需要删除。为什么需要2.5小时的bcp-in - 你有理解为什么吗?我愿意认为这是由于磁盘IO成为瓶颈。那和/或您的数据/日志文件是自动增长的。在准备新表时,您可以通过适当设置恢复模型(批量日志或简单),然后在批量操作完成后将其设置回来来缓解其中的一些。