感谢您的阅读,此博客的文章都是原著,转载请您保留相关链接,谢谢!

历史数据迁移那些事

      关于历史数据迁移这事,相信很多数据库管理员都经常要面对,因为应对的场景和数据量的不同,数据迁移方法和技术也

五花八门;在一个数据量不大的表上迁移数据一般都不会有什么问题,一旦表的数据量增大,而且应用对这个表访问非常频繁时,

那如何有效的迁移数据就会是一件很有讲究的事。

迁移时先考虑以下几点要求:

  1. 如何设计历史数据保存方法;
  2. 如何将数据搬移到历史表中,如何将原表的数据删除;
  3. 在线系统要求在数据搬移时,对系统影响很小;
  4. Job什么时间运行合适;数据量变大时,搬移时间是否足够;
  5. 数据搬移,采用with(nolock)可能造成重复数据,使得搬移过程出错,如何处理比较好;

第一:设计历史数据保持方法:

     这个是个比较泛的概念,不同的公司使用的方式都不一样,常见有以下几种:

  1. 简单的历史表方式:

         其实就是在库中新建一个同结构的历史表,如:Product_History,然后将历史数据都放入到这个表中,一般适合数据量不

         大的情况;

         优劣:优点是比较简单,便于维护,查询的时候通过View将两个表Union起来;缺点是如果数据量比较大时,会有很大的

         性能问题(包括查询和插入);

  1. 按日期新建历史表:

        可能是按一个月或者一年新建一个历史表,如:Product_2010、Product_2011、Product_2012…等,然后将不同时期的

        历史数据存放到不同的历史表中;这个方式比较常见,查询的时候通过View将这些表Union All起来。

        优劣:优点是按日期分表后,会将数据量横向切小,单个表的数据量一般不会太大,处理起来性能比较好;缺点是维护比较

        麻烦,尤其是有字段的增删改的时候,所有的历史表都需要跟着修改。

  1. 数据仓库存放:

       这种方式很多有BI部门的公司采用,会将历史数据和当前数据都定期存放到数据仓库中,不仅可以给BI提供制作报表的数据,

       前台用户查询历史数据,APP程序就可以直接定位到仓库去查询,不影响产品环境。

       优劣:优点很明显,不仅解决了历史数据查询对产品环境的影响和BI报表数据来源的问题,而且减轻了生产库保存这些历史数

       据的负担;缺点是需要有BI部门,也就意味着需要相关的软、硬和人员的投入。

 

第二: 数据插入和删除:

     数据插入这个还是比较好解决的,我们可以直接用insert插入,也可以用BCP,Bulk Insert、SSIS等方式;数据删除在数据量

比较大的时候比较棘手,主要是要权衡系统影响和速度的问题,如果每次删除的数据量比较大,势必会对系统使用者造成影响,如果

每次删除的数据量比较小,可能需要比较长的时间才能完成(分区表有直接删掉某个分区的操作,这样可以不用删除数据,直接删掉

分区就行,在此不做说明,可以参考相关文档);

 

第三: 系统影响的考虑:

     这个和第二点相关,主要是处在系统删除的时候,需要考虑到锁的情况,避免数据库出现大批量的阻塞。

 

第四: Job运行时间和预计的搬移时间控制:

     JOB运行时间毫无疑问是要放到系统业务的低峰时期(一般都是晚上),不过前期我们应该知道我们的系统在什么时间段内业务

比较少,这段时间里面就适合做数据的搬移操作;控制搬移时间,这个要求我们对数据搬移进行控制,如果达到某个时间点(预计业

务开发繁忙了),数据还没有搬移完成,那么应该中断搬移过程,以免影响到业务的正常进行;如果出现这种数据不能按时搬移完成

的情况,意味着需要调整搬移的过程,或者使用更高效的方法,防止历史数据越积越多。

 

