关于数据同步主要有两个层面的同步,一是通过后台程序编码实现数据同步,二是直接作用于数据库,在数据库层面实现数据的同步。通过程序编码实现数据同步,其主要的实现思路很容易理解,即有就更新,无则新增,其他情况日志记录,就不做过多的介绍,这里主要讲述的是第二个层面的数据同步,即在数据库层面实现数据同步。
数据库层面的数据库同步主要有三种方式:通过发布/订阅的方式实现同步,通过SQL JOB方式实现数据同步,通过Service Broker 消息队列的方式实现数据同步。
下面分别就这三种数据同步方式,一一详解。
发布/订阅是Sql Server自带的一种数据库备份的机制,通过该机制可以快速的实现数据的备份同步,不用编写任何的代码。
此种数据同步的方式存在的以下的一些问题:
总的来说,这种数据备份同步的方式,在表结构一致、数据量不是特别大的情况下还是非常高效的一种同步方式。
网上有很多的关于如何使用发布/订阅的方式实现数据同步的操作示例,这里就不再重复的演示了,有兴趣想要了解的朋友可以参考下面这篇文章:
http://kb.cnblogs.com/page/103975/
通过Sql Job定时作业的方式实现同步其基本原理就是通过目标服务器和源服务器的连接,然后通过编写Sql语句,从源服务器中读取数据,再更新到目标服务器。
这种数据同步的方式比较灵活。创建过sql定时作业之后,主要需要执行以下关键的两步。
不同数据库之间的连接可以通过系统的存储过程实现。下面就直接用一个示例来讲一下如何创建数据库连接。
--添加一个连接
--系统存储过程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'
主要的同步思路:
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
SQL Server Service Broker 是数据库引擎的组成部分,为 SQL Server 提供队列和可靠的消息传递。既可用于使用单个 SQL Server 实例的应用程序,也可用于在多个实例间分发工作的应用程序。
在单个 SQL Server 实例内,Service Broker 提供了一个功能强大的异步编程模型。数据库应用程序通常使用异步编程来缩短交互式响应时间,并增加应用程序总吞吐量。
在多个SQL Server实例之间Service Broker 还可以提供可靠的消息传递服务。Service Broker 可帮助开发人员通过称为服务的独立、自包含的组件来编写应用程序。需要使用这些服务中所包含功能的应用程序可以使用消息来与这些服务进行交互。Service Broker 使用 TCP/IP 在实例间交换消息。Service Broker 中所包含的功能有助于防止未经授权的网络访问,并可以对通过网络发送的消息进行加密。
在这一小节里,主要是通过一个完整的数据同步的流程向大家演示,如何实现同一个数据库实例不同数据库的数据同步。关于不同的数据库实例间的数据库的数据同步整体上跟同一个实例的数据库同步是一样的,只不过在不同的数据库实例间同步时还需启用传输安全、对话安全,创建路由、远程服务绑定等额外的操作。
这里边用到了大量的SQL Server XML的东西,如果有不理解的地方可以参考以下链接:http://www.cnblogs.com/Olive116/p/3355840.html
这是我在做技术准备时,自己的一点学习记录。
下面就是具体的实现步骤:
这一步主要是用来对要进行数据同步的数据启用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
|
这一步主要用来创建数据库主密匙,上边有提到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
|
这里主要用来创建消息类型和消息协定,源数据库和目标数据库的消息类型和协定都要一致。
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
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
|
创建之后效果如下图:
这里我们通过利用上边创建的消息协定和消息队列来创建数据同步的服务。
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
|
创建后效果如下图:
这里需要在源数据库上创建一个服务配置列表,主要用来保存之前创建过的服务名称,本例只是用来演示,所以只创建了一个服务,只能是同步一个数据表,如果有多个数据表需要同步,则需创建多个服务,所以这里创建一个服务配置列表,用来存储多个服务的服务名称。
需要注意的是,下面的脚本在执行完创建表的操作之后又插入了一条数据,也就是上边我们创建的服务名,如果有多个服务的话,依次插入该表即可。
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
|
效果如下图:
这里创建了一个存储过程主要用来发送同步消息,该消息内容主要包括操作类型、主键、表名、正文内容,分别对应@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
);
SET @XMLData.modify(
'insert
);
SET @XMLData.modify(
'insert
);
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
|
这里创建该表主要用来记录在数据同步过程中出现的异常信息。
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
|
效果如下图:
这里主要用来在源数据库中接收队列中的消息,将同时出错的信息,解析一下,然后插入到异常信息记录表里边。
--数据同步回馈
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,
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.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
|
这里就以用户表为例,具体操作如下,这里通过查询系统的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
|
该函数主要是用来进行字符分割,用来处理主键有多个字段的情况。
--目标数据库
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
);
end
end
return
Convert(nvarchar(2000),@Fields);
end
go
|
这是所有的数据同步中最关键也是最复杂的一步了,在整个开发的过程中,大部分时间都花在这上边了,具体的操作都在下面解释的很清楚了。
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=
' '
;
--主键信息,根据参数求解如:
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
'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
|