关于数据同步的几种实现

概述

关于数据同步主要有两个层面的同步,一是通过后台程序编码实现数据同步,二是直接作用于数据库,在数据库层面实现数据的同步。通过程序编码实现数据同步,其主要的实现思路很容易理解,即有就更新,无则新增,其他情况日志记录,就不做过多的介绍,这里主要讲述的是第二个层面的数据同步,即在数据库层面实现数据同步。

数据库层面的数据库同步主要有三种方式:通过发布/订阅的方式实现同步,通过SQL JOB方式实现数据同步,通过Service Broker 消息队列的方式实现数据同步。

下面分别就这三种数据同步方式,一一详解。

1.    通过发布/订阅的方式实现同步

发布/订阅是Sql Server自带的一种数据库备份的机制,通过该机制可以快速的实现数据的备份同步,不用编写任何的代码。

此种数据同步的方式存在的以下的一些问题:

  1. 表结构不能更改,同步双方的表结构必须一致,一旦表结构发生更改需要重新生成数据库快照。
  2. 对于大数据量的同步没有可靠的保证。
  3. 网络不稳定的情况下同步也不能保证。

总的来说,这种数据备份同步的方式,在表结构一致、数据量不是特别大的情况下还是非常高效的一种同步方式。

网上有很多的关于如何使用发布/订阅的方式实现数据同步的操作示例,这里就不再重复的演示了,有兴趣想要了解的朋友可以参考下面这篇文章:

http://kb.cnblogs.com/page/103975/

2.    通过SQL JOB方式实现数据同步

通过Sql Job定时作业的方式实现同步其基本原理就是通过目标服务器和源服务器的连接,然后通过编写Sql语句,从源服务器中读取数据,再更新到目标服务器。

这种数据同步的方式比较灵活。创建过sql定时作业之后,主要需要执行以下关键的两步。

2.1     创建数据库连接(一般作为定时作业执行的第一步)

不同数据库之间的连接可以通过系统的存储过程实现。下面就直接用一个示例来讲一下如何创建数据库连接。

--添加一个连接

--系统存储过程sp_addlinkedserver 参数:

----------------------1:目标服务器的IP或别名,本例中为:'WIN-S1PO3UA6J7I';----------------------2:'' (srvproduct,默认);

----------------------3:'SQLOLEDB'(provider,默认值);

----------------------4:目标服务器的IP或别名(datasrc),本例中为:'WIN-S1PO3UA6J7I'

exec sp_addlinkedserver 'WIN-S1PO3UA6J7I','','SQLOLEDB','WIN-S1PO3UA6J7I'

--添加登录用户连接

--系统存储过程sp_addlinkedsrvlogin 参数:

----------------------1:目标服务器的IP或别名,本例中为:'WIN-S1PO3UA6J7I';

----------------------2:'false',默认值;

----------------------3:null,默认值;

----------------------4:'sa',登录用户名;

----------------------5:'pass@word1',登录密码;

exec sp_addlinkedsrvlogin 'WIN-S1PO3UA6J7I','false',null,'sa','pass@word1'

创建数据库连接主要用到了以上的两个存储过程,但是在实际操作的过程中可能会遇到“仍有对服务器XXX的远程登录或连接登录问题”这样的问题,如果遇到此类问题,在执行上边的添加连接和登录用户连接之前还需要先删除某个已存在的链接,具体如下:

--系统存储过程sp_droplinkedsrvlogin 参数:

----------------------1:目标服务器的IP或别名,本例中为:'WIN-S1PO3UA6J7I';----------------------2:null

exec sp_droplinkedsrvlogin 'WIN-S1PO3UA6J7I',null

--系统存储过程sp_dropserver 参数:

----------------------1:目标服务器的IP或别名,本例中为:'WIN-S1PO3UA6J7I'

exec sp_dropserver 'WIN-S1PO3UA6J7I'

2.2     使用SQL 语句 实现数据同步

主要的同步思路:

1:在目标数据库中先清空要同步的表的数据

2:使用insert into Table (Cloumn....) select Column..... from 服务器别名或IP.目标数据库名.dbo.TableName 的语法将数据从源数据库读取并插入到目标数据库

Truncate  table  Org_DepartmentsExt –删除现有系统中已存在的部门表

insert into Org_DepartmentsExt –从名为WIN-S1PO3UA6J7I的服务器上的DBFrom数据库上获取源数据,并同步到目标数据库中

     (

      [DeptID]

      ,[DeptStatus]

      ,[DeptTel]

      ,[DeptBrief]

      ,[DeptFunctions] 

     )