第五:主键冲突问题:

     基于对系统的影响考虑,我们一般在查询数据时都采用了With(nolock) 的方式,而数据库在默认的Read Commited隔离级

别下,加上With(nolock) 效果和Read UnCommited一样,这样在搬移数据时,可能会造成将还没有提交的数据搬移到历史表的情

况,在下次运行时,便产生了主键冲突的问题;有以下几种方法能避免此种情况:

  1. 搬移时不加With(nolock),考虑到对系统的影响,一般不采用;
  2. 历史表去掉主键,但是我们的历史数据是需要查询的,这样会造成很大的性能问题;
  3. 插入到历史表时先做判断,只插入历史表中不存在的数据,这个方法可行,但是如果历史数据表很大,判断的时间将会很长,不是最优的方法;
  4. 利用索引创建时的参数设置ignore_dup_key,将其设置为ON,这样就可以将重复的记录直接过滤掉,这种方法比较理想(后面例子有说明)。

我们以下面的两个表为例,对一些常用的数据搬移方法(将相关的方法建到JOB或者SP里面调用就行了),进行说明:

--Master table
create table Product
(
Id int identity(1,1) primary key,
Name varchar(100),
Product_Desc varchar(1000),
Price decimal(12,2),
CreateTime datetime,
UpdateTime datetime,
Statu tinyint
)
--index
create index ix_createtime on Product(CreateTime) with(fillfactor=90,online=on)

--History
create table Product_History
(
Id int primary key, --没有自增属性
Name varchar(100),
Product_Desc varchar(1000),
Price decimal(12,2),
CreateTime datetime,
UpdateTime datetime,
Status tinyint
)
--index
create index ix_createtime on Product_History (CreateTime) with(fillfactor=90,online=on)

No1. 简单的数据搬移:

简单的历史数据迁移方法,我们直接新建一个同结构的表,命名为表名加History,然后按照条件,每天搬移一部分数据到历史表中即可;

例如:我们将下面Product表的数据搬移到Product_History历史表中:

--搬移条件
--
10天前,Status=0的Product
insert into Product_History
select * from Product with(nolock)
where CreateTime<convert(varchar,DATEADD(day,-10,GETDATE()),112) and Status=0

--删除条件
delete from Product
where CreateTime<convert(varchar,DATEADD(day,-10,GETDATE()),112) and Status=0

No2. 方法一的改进:

对于数据量不大的表,我们为节省时间,可以直接把搬移和删除两个步骤放到一起来进行,方法如下:

--合并搬移和删除条件
delete a
output deleted.* into Product_History
from Product a
where CreateTime<convert(varchar,DATEADD(day,-10,GETDATE()),112) and Status=0

No3. 分步搬移法(减轻对系统影响):

方法一和二在数据量比较小时是没有问题的,搬移过程能很快完成,但是一旦数据量变大,数据搬移可能需要一段时间才能完成的情况

下,如果再采用这种方式,势必对访问这个表的APP造成大量阻塞,这个是在线系统不能接受的,于是我们不得不把系统影响考虑进来,

把大的数据量分批次来搬移,方法如下:

--------------------------分步搬移---------------------------------
while(1=1)
begin
declare @productid table
(
id int primary key
)

insert into @productid
select top 1000 id from Product with(nolock)
where CreateTime<convert(varchar,DATEADD(day,-10,GETDATE()),112) and Status=0

if exists(select 1 from @productid )
begin
--搬移条件
insert into Product_History
select * from Product with(nolock)
where id in(select id from @productid)

--删除条件
delete from Product
where id in(select id from @productid)

waitfor delay '00:00:02'
end
else
break;

end
--------------------------------------------------------------------

---------------------------分步搬移,合并搬移和删除条件------------------
--
分步搬移,合并搬移和删除条件
while(1=1)
begin

delete top (1000) a
output deleted.* into Product_History
from Product a
where CreateTime<convert(varchar,DATEADD(day,-10,GETDATE()),112) and Status=0

if @@ROWCOUNT=0
break
else
waitfor delay '00:00:02' --delay 2s

end
---------------------------------------------------------------------

No4. 控制搬移时间:

  我们在第三步的基础上做修改,为了防止搬移作业在业务高峰时间还在运行,阻塞业务操作,我们需要在搬移进行到某个时间点时,

中断搬移作业的运行,方法如下:

---------------------------7点后终止运行------------------
while(1=1)
begin

delete top (1000) a
output deleted.* into Product_History
from Product a
where CreateTime<convert(varchar,DATEADD(day,-10,GETDATE()),112) and Status=0

if @@ROWCOUNT=0 or datepart(HOUR, GETDATE())>=7 --7点后终止运行
break
else
waitfor delay '00:00:02' --delay 2s

end
---------------------------------------------------------------------

No5. 了解搬移的进度:

   有时候我们看到搬移JOB一直在运行,但是没有办法知道它要运行到什么时候才能运行完,只能干等,于是我们有对某些搬移作业进

度情况作了解的需求,我们再来更改脚本:

--先创建一个搬移记录表
create table temp_history_2011_12_01
( prd_id int primary key,
is_del bit --是否搬移完成
)

--------------------------了解搬移进度---------------------------------
truncate table temp_history_2011_12_01

--所有需要搬移的数据插入到表中
insert into temp_history_2011_12_01
select id,0 from Product with(nolock)
where CreateTime<convert(varchar,DATEADD(day,-10,GETDATE()),112) and Status=0

while(1=1)
begin
declare @productid table
(
id int primary key
)
--1000
insert into @productid
select top 1000 prd_id from temp_history_2011_12_01 where is_del=0

if exists(select 1 from @productid )
begin

--搬移条件
insert into Product_History
select * from Product with(nolock)
where id in(select id from @productid)

--删除条件
delete from Product
where id in(select id from @productid)

--update is_del
update temp_history_2011_12_01 set is_del=1
where prd_id in(select id from @productid)

waitfor delay '00:00:02'
end
else
break;

end
truncate table temp_history_2011_12_01
---------------------------------------------------------------------

--------------假定已运行时间是5小时,估计剩余时间-------------
declare @fin_count int,@need_count int
select @fin_count=COUNT(0) from temp_history_2011_12_01 with(nolock) where is_del=1
select @need_count=COUNT(0) from temp_history_2011_12_01 with(nolock) where is_del=0

select @need_count*1.0/@fin_count*5
---------------------------------------------------------------------

No6. 避免重复键冲突:

 我们先来看下ignore_dup_key作用的描述:使涉及多行的insert语句在有重复键值的错误时不会引起整个语句的回滚,那个导致不唯一的行会被舍弃, 而其他的行都将被插入或者更新,同样可以在创建主键约束时,使用该选项。

于是,我们可以在历史表创建时,在主键上使用这个选项,例如:

CREATE TABLE [dbo].[Product_History](
[Id] [int] NOT NULL,
[Name] [varchar](100) NULL,
[Product_Desc] [varchar](1000) NULL,
[Price] [decimal](12, 2) NULL,
[CreateTime] [datetime] NULL,
[UpdateTime] [datetime] NULL,
[Status] [tinyint] NULL,
PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (IGNORE_DUP_KEY = ON) ON [PRIMARY]
) ON [PRIMARY]

这样就可以避免因主键冲突导致数据搬移JOB的失败了。

 

    以上是就SQL层面的历史数据搬移的一些方法,还有BCP、Bulk Insert、SSIS等方法可以使用,在此不做说明;以上方法加以

调整,便可以做成比较通用的数据搬移方法,包括控制搬移记录数、控制搬移时间、了解搬移进度等问题都可以得到灵活控制,让数

据搬移顺起来。

posted @ 2011-12-07 10:31  飞洋过海  阅读(7962)  评论(13编辑  收藏  举报