《Replication的犄角旮旯》系列导读
Replication的犄角旮旯(一)--变更订阅端表名的应用场景
Replication的犄角旮旯(二)--寻找订阅端丢失的记录
Replication的犄角旮旯(三)--聊聊@bitmap
Replication的犄角旮旯(四)--关于事务复制的监控
Replication的犄角旮旯(五)--关于复制identity列
Replication的犄角旮旯(六)-- 一个DDL引发的血案(上)(如何近似估算DDL操作进度)
Replication的犄角旮旯(七)-- 一个DDL引发的血案(下)(聊聊logreader的延迟)
Replication的犄角旮旯(八)-- 订阅与发布异构的问题
Replication的犄角旮旯(九)-- sp_setsubscriptionxactseqno,赋予订阅活力的工具
---------------------------------------华丽丽的分割线--------------------------------------------
关于replication中的bitmap,貌似介绍的文档不多;本文将从对此参数做一初步的简析,并介绍如何利用这个参数处理一些特定环境下的问题;
再次强调, 本方法虽多次经受验证无误,但多次被MS supporter们建议不要尝试使用此方法,还望各位DBA三思!
先来看看@bitmap在哪里出现
我们先创建一个表的复制订阅,表结构如下
1
USE
[
test_aaa
]
2
GO
3
4
/*
***** Object: Table [dbo].[test_b] Script Date: 2014/1/23 16:12:28 *****
*/
5
SET
ANSI_NULLS
ON
6
GO
7
8
SET
QUOTED_IDENTIFIER
ON
9
GO
10
11
SET
ANSI_PADDING
ON
12
GO
13
14
CREATE
TABLE
[
dbo
]
.
[
test_b
]
(
15
[
id1
]
[
int
]
NOT
NULL
,
16
[
id2
]
[
int
]
NOT
NULL
,
17
[
id3
]
[
int
]
NOT
NULL
,
18
[
id4
]
[
int
]
NOT
NULL
,
19
[
name
]
[
varchar
]
(
10
)
NULL
,
20
[
remark1
]
[
varchar
]
(
100
)
NULL
,
21
[
remark2
]
[
varchar
]
(
100
)
NULL
,
22
[
remark3
]
[
varchar
]
(
100
)
NULL
,
23
[
remark4
]
[
varchar
]
(
100
)
NULL
,
24
CONSTRAINT
[
pk_id1_id2_id3_id4
]
PRIMARY
KEY
CLUSTERED
25
(
26
[
id1
]
ASC
,
27
[
id2
]
ASC
,
28
[
id3
]
ASC
,
29
[
id4
]
ASC
30
)
WITH
(PAD_INDEX
=
OFF
, STATISTICS_NORECOMPUTE
=
OFF
, IGNORE_DUP_KEY
=
OFF
, ALLOW_ROW_LOCKS
=
ON
, ALLOW_PAGE_LOCKS
=
ON
)
ON
[
PRIMARY
]
31
)
ON
[
PRIMARY
]
32
33
GO
34
35
SET
ANSI_PADDING
OFF
36
GO
到订阅库的存储过程中,找到sp_MSupd_dbotest_b,生成脚本
1
USE
[
test_byxl1
]
2
GO
3
/*
***** Object: StoredProcedure [dbo].[sp_MSupd_dbotest_b] Script Date: 2014/1/23 14:28:46 *****
*/
4
SET
ANSI_NULLS
ON
5
GO
6
SET
QUOTED_IDENTIFIER
ON
7
GO
8
ALTER
procedure
[
dbo
]
.
[
sp_MSupd_dbotest_b
]
9
@c1
int
=
NULL
,
10
@c2
int
=
NULL
,
11
@c3
int
=
NULL
,
12
@c4
int
=
NULL
,
13
@c5
varchar
(
10
)
=
NULL
,
14
@c6
varchar
(
100
)
=
NULL
,
15
@c7
varchar
(
100
)
=
NULL
,
16
@c8
varchar
(
100
)
=
NULL
,
17
@c9
varchar
(
100
)
=
NULL
,
18
@pkc1
int
=
NULL
,
19
@pkc2
int
=
NULL
,
20
@pkc3
int
=
NULL
,
21
@pkc4
int
=
NULL
,
22
@bitmap
binary
(
2
)
23
as
24
begin
25
if
(
substring
(
@bitmap
,
1
,
1
)
&
1
=
1
)
or
26
(
substring
(
@bitmap
,
1
,
1
)
&
2
=
2
)
or
27
(
substring
(
@bitmap
,
1
,
1
)
&
4
=
4
)
or
28
(
substring
(
@bitmap
,
1
,
1
)
&
8
=
8
)
29
begin
30
update
[
dbo
]
.
[
test_b
]
31
set
[
id1
]
=
case
substring
(
@bitmap
,
1
,
1
)
&
1
when
1
then
@c1
else
[
id1
]
end
,
32
[
id2
]
=
case
substring
(
@bitmap
,
1
,
1
)
&
2
when
2
then
@c2
else
[
id2
]
end
,
33
[
id3
]
=
case
substring
(
@bitmap
,
1
,
1
)
&
4
when
4
then
@c3
else
[
id3
]
end
,
34
[
id4
]
=
case
substring
(
@bitmap
,
1
,
1
)
&
8
when
8
then
@c4
else
[
id4
]
end
,
35
[
name
]
=
case
substring
(
@bitmap
,
1
,
1
)
&
16
when
16
then
@c5
else
[
name
]
end
,
36
[
remark1
]
=
case
substring
(
@bitmap
,
1
,
1
)
&
32
when
32
then
@c6
else
[
remark1
]
end
,
37
[
remark2
]
=
case
substring
(
@bitmap
,
1
,
1
)
&
64
when
64
then
@c7
else
[
remark2
]
end
,
38
[
remark3
]
=
case
substring
(
@bitmap
,
1
,
1
)
&
128
when
128
then
@c8
else
[
remark3
]
end
,
39
[
remark4
]
=
case
substring
(
@bitmap
,
2
,
1
)
&
1
when
1
then
@c9
else
[
remark4
]
end
40
where
[
id1
]
=
@pkc1
and
[
id2
]
=
@pkc2
and
[
id3
]
=
@pkc3
and
[
id4
]
=
@pkc4
41
if
@@rowcount
=
0
42
if
@@microsoftversion
>
0x07320000
43
exec
sp_MSreplraiserror
20598
44
end
45
else
46
begin
47
update
[
dbo
]
.
[
test_b
]
48
set
[
name
]
=
case
substring
(
@bitmap
,
1
,
1
)
&
16
when
16
then
@c5
else
[
name
]
end
,
49
[
remark1
]
=
case
substring
(
@bitmap
,
1
,
1
)
&
32
when
32
then
@c6
else
[
remark1
]
end
,
50
[
remark2
]
=
case
substring
(
@bitmap
,
1
,
1
)
&
64
when
64
then
@c7
else
[
remark2
]
end
,
51
[
remark3
]
=
case
substring
(
@bitmap
,
1
,
1
)
&
128
when
128
then
@c8
else
[
remark3
]
end
,
52
[
remark4
]
=
case
substring
(
@bitmap
,
2
,
1
)
&
1
when
1
then
@c9
else
[
remark4
]
end
53
where
[
id1
]
=
@pkc1
and
[
id2
]
=
@pkc2
and
[
id3
]
=
@pkc3
and
[
id4
]
=
@pkc4
54
if
@@rowcount
=
0
55
if
@@microsoftversion
>
0x07320000
56
exec
sp_MSreplraiserror
20598
57
end
58
end
看到这么多@bitmap,是不是有种升仙的感觉?
@bitmap 是binary类型,即二进制串;简单来说,它是用来表示所操作的字段位置的参数,通过@bitmap,分发代理从distribution.dbo.msrepl_commands中读取命令时(update操作),才会知道哪些列进行了更新;
我们先来解析一下这个存储过程;
1、根据表结构的code,我们知道这个表共有9个字段,其中id1~id4被定义为联合主键;
由于binary(1)表示1个字节(8位的2进制),因此我们表示9个字段的@bitmap就只能用binary(2)来容纳了;
其次,有的童鞋说,他们看到的update存储过程只有一个程序段,而我的例子中有两部分(29行~44行、46行~57行)。这个是由于存在联合主键造成的;即当被订阅的表中含有联合主键(2个或以上的字段一同作为主键)的时候才会出现两段代码,前者是更新主键列,后者则是更新非主键列;
2、根据更新列的位置不同,@bitmap中的对应的值也不同;
substring(@bitmap,1,1) & 1 = 1 表示第一列有更新;
substring(@bitmap,1,1) & 2 = 2 表示第二列有更新;
substring(@bitmap,1,1) & 4 = 4 表示第三列有更新;
以此类推
substring(@bitmap,1,1) & 128 = 128 表示第八列有更新;
那第九位呢? =256么? 由于1个字节只有8位,而128=2^7,当第九位出现时就要进位了
substring(@bitmap,2,1) & 1 = 1
怎么样,不难理解吧?
定义4个字段的联合主键只是为了举例说明的时候方便一些,实际的生产环境中可能不太经常能遇到;
再来看一下@bitmap在哪里可以获取到呢?我先更新一条记录,更新之前先关闭相应的分发代理(此处不需要分发命令应用到订阅端)
我们去distribution里看看具体的分发命令(具体做法请见《 Replication的犄角旮旯(二)--寻找订阅端丢失的记录 》)
从命令中我们可以看出,更新的列位置为第5(name)、7(remark2)、8(remark3)列,按照二进制的表示方法为(注意反取,即←表示第1至第9位)
0 1101 0000
由于第九位没有更新,因此为0,所以bitmap就是 1101 0000,换成十六进制就是0xD0,由于bitmap超出1个字节,因此后面再补0,就是我们看到的0xd000了
可能看到这里,有些童鞋会说,这太麻烦了,遇到一个很宽的表,光数逗号就数死了……尽管我们明白逗号是为了分割字段的,但系统为什么会这样生成呢,这个问题可以参考: http://msdn.microsoft.com/zh-cn/library/ms152489%28v=sql.120%29.aspx
这个和article的属性,调用订阅端存储过程方式有关(SCALL),这个不是本文的重点,在此不做赘述;
根据上面的算法,我们就可以知道,当我们要更新一个表时,可以根据更新列的位置,推算出实际的bitmap值,但这又有什么用呢?
-------------------------------------我是华丽丽的分割线-------------------------------------
应用场景:一个表(还是说商品表吧,比较典型),保存商品信息、简介等内容,都是varchar(max)或text类型;商家在促销活动前通常会大批量的更新这些内容(比如加个促销活动介绍等等),更新大字段是复制环境中最头疼的问题;
由于一条复制命令有长度限制(1K左右),如果一条更新记录中的更新内容过大,就会被拆分成多条命令写到msrepl_commands中(我曾经碰到过1条记录的更新操作被拆成100个复制命令),如果高峰时期有用户大量的进行这种操作,那作为DBA就可以升级为“张三疯”了;
这时候我们就可以根据具体的update命令(一般除了更新必要的字段外,还会捎带更新updatetime这样的时间戳,具体就去找研发兄弟们要吧),计算出相应的bitmap,然后在相应的存储过程中加个判断,屏蔽掉这样的操作;
具体操作就是,在sp_MSupd_dbotest_b这里的第24、25行之间加上
if @bitmap=0xd000
return;
再打开分发代理,这样就屏蔽了@bitmap=0xd000的全部操作;
按照前面的操作,并没有进行下面的更新
而我在此之后又从发布服务器insert了一条主键为2,2,2,2的记录,以证明该操作在update之后已经传到了订阅端,而update被屏蔽了;
需要注意的是,由于按位操作是绝对严格,对于只更新column1和同时更新column1、column2将产生不同的bitmap,操作时一定要谨慎;