SELECT [DeptID]

      ,[DeptStatus]

      ,[DeptTel]

      ,[DeptBrief]

      ,[DeptFunctions]

  FROM [WIN-S1PO3UA6J7I].[DBFrom].[dbo].[Org_DepartmentsExt]

以上这两步便是通过SQL Job实现数据同步的关键步骤,在完成以上两步之后,如果没有其他的表要进行同步,则可创建同步计划以完善定时作业。带作业创建完后,便可以执行。

这里主要只是演示了通过Sql Job方式实现数据同步的关键步骤。网上有很多具体的实例演示。有兴趣的朋友可以参考以下文章进行练习检验:

http://www.cnblogs.com/tyb1222/archive/2011/05/27/2060075.html

3.    通过SQL Server Service Broker 消息队列的方式实现数据同步

3.1 SQL Server Service Broker概述

SQL Server Service Broker 是数据库引擎的组成部分,为 SQL Server 提供队列和可靠的消息传递。既可用于使用单个 SQL Server 实例的应用程序,也可用于在多个实例间分发工作的应用程序。

在单个 SQL Server 实例内,Service Broker 提供了一个功能强大的异步编程模型。数据库应用程序通常使用异步编程来缩短交互式响应时间,并增加应用程序总吞吐量。

在多个SQL Server实例之间Service Broker 还可以提供可靠的消息传递服务。Service Broker 可帮助开发人员通过称为服务的独立、自包含的组件来编写应用程序。需要使用这些服务中所包含功能的应用程序可以使用消息来与这些服务进行交互。Service Broker 使用 TCP/IP 在实例间交换消息。Service Broker 中所包含的功能有助于防止未经授权的网络访问,并可以对通过网络发送的消息进行加密。

3.2 具体的实现演示

在这一小节里,主要是通过一个完整的数据同步的流程向大家演示,如何实现同一个数据库实例不同数据库的数据同步。关于不同的数据库实例间的数据库的数据同步整体上跟同一个实例的数据库同步是一样的,只不过在不同的数据库实例间同步时还需启用传输安全、对话安全,创建路由、远程服务绑定等额外的操作。

这里边用到了大量的SQL Server XML的东西,如果有不理解的地方可以参考以下链接:http://www.cnblogs.com/Olive116/p/3355840.html

这是我在做技术准备时,自己的一点学习记录。

下面就是具体的实现步骤:

3.2.1为数据库启动Service Broker活动

    这一步主要是用来对要进行数据同步的数据启用Service Broker 活动,并且授信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
USE master
GO
--如果数据库DBFrom、DBTo不存在,则创建相应的数据库
IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = 'DBFrom' )
CREATE DATABASE DBFrom
GO
IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = 'DBTo' )
CREATE DATABASE DBTo
GO
--分别为该数据库启用Service Broker活动并且授权信任
ALTER DATABASE DBFrom SET ENABLE_BROKER
GO
ALTER DATABASE DBFrom SET TRUSTWORTHY ON
GO
ALTER AUTHORIZATION ON DATABASE::DBFrom To sa
GO
ALTER DATABASE DBTo SET ENABLE_BROKER
GO
ALTER DATABASE DBTo SET TRUSTWORTHY ON
GO
ALTER AUTHORIZATION ON DATABASE::DBTo TO sa
GO

 

3.2.2 创建数据库主密匙

这一步主要用来创建数据库主密匙,上边有提到Service Broker可以对要发送的消息进行加密。

1
2
3
4
5
6
7
8
9
10
11
Use DBFrom
go
create master key
encryption  by  password= 'pass@word1'
go
 
Use DBTo
go
create master key
encryption  by  password= 'pass@word1'
go

 

3.2.3 创建消息类型、协定

这里主要用来创建消息类型和消息协定,源数据库和目标数据库的消息类型和协定都要一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Use DBFrom
go
--数据同步—消息类型
create message type [http: //oa.founder.com/Data/Sync]
validation=well_formed_xml
go
--数据同步--错误反馈消息类型
create message type [http: //oa.founder.com/Data/Sync/Error]
validation=well_formed_xml
go
--数据同步协议
create contract[http: //oa.founder.com/Data/SyncContract]
(
[http: //oa.founder.com/Data/Sync]
sent  by  initiator,
[http: //oa.founder.com/Data/Sync/Error]
sent  by  target
)
go
 
Use DBTo
go
--数据同步—消息类型
create message type [http: //oa.founder.com/Data/Sync]
validation=well_formed_xml
go
--数据同步--错误反馈消息类型
create message type [http: //oa.founder.com/Data/Sync/Error]
validation=well_formed_xml
go
--数据同步协议
 
create contract[http: //oa.founder.com/Data/SyncContract]
(
[http: //oa.founder.com/Data/Sync]
sent  by  initiator,
[http: //oa.founder.com/Data/Sync/Error]
sent  by  target
)
Go

 

创建过之后效果如下图:

关于数据同步的几种实现_第1张图片                       

3.2.4 创建消息队列

    这里主要用来创建消息队列,源数据库和目标数据库都要创建,队列名字可以自主命名。

1
2
3
4
5
6
7
8
9
10
11
use DBFrom
go
create queue [DBFrom_DataSyncQueue]
with status= on
go
 
use DBTo
go
create queue [DBFrom_DataSyncQueue]
with status= on
go

 

创建之后效果如下图:

 

3.2.5 创建数据同步服务

这里我们通过利用上边创建的消息协定和消息队列来创建数据同步的服务。

1
2
3
4
5
6
7
8
9
10
11
12
use DBFrom
go
create service [http: //oa.founder.com/DBFrom/Data/SyncService]
on  queue dbo.[DBFrom_DataSyncQueue]([http: //oa.founder.com/Data/SyncContract])
go
 
--数据同步服务
use DBTo
go
create service [http: //oa.founder.com/DBTo/Data/SyncService]
on  queue dbo.[DBFrom_DataSyncQueue]([http: //oa.founder.com/Data/SyncContract])
go

 

    创建后效果如下图:

 

3.2.6 在源数据库上创建服务配置列表

这里需要在源数据库上创建一个服务配置列表,主要用来保存之前创建过的服务名称,本例只是用来演示,所以只创建了一个服务,只能是同步一个数据表,如果有多个数据表需要同步,则需创建多个服务,所以这里创建一个服务配置列表,用来存储多个服务的服务名称。

需要注意的是,下面的脚本在执行完创建表的操作之后又插入了一条数据,也就是上边我们创建的服务名,如果有多个服务的话,依次插入该表即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use DBFrom
go
--同步数据--目标服务配置
create table SyncDataFarServices
(
ServiceID uniqueidentifier,
ServiceName nvarchar(256)
)
go
--将上边创建的服务名,插入此表中
insert  into  SyncDataFarServices (ServiceID,ServiceName)
values
(NEWID(), 'http://oa.founder.com/DBTo/Data/SyncService' )
go

 

效果如下图:

 

 

3.2.7 发送数据同步消息

    这里创建了一个存储过程主要用来发送同步消息,该消息内容主要包括操作类型、主键、表名、正文内容,分别对应@DMLType,@PrimaryKeyField,@TableName,@XMLData。然后通过创建一个游标来条的读取上边创建的服务列表中的列表信息,向不同的服务发送消息。

   

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Use DBFrom
go
--发送同步数据消息
Create procedure UP_SyncDataSendMsg
(
@PrimaryKeyField nvarchar(128),
@TableName nvarchar(128),
@DMLType  char (1),
@XMLData xml
)
as
begin
    SET @XMLData.modify( 'insert {sql:variable("@DMLType")}  as first into /' );
     SET @XMLData.modify( 'insert {sql:variable("@PrimaryKeyField")}  as first into /' );
     SET @XMLData.modify( 'insert {sql:variable("@TableName")}
as first into /'
);
     DECLARE FarServices CURSOR FOR SELECT ServiceName FROM SyncDataFarServices; 
    open FarServices
       declare @FarServiceName nvarchar(256);
       fetch FarServices  into  @FarServiceName;
       while  @@FETCH_STATUS=0
         begin
            begin Transaction
              declare @Conv_Handler uniqueidentifier
              begin DIALOG conversation @Conv_Handler --开始一个会话
              from  service [http: //oa.founder.com/DBFrom/Data/SyncService]
              to service @FarServiceName
              on  contract [http: //oa.founder.com/Data/SyncContract];         
              send  on  conversation @Conv_Handler
              Message type [http: //oa.founder.com/Data/Sync](@XMLData);
              fetch FarServices  into  @FarServiceName;
              commit;
         end
     close FarServices;
     deallocate FarServices;
end
go

 

 

3.2.8 创建数据同步异常信息记录表

这里创建该表主要用来记录在数据同步过程中出现的异常信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use DBFrom
go
create Table dbo.SyncException
(
ErrorID uniqueidentifier,
ConversationHandleID uniqueidentifier,
ErrorNumber  int ,
ErrorSeverity  int ,
ErrorState  int ,
ErrorProcedure nvarchar(126),
ErrorLine  int ,
ErrorMessage nvarchar(2048),
MessageContent nvarchar(max),
CreateDate DateTime
)
go
 
 
--修改异常信息记录表
alter table dbo.SyncException
add
PrimaryKeyField nvarchar(128),
TableName nvarchar(128),
DMLType  char (1),
DBName nvarchar(128)
Go

 

 

效果如下图:

关于数据同步的几种实现_第2张图片 

3.2.9 数据同步反馈

这里主要用来在源数据库中接收队列中的消息,将同时出错的信息,解析一下,然后插入到异常信息记录表里边。

--数据同步回馈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
use DBFrom
go
create procedure UP_SyncDataFeedback
as
begin
set  nocount  on
--会话变量声明
declare @ConversationHandle uniqueidentifier;--会话句柄
declare @Msg_Body nvarchar(max);
declare @Msg_Type_Name sysname;
--变量赋值
while (1=1)
begin
   begin transaction
      --从队列中接收消息
      waitfor
      (
         receive top(1)
         @Msg_Type_Name=message_type_name,
         @ConversationHandle=[conversation_handle],
         @Msg_Body=message_body
         from  dbo.[DBFrom_DataSyncQueue]
      ),timeout 1000
      --如果接收到消息处理,否则跳过
      if (@@ROWCOUNT<=0)
         break ;
      if  @Msg_Type_Name= 'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'
          end conversation @ConversationHandle;
      else  if  @Msg_Type_Name= 'http://oa.founder.com/Data/Sync/Error'
          begin
              declare @DataSource xml;
              set  @DataSource=Convert(xml,@Msg_Body);
              insert  into  dbo.SyncException(ErrorID,ConversationHandleID,ErrorNumber,ErrorSeverity,ErrorState,ErrorProcedure,ErrorLine,ErrorMessage,
PrimaryKeyField,TableName,DMLType,MessageContent,DBName,CreateDate)
              select
              NEWID(),@ConversationHandle,
              T.c.value( './@ErrNumber' , 'INT' ),
              T.c.value( './@ErrSeverity' , 'INT' ),
              T.c.value( './@ErrState' , 'INT' ),
              T.c.value( './@ErrProcedure' , 'Nvarchar(126)' ),
              T.c.value( './@ErrLine' , 'INT' ),
              T.c.value( './@ErrMessage' , 'nvarchar(2048)' ),
              T.c.value( './@PrimaryKeyField' , 'nvarchar(128)' ),
              T.c.value( './@TableName' , 'nvarchar(128)' ),
              T.c.value( './@DMLType' , 'char(1)' ),
              T.c.value( './@MessageContent' , 'nvarchar(max)' ),
             T.c.value( './@DBName' , 'nvarchar(128)' ),
              GETDATE()
              from  @DataSource.nodes( '/row' as  T(c);
           end
      else  if  @Msg_Type_Name= 'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
          end conversation @ConversationHandle;
   commit Transaction;
end
end
commit;
go

 

3.2.10对Service Broker队列使用内部激活,并指定将调用的存储过程

    这里主要用来激活源数据库的消息队列,并为其指定调用的存储过程,即上边3.2.9 中创建的存储过程。

1
2
3
4
5
6
7
8
9
10
11
--对Service Broker队列使用内部激活,并指定将调用的存储过程
use DBFrom
go
alter queue dbo.DBFrom_DataSyncQueue with activation
(
   status= on ,
   max_queue_Readers=1,
   procedure_name=UP_SyncDataFeedback,
   execute  as  owner
);
Go

 

3.2.11 在源数据库中为需要同步的数据表创建触发器

这里就以用户表为例,具体操作如下,这里通过查询系统的Inserted和Deleted临时表来判断执行同步的操作类型是更新(U)、新增(A)还是删除(D),最后调用3.2.7 中创建的存储过程来对数据进行处理并发送。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
use DBFrom
Go
--用户信息同步
Create Trigger UT_DataSync_Users
on  dbo.Org_Users
after insert,update,delete
as
set  nocount  on  ;
--变量声明
declare @PrimaryKeyField nvarchar(128),@TableName nvarchar(128),@DMLType  char (1);
declare @InsertCount  int  ,@DeleteCount  int  ;
declare @XMLData xml;
--变量赋值
set  @PrimaryKeyField= 'ID'  --组合主键,多个主键使用 "," 隔开
set  @TableName= 'Org_Users'
set  @InsertCount=( select  COUNT(*)  from  inserted)
set  @DeleteCount=( select  COUNT(*)  from  deleted)
if  @InsertCount=@DeleteCount and @InsertCount<>0  ----Update
   begin
   select  @XMLData=( select  from  inserted For xml raw,binary base64,ELEMENTS XSINIL);
   set  @DMLType= 'U' ;
   end
else  if (@InsertCount<>0 and @DeleteCount=0) ----Insert
   begin
   select  @XMLData=( select  from  inserted  for  xml raw ,Binary base64,ELEMENTS XSINIL)
   set  @DMLType= 'A' ;
   end
else ----Delete
   begin
       select  @XMLData=( select  * from  deleted  for  xml raw,binary base64,ELEMENTS XSINIL)
       set  @DMLType= 'D' ;
   end
  if (@XMLData  is  not  null )
  begin
      exec UP_SyncDataSendMsg @PrimaryKeyField,@TableName,@DMLType,@XMLData;
  end
  go

 

3.2.12 目标数据库中创建,字符分割函数

该函数主要是用来进行字符分割,用来处理主键有多个字段的情况。

--目标数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
use DBTo
go
--转换用‘,'分割的字符串@str
create Function dbo.uf_SplitString
(
@str nvarchar(max),
@Separator nchar(1)= ','
)
returns nvarchar(2000)
as
begin
    declare @Fields xml;--结果字段列表
   declare @Num  int ;-----记录循环次数
   declare @Pos  int ;-----记录开始搜索位置
    declare @NextPos  int ;--搜索位置临时变量
    declare @FieldValue nvarchar(256);--搜索结果
    set  @Num=0;
    set  @Pos=1;
    set  @Fields=CONVERT(xml, '' );
    while  (@Pos<=LEN(@Str)) 
       begin
         select  @NextPos=CHARINDEX(@Separator,@Str,@Pos)
         if (@NextPos=0 OR @NextPos  is  null )
             select  @NextPos=LEN(@Str)+1;
         select  @FieldValue=RTRIM(ltrim(substring(@Str,@Pos,@NextPos-@Pos)))
         select  @Pos=@NextPos+1
         set  @Num=@Num+1;
         if  @FieldValue<>  ''
            begin
              set  @Fields.modify( 'insert {sql:variable("@FieldValue")} as last into /Fields[1]' );          
            end  
       end
       return  Convert(nvarchar(2000),@Fields);
end
go

 

3.2.13 将解析过的消息信息,根据操作类型的不同同步到数据表中

    这是所有的数据同步中最关键也是最复杂的一步了,在整个开发的过程中,大部分时间都花在这上边了,具体的操作都在下面解释的很清楚了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
--将XML数据源中的数据同步到数据表中(包括增删改)
 
Use DBTo
go
create function dbo.UF_XMLDataSourceToSQL
(
    @DataSource XML,--数据源
    @TableName varchar(128),--同步数据表名称
    @PrimaryKeyField varchar(128),--需要同步的表的主键,主键为多个时用‘,'隔开
    @DMLType  char (1) --A:新建;U:编辑;D:删除
)
returns nvarchar(4000)
as
begin
     --变量声明及数据初始化
     --声明数据表@TableName列Column相关信息变量
     declare @ColumnName nvarchar(128),@DataType nvarchar(128),@MaxLength  int ;
     --声明用于拼接SQL的变量
    declare @FieldsList nvarchar(4000),@QueryStatement nvarchar(4000);
     declare @Sql nvarchar(4000);
     declare @StrLength  int ;
     --变量初始化
     set  @FieldsList= '  ' ;--初始化变量不为 null ,否则对变量使用 '+=' 操作符无效
   set  @QueryStatement= '  ' ;
     --主键信息,根据参数求解如:ID1ID2
     declare @PKs xml;
     --当前字段是否主键-在‘更新’,‘删除’同步数据时使用
     declare @IsPK nvarchar(128);
     --初始化游标--游标内容包括目标数据表TableName列信息
     DECLARE ColumnNameList CURSOR FOR SELECT COLUMN_NAME,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=@TableName AND
DATA_TYPE<>
'xml' ;
    --数据处理
     if  @DMLType= 'A' --插入数据
        begin
           open ColumnNameList
              fetch ColumnNameList  into  @ColumnName,@DataType,@MaxLength;
              while  @@FETCH_STATUS=0
                begin
                --判断数据源列中是否存在属性:@ColumnName
                --判断数据源列中是否存在--元素:@ColumnName
                If @DataSource.exist( '/row/*[local-name()=sql:variable("@ColumnName")]' )=1
                  begin
                     --拼接SQL
                     set  @FieldsList+=(@ColumnName+ ',' );
                    set  @QueryStatement+=( 'T.c.value(' '(./' +@ColumnName+ '[not(@xsi:nil)])[1]' ',' '' +@DataType);--元素读取(包含空值情况)
                     if 

你可能感兴趣的:(关于数据同步的几种实现)