SQL Server Replication II

Sql Server Replication Scripting Setup

  1. 大概的一个拓扑, 我们将三种角色分别安装在三台不同的 sql server 服务器上
    1.1 distributor
    1.2 publisher, publication, article
    1.3 subscriber , subscription
    1.4 Agents , Schedulers

  2. 简单的一个实现, 以 snapshot replication 为例子

2.1 distributor script :
我们会在 distributor 角色的 sql server 服务器上,将这台服务器设置为 distributor ,建立 distribution 数据库, 并配置一个允许使用它作为 distributor 的 publisher。

2.1.1 sp_adddistributor 第一次执行的时候,必须指定password, 这个 password 是 distributor_admin 密码。 在 publisher 连接 distributor 的时候,也必须指定这个 distributor_admin 的密码,用来通信。下面这个例子其实还需要为 sp_adddistributor 参数 @password 赋值.

2.1.2 三大要素: distributor 所用到的 Instance , 以 serverName\instanceName 命名; distributor 用到的数据库 distribution (名字可以更改); Snapshot 用到的存储路径.

-- This script uses sqlcmd scripting variables. They are in the form
-- $(MyVariable). For information about how to use scripting variables
-- on the command line and in SQL Server Management Studio, see the
-- "Executing Replication Scripts" section in the topic
-- "Programming Replication Using System Stored Procedures".
-- Install the Distributor and the distribution database.
DECLARE @distributor AS sysname;
DECLARE @distributionDB AS sysname;
DECLARE @publisher AS sysname;
DECLARE @directory AS nvarchar(500);
DECLARE @publicationDB AS sysname;
-- Specify the Distributor name.
SET @distributor = $(DistPubServer);
-- Specify the distribution database.
SET @distributionDB = N'distribution';
-- Specify the Publisher name.
SET @publisher = $(DistPubServer);
-- Specify the replication working directory.
SET @directory = N'\\' + $(DistPubServer) + '\repldata';
-- Specify the publication database.
SET @publicationDB = N'AdventureWorks2008R2';
-- Install the server MYDISTPUB as a Distributor using the defaults,
-- including autogenerating the distributor password.
USE master
EXEC sp_adddistributor @distributor = @distributor;
-- Create a new distribution database using the defaults, including
-- using Windows Authentication.
USE master
EXEC sp_adddistributiondb @database = @distributionDB,
@security_mode = 1;
GO
-- Create a Publisher and enable AdventureWorks2008R2 for replication.
-- Add MYDISTPUB as a publisher with MYDISTPUB as a local distributor
-- and use Windows Authentication.
DECLARE @distributionDB AS sysname;
DECLARE @publisher AS sysname;
-- Specify the distribution database.
SET @distributionDB = N'distribution';
-- Specify the Publisher name.
SET @publisher = $(DistPubServer);
USE [distribution]
EXEC sp_adddistpublisher @publisher=@publisher,
@distribution_db=@distributionDB,
@security_mode = 1;
GO

2.2 publication script , article script

2.2.1 首先要做的两点,就是:一启动 publisher 的角色;二配置要使用的 distributor . 这里使用到的存储过程 sp_replicationdboption .

2.2.2 在第一步里指定的 replication database, 执行 sp_addpublication 来添加 publication.

-- Create a new transactional publication with the required properties.
EXEC sp_addpublication
@publication = @publication,
@status = N'active',
@allow_push = N'true',
@allow_pull = N'true',
@independent_agent = N'true';
-- Create a new snapshot job for the publication, using a default schedule.
EXEC sp_addpublication_snapshot
@publication = @publication,
@job_login = @login,
@job_password = @password,
-- Explicitly specify the use of Windows Integrated Authentication (default)
-- when connecting to the Publisher.
@publisher_security_mode = 1;
GO

2.2.3 添加 article

DECLARE @publication AS sysname;
DECLARE @table AS sysname;
DECLARE @filterclause AS nvarchar(500);
DECLARE @filtername AS nvarchar(386);
DECLARE @schemaowner AS sysname;
SET @publication = N'AdvWorksProductTran';
SET @table = N'Product';
SET @filterclause = N'[DiscontinuedDate] IS NULL';
SET @filtername = N'filter_out_discontinued';
SET @schemaowner = N'Production';
-- Add a horizontally and vertically filtered article for the Product table.
-- Manually set @schema_option to ensure that the Production schema
-- is generated at the Subscriber (0x8000000).
EXEC sp_addarticle
@publication = @publication,
@article = @table,
@source_object = @table,
@source_owner = @schemaowner,
@schema_option = 0x80030F3,
@vertical_partition = N'true',
@type = N'logbased',
@filter_clause = @filterclause;
-- (Optional) Manually call the stored procedure to create the
-- horizontal filtering stored procedure. Since the type is
-- 'logbased', this stored procedures is executed automatically.
EXEC sp_articlefilter
@publication = @publication,
@article = @table,
@filter_clause = @filterclause,
@filter_name = @filtername;
-- Add all columns to the article.
EXEC sp_articlecolumn
@publication = @publication,
@article = @table;
-- Remove the DaysToManufacture column from the article
EXEC sp_articlecolumn
@publication = @publication,
@article = @table,
@column = N'DaysToManufacture',
@operation = N'drop';
-- (Optional) Manually call the stored procedure to create the
-- vertical filtering view. Since the type is 'logbased',
-- this stored procedures is executed automatically.
EXEC sp_articleview
@publication = @publication,
@article = @table,
@filter_clause = @filterclause;
GO

2.3 subscription script 以 push subscription 为例子. 所有的操作都在publisher, publication 里面执行。

2.3.1 判断 publication 是不是可以被 push 或者 pull
Sp_helppublication
2.3.3 添加 push subscription
Sp_addsubscription
2.3.4 添加 push distributor agent
Sp_addpushsubscription_agent
默认是一天执行一次 snapshot push over ,那么怎么去修改这个同步间隔呢?
Sp_add_schedule

  1. 监控健康指标
    3.1 Replication Monitor

  2. 移除 replication , 察看 distribution 数据库元数据的更改

4.1 先移除 subscriber 和 subscription
Sp_dropsubscription( publication database), sp_subscription_cleanup(subscriber database)

4.2 再移除 publisher 和 publication

4.3 再移除 distributor
4.4 细节解说

To disable publishing and distribution
Stop all replication-related jobs. For a list of job names, see the “Agent Security Under SQL Server Agent” section of Replication Agent Security Model.
At each Subscriber on the subscription database, execute sp_removedbreplication to remove replication objects from the database. This stored procedure will not remove replication jobs at the Distributor.
At the Publisher on the publication database, execute sp_removedbreplication to remove replication objects from the database.
If the Publisher uses a remote Distributor, execute sp_dropdistributor.
At the Distributor, execute sp_dropdistpublisher. This stored procedure should be run once for each Publisher registered at the Distributor.
At the Distributor, execute sp_dropdistributiondb to delete the distribution database. This stored procedure should be run once for each distribution database at the Distributor. This also removes any Queue Reader Agent jobs associated with the distribution database.
At the Distributor, execute sp_dropdistributor to remove the Distributor designation from the server.

当Publisher, Distributor, Subscriber都配置好以后,我们可以通过下面的脚本来查看各个服务器充当的角色,以及配置的属性:

  1. 查看服务器的角色:
select server_id,name ,is_remote_login_enabled,is_system,is_publisher,is_subscriber,is_distributor,is_nonsql_subscriber from sys.servers ;
select name,is_published,is_subscribed,is_merge_published,is_distributor from sys.databases where name in( 'siebeldb','distribution')

配置原理详细解析:

**

1. Distributor :

** Distributor在每一个replication环境中都必须存在,它的作用是保存snapshot replication, 在publisher, subscriber之间通信,从publisher接收replication,并分发到各个subscriber上。在配置distributor的时候,特别需要注意的两件事就是配置保存snapshot replication文件的文件夹,第二就是指定是否是远程distributor,一般来说都会用一个专门的server来做distributor,来隔离与业务系统的资源利用冲突。

Step 1 指定一个server为distributor角色

     sp_adddistributor [ @distributor= ] 'distributor'   
[ , [ @heartbeat_interval= ] heartbeat_interval ]
[ , [ @password= ] 'password' ]
[ , [ @from_scripting= ] from_scripting ]

[ @distributor=] ‘distributor’ Is the distribution server name. distributor is sysname, with no default. This parameter is only used if setting up a remote Distributor. It adds entries for the Distributor properties in the msdb..MSdistributor table.

如果distributor对于publisher来说是台远程服务器,不是本机instance上的服务器,那么我们就需要配置@distributor这个参数。相关属性的值也会被记录在MSDB..Msdistributor表里面。特别需要注意的是,这个命令是运行在distributor上面的,而不是用来给publisher添加一个distributor。这个命令会将本服务器标示为distributor,并且在MSDB..Msdistributor里面添加一个distributor的配置信息,如果是单独的一台服务器,那么就会给这个服务器贴一个专门是distributor的标签。既然已经涉及到服务器层面,这就意味着整个instance就只能配置一台数据库专用来做distribution.

[ @heartbeat_interval=] heartbeat_interval
Is the maximum number of minutes that an agent can go without logging a progress message. heartbeat_interval is int, with a default of 10 minutes. A SQL Server Agent job is created that runs on this interval to check the status of the replication agents that are running.

这里的心跳设置用来检查replication各个代理之间通信的状况,默认是10分钟。当新加一个distributor之后会自动创建一个Job来实现心跳的功能。这个属性会在MSDB..Msdistributor表里面记录下来。

[ @password=] ‘password’]
Is the password of the distributor_admin login. password is sysname, with a default of NULL. If NULL or an empty string, password is reset to a random value. The password must be configured when the first remote distributor is added. distributor_admin login and password are stored for linked server entry used for a distributor RPC connection, including local connections. If distributor is local, the password for distributor_admin is set to a new value. For Publishers with a remote Distributor, the same value for password must be specified when executing sp_adddistributor at both the Publisher and Distributor. sp_changedistributor_password can be used to change the Distributor password.

如果Distributor是建立在远程服务器上的,那么我们需要为distributor_admin这个用户设定一个密码。这个密码在publisher配置distributor的时候,也需要指定。

[ @from_scripting= ] from_scripting
Identified for informational purposes only. Not supported. Future compatibility is not guaranteed.
返回值:0-成功;1-失败

上面的脚本还只是用来配置一个角色,但是这个角色对应的数据库还没有真正建立起来,下面的脚本就是用来创建distributor数据库的,而且事先我们不需要这个数据库存在。

这里要思考的问题是:1同一个instance上面可以创建多少个distribution数据库?2同一个distribution数据库可以支撑多少个publisher?

Step 2 创建一个distribution数据库

下面这个脚本是运行在distributor上的,为distributor创建一个distribution数据库,这个数据库的名字当然是可以任意指定的。前提是必须先运行sp_adddistributor配置distributor.

sp_adddistributiondb [ @database= ] 'database'   
    [ , [ @data_folder= ] 'data_folder' ]   
    [ , [ @data_file= ] 'data_file' ]   
    [ , [ @data_file_size= ] data_file_size ]   
    [ , [ @log_folder= ] 'log_folder' ]   
    [ , [ @log_file= ] 'log_file' ]   
    [ , [ @log_file_size= ] log_file_size ]   
    [ , [ @min_distretention= ] min_distretention ]   
    [ , [ @max_distretention= ] max_distretention ]   
    [ , [ @history_retention= ] history_retention ]   
    [ , [ @security_mode= ] security_mode ]   
    [ , [ @login= ] 'login' ]   
    [ , [ @password= ] 'password' ]   
    [ , [ @createmode= ] createmode ]  
    [ , [ @from_scripting = ] from_scripting ]  

[ @min_distretention=] min_distretention
Is the minimum retention period, in hours, before transactions are deleted from the distribution database. min_distretention is int, with a default of 0 hours.
[ @max_distretention=] max_distretention
Is the maximum retention period, in hours, before transactions are deleted. max_distretention is int, with a default of 72 hours. Subscriptions that have not received replicated commands that are older than the maximum distribution retention period are marked as inactive and need to be reinitialized. RAISERROR 21011 is issued for each inactive subscription. A value of 0 means that replicated transactions are not stored in the distribution database.

@min_distretention, @max_distretention, 指的是为重复事务保留的最小,最大时间长。如果某一个subscriber连最大值之前的重复日志都没有接收的话,这个subscriber就被标识为inactive,需要重新reinitialized.

[ @history_retention=] history_retention
Is the number of hours to retain history. history_retention is int, with a default of 48 hours.
[ @security_mode=] security_mode
Is the security mode to use when connecting to the Distributor. security_mode is int, with a default of 1. 0 specifies SQL Server Authentication; 1specifies Windows Integrated Authentication.
指定为0 ,表示采用的安全认证方式是SQL SERVER帐户认证;如果为1,说明采用的是Windows Server认证方式。值得思考的是,我们怎么可以指定这个数据库是既可以用Windows认证也可以用SQL Server帐户认证?

[ @login=] ‘login’
Is the login name used when connecting to the Distributor to create the distribution database. This is required if security_mode is set to 0. login issysname, with a default of NULL.
只有当Security_Mode指定为0的时候,需指定这个参数值。

[ @password=] ‘password’
Is the password used when connecting to the Distributor. This is required if security_mode is set to 0. password is sysname, with a default of NULL.

[ @createmode=] createmode
createmode is int, with a default of 1, and can be one of the following values.
这个参数默认是1,创建数据库或者使用现有的数据库,然后在这些数据库上调用instdist.sql来创建一些replication的对象,包括原数据表,存储过程,试图等。

虽说我们可以在配置这个distributor对应的数据库时候,直接创建数据库,但是我们还是倾向于先常规的创建一个数据库,指定一些特别的参数,然后配置这个数据库为distributor database.

当然我们要查询是不是distributor搭建成功,来决定是否需要配置其它的项目:

exec sp_get_distributor ;

返回一个数据集;
- Distribution db installed: distribution数据库是不是创建完成
- Is distribution publisher: distribution, publisher是不是在同一个instance上面
- Has remote distribution publisher:是否有远程服务器数据库作为Publisher存在

搭建完成distributor的时候我们需要再次检查属性配置或者我们新接手了一个repliaction环境需要熟悉这些配置,我们可以用下面的T-SQL脚本:
- 返回distributor, distribution数据库, working directory的配置信息: sp_helpdistributor ;
- 返回指定distribution数据库的信息: sp_helpdistributiondb

这两个脚本可以返回很多有用的信息,比如snapshot file location(working directory), history cleanup agent job , distribution cleanup agent job, 用来给publisher配置distributor的server name, login name等。

如果需要更改这些属性,可以用下面的脚本来执行:
1. At the Distributor, execute sp_changedistributor_propertyto modify Distributor properties.
2. At the Distributor, execute sp_changedistributiondbto modify distribution database properties.
3. At the Distributor, execute sp_changedistributor_passwordto change the Distributor password.
4. At the Distributor, execute sp_changedistpublisherto change the properties of a Publisher using the Distributor.

Step 3 指定一个publisher为distributor支持的 publisher

sp_adddistpublisher [ @publisher= ] 'publisher'   
        , [ @distribution_db= ] 'distribution_db'   
    [ , [ @security_mode= ] security_mode ]   
    [ , [ @login= ] 'login' ]   
    [ , [ @password= ] 'password' ]   
    [ , [ @working_directory= ] 'working_directory' ]   
    [ , [ @trusted= ] 'trusted' ]   
    [ , [ @encrypted_password= ] encrypted_password ]   
    [ , [ @thirdparty_flag = ] thirdparty_flag ]  
    [ , [ @publisher_type = ] 'publisher_type' ]  

这个脚本可以在distributor角色服务器的任何数据库上执行,因为distributing replication(分发副本)这个操作是服务器级别的,所以这个脚本一系列操作的本质也是作用在服务器上,因此在哪个数据内执行这个命令就无所谓了。

  1. @publisher这个参数尤其要注意,暂不敢确定到底是Publisher的instance名称还是其它
  2. @thirdparty_flag, @publisher_type两个参数用来指定publisher是不是非SQL SERVER数据库
  3. @working_directory,就是snapshot replication存在的地方

Step 4 为publisher配置一个可用的distributor(仅用在分布式replication中)

指定一个被distributor支持的publisher,和为publibsher配置一个可用的distributor是一个连通的过程,相当于企业之间互相签订合同一样,双方各执一份。在publisher上配置一个distributor的命令和在distributor上配置一个distributor一样:

sp_adddistributor [ @distributor= ] 'distributor'   
    [ , [ @heartbeat_interval= ] heartbeat_interval ]   
    [ , [ @password= ] 'password' ]   
[ , [ @from_scripting= ] from_scripting ]  

这里password就起到认证授权的作用了。

因为每增加一个distributor都会往sys.servers表里写入一个象征性的服务器名一样,这里面的服务器名带着各自的instance名称,同时我们会给distributor所在的服务器定义一个名称,比如repl_distributor。 这个名称很重要,暂时我们可以理解为是一台服务器的名称,其它服务器或者客户端都可以通过这个名称来连接,实际上是用在replication架构中。

Step 5 配置publisher,distributor, subscriber数据库的元数据

sp_replicationdboption [ @dbname= ] 'db_name'   
        , [ @optname= ] 'optname'   
        , [ @value= ] 'value'   
    [ , [ @ignore_distributor= ] ignore_distributor ]  
    [ , [ @from_scripting = ] from_scripting ]  

@optname,可以是:
- Merge publish: 指定数据库可以用作merge publication
- Publish:除了merge publish之外的publication
- Subscriber:指定数据库为subscription数据库
- Sync with backup
@value: false, 丢弃某个数据库的角色;true,设定某个数据库的角色
@ignore_distributor: false,可以不连接distributor来更新信息,比如若想丢弃publication数据库角色,而distributor又连不通,就需要false.

**

2 Publisher :

** 通过publication,我们可以将publisher数据库里的数据以及数据库对象发布到其它服务器的指定数据库里面,有些对象在merge publication中不适用,比如stored procedures –Execution(Transact-SQL, CLR), Indexed Views as Tables,除此之外,其它对象都可以在三种publication中使用,这些对象可以是 :

Tables, Partitioned Tables, Stored Procedures – Definition (Transact-SQL and CLR), Views, Indexed Views, User-Defined Types (CLR), User-Defined Functions (Transact-SQL and CLR), Alias Data Types, Full text indexes, Schema Objects (constraints, indexes, user DML triggers, extended properties, and collation).

在没有通过查找MSDN来获取publish细节之前,我们先自我假设下,正确搭建一个Publisher来publish一些Publication, 大概需要哪些步骤?
- 正确搭建一个distributor
- 在distributor上增加一个publisher的认证:sp_adddistpublisher
- 在publisher上指定我们要做publish的数据库
- 在publisher上配置我们要使用的distributor
- 创建一份publication
- 发布publication

这是replication架构的前半部分配置,之后还需要配置subscriber和subscription。最后需要配置的是读写分离的逻辑实现,概念参见《构建高性能Web站点》郭欣(著),采用了HAProxy机制,对读写SQL进行路由分离,写的SQL都放到publisher服务器上,而读SQL都转到subscriber服务器上,对subscriber做Network Load Balance.

步骤一写出来就知道自己往什么方向着手了,创建一份publication是有讲究的,怎么创建,哪些对象是可以被published的,都有哪些方式,怎么做全量,怎么做增量,publication的定义都存储在哪里? Publication的pipeline,过程控制的点,频率怎么控制,延迟如何管控,在本节都需要详细地阐述。

Publisher可以是和distributor在同一个instance上面,也可以是远程服务器上的某一个instance。搭建本地publisher和远程publisher的区别在哪里?

首先我们先来看下本地Publisher怎么搭建:
- 先搭建一个distributor,配置distributor服务器属性,创建distribution数据库
- 在distributor上指定一个publisher的认证sp_adddistpublisher
- 在distributor上指定一个数据库为publication数据库,设定某一种特定的publication方式,snapshot replication, transaction replication,merge replication

接着再分析下远程Publisher怎么搭建:
- 先搭建一个distributor,配置distributor服务器属性,创建distribution数据库
- 在distributor上指定一个publisher的认证sp_adddistpublisher
- 在publisher上配置一个distributor,指定一个数据库为publication数据库,设定某一种特定的publication方式,snapshot replication, transaction replication,merge replication

Distributor和publisher其实就类似内容生产商与分销商,必须一一签订合同。本地关系就简单些,远程关系就双方人手一份合同,有趣的是授权认证的方式,如果双方是通过私人掮客签订合同的,那么需要同一个掮客来回搭线,类似于security model为0,使用SQL SERVER 帐户密码;如果双方是通过中介公司签订合同的,那么同一个公司的任何业务员都能办理这桩事,所以security model可以设置为1,使用Windows帐户认证。

假如我现在已经搭好了distributor,也指定了publisher,无论是本地的还是远程的,那么这些元数据存储在哪里? 既然distributor,publisher都签订了合同,那么双方都应该有保存,这份保存就是我们要找的东西。

  • GUI方式: SSMS – Replication-Replication Monitor ; SQL Command prompt:sqlmonitor.exe
  • T-SQL方式:
    sp_replmonitorhelppublisher
    sp_replmonitorhelppublisher [ [ @publisher = ] ‘publisher’ ]
    [ , [ @refreshpolicy = ] refreshpolicy ]
    @publisher可以指定一个具体的服务器名,也可以不设置,返回包括所有的publisher。 返回的结果集要注意的就是status字段,它 有6个可能值,意思分别是: 1: started; 2:succeeded; 3:In progress; 4:Idle ;5:Retrying ;6:Failed .
    exec sp_helpdistributor

既然我们说了是双方合同,那么publisher上也应该有副本存在,sp_helpdistributor就可以帮我们查询到使用的distributor信息。

关于publication:

use distribution
go
exec sp_replmonitorhelppublication

上面这个脚本只能用来看哪些Publication 存在,以及对应的Job,但是publication具体定义看不到.

上面的说明讨论都还只是第一步,还没有进入到创建publication阶段,所以称之为准备阶段。接下来就讨论publication阶段了。
不同的publication使用的命令方式不同,总体上分为两种: snapshot或者transactional publication;merge publication. 在创建snapshot, transactional publication的时候我们使用sp_addpublication.

sp_addpublication [ @publication = ] 'publication'  
    [ , [ @taskid = ] tasked ]  
    [ , [ @restricted = ] 'restricted' ]  
    [ , [ @sync_method = ] 'sync_method' ]  
    [ , [ @repl_freq = ] 'repl_freq' ]  
    [ , [ @description = ] 'description' ]  
    [ , [ @status = ] 'status' ]  
    [ , [ @independent_agent = ] 'independent_agent' ]  
    [ , [ @immediate_sync = ] 'immediate_sync' ]  
    [ , [ @enabled_for_internet = ] 'enabled_for_internet' ]  
    [ , [ @allow_push = ] 'allow_push'  
    [ , [ @allow_pull = ] 'allow_pull' ]  
    [ , [ @allow_anonymous = ] 'allow_anonymous' ]  
    [ , [ @allow_sync_tran = ] 'allow_sync_tran' ]  
    [ , [ @autogen_sync_procs = ] 'autogen_sync_procs' ]  
    [ , [ @retention = ] retention ]  
    [ , [ @allow_queued_tran= ] 'allow_queued_updating' ]  
    [ , [ @snapshot_in_defaultfolder= ] 'snapshot_in_default_folder' ]  
    [ , [ @alt_snapshot_folder= ] 'alternate_snapshot_folder' ]  
    [ , [ @pre_snapshot_script= ] 'pre_snapshot_script' ]  
    [ , [ @post_snapshot_script= ] 'post_snapshot_script' ]  
    [ , [ @compress_snapshot= ] 'compress_snapshot' ]  
    [ , [ @ftp_address = ] 'ftp_address' ]  
    [ , [ @ftp_port= ] ftp_port ]  
    [ , [ @ftp_subdirectory = ] 'ftp_subdirectory' ]  
    [ , [ @ftp_login = ] 'ftp_login' ]  
    [ , [ @ftp_password = ] 'ftp_password' ]  
    [ , [ @allow_dts = ] 'allow_dts' ]  
    [ , [ @allow_subscription_copy = ] 'allow_subscription_copy' ]  
    [ , [ @conflict_policy = ] 'conflict_policy' ]  
    [ , [ @centralized_conflicts = ] 'centralized_conflicts' ]   
    [ , [ @conflict_retention = ] conflict_retention ]  
    [ , [ @queue_type = ] 'queue_type' ]  
    [ , [ @add_to_active_directory = ] 'add_to_active_directory' ]  
    [ , [ @logreader_job_name = ] 'logreader_agent_name' ]  
    [ , [ @qreader_job_name = ] 'queue_reader_agent_name' ]  
    [ , [ @publisher = ] 'publisher' ]   
    [ , [ @allow_initialize_from_backup = ] 'allow_initialize_from_backup' ]  
    [ , [ @replicate_ddl = ] replicate_ddl ]  
    [ , [ @enabled_for_p2p = ] 'enabled_for_p2p' ]  
    [ , [ @publish_local_changes_only = ] 'publish_local_changes_only' ]  
    [ , [ @enabled_for_het_sub = ] 'enabled_for_het_sub' ]  
    [ , [ @p2p_conflictdetection = ] 'p2p_conflictdetection' ]  
    [ , [ @p2p_originator_id = ] p2p_originator_id  
    [ , [ @p2p_continue_onconflict = ] 'p2p_continue_onconflict'  
    [ , [ @allow_partition_switch = ] 'allow_partition_switch'  
    [ , [ @replicate_partition_switch = ]'replicate_partition_switch'  

定义好一个publication,就要为publication增加article的定义,然后创建一个publication的snapshot,执行完这个snapshot的生成,接着就可以做transactional的增量了。
所以这里的问题就在于publication是否定为Transactional还是Snapshot了。不论是否定义publication为Transactional还是Snapshot,我们都需要做的一步就是生成这份Publication的Snapshot.

  • Sp_addpublication来定一个publication
    怎么控制这个publication是snapshot, transactional, merge publication? 首先在增加publication之前,我们应该先指定一个发布数据库 ,使用sp_repliactiondboption来启用某一个数据库的发布功能。

如果我们需要建立的是transactional replication,我们还需要创建一个logReader Agent(Job).
执行sp_addlogreader_agent来添加这个Agent Job,执行sp_helplogreader_agent可以查询到我们新建的这个Agent job. 每一个publication database只能有一个同账户的log reader agent job, 如果是非sql server数据库的publiaction database,那么必须设定特定账户用户及密码,而且连接publisher的授权认证模式也必须是0,@publisher_security_mode = 0.

接下来我们添加publication. 执行sp_addpublication, 设定是否可以被pull, push, 设置@repl_freq,为snapshot replication设置snapshot, 为transactional replication设置continuous. Transactional replication是默认值

这里摘抄MSDN上的一个小例子:

-- To avoid storing the login and password in the script file, the values 
-- are passed into SQLCMD as scripting variables. For information about 
-- how to use scripting variables on the command line and in SQL Server
-- Management Studio, see the "Executing Replication Scripts" section in
-- the topic "Programming Replication Using System Stored Procedures".
DECLARE @publicationDB AS sysname;
DECLARE @publication AS sysname;
DECLARE @login AS sysname;
DECLARE @password AS sysname;
SET @publicationDB = N'AdventureWorks'; 
SET @publication = N'AdvWorksProductTran'; 
-- Windows account used to run the Log Reader and Snapshot Agents.
SET @login = $(Login); 
-- This should be passed at runtime.
SET @password = $(Password); 
-- Enable transactional or snapshot replication on the publication database.
EXEC sp_replicationdboption 
  @dbname=@publicationDB, 
  @optname=N'publish',
  @value = N'true';
-- Execute sp_addlogreader_agent to create the agent job. 
EXEC sp_addlogreader_agent 
  @job_login = @login, 
  @job_password = @password,
  -- Explicitly specify the use of Windows Integrated Authentication (default) 
  -- when connecting to the Publisher.
  @publisher_security_mode = 1;
-- Create a new transactional publication with the required properties. 
EXEC sp_addpublication 
  @publication = @publication, 
  @status = N'active',
  @allow_push = N'true',
  @allow_pull = N'true',
  @independent_agent = N'true';
-- Create a new snapshot job for the publication, using a default schedule.
EXEC sp_addpublication_snapshot 
  @publication = @publication, 
  @job_login = @login, 
  @job_password = @password,
  -- Explicitly specify the use of Windows Integrated Authentication (default) 
  -- when connecting to the Publisher.
  @publisher_security_mode = 1;
GO
  • Sp_addpublication_snpashot来定义一个publication的snapshot
    为什么要定义这个publication的snapshot,既然可以生成snapshot,为什么还需要定义它,而不直接从publication中生成snapshot? 连着上面的分析,可知默认情况下,publication是transactional级别的,所以并没有一个全量的snapshot给到我们新建的transactional publication, 所以这里要为transactional publication新建一个snapshot. 其二,无论publication定义为哪种publication,都只是定义,没有实际执行的执行者。

这个地方隐藏的一点就是,当执行完这个命令之后,就新建了一个snapshot agent job.
这个snapshot的agent job创建,必须是在publisher database上进行,当我们为某一个publication创建一个snapshot agentj job的时候,publisher通过与distributor通信,把这个新建的agent job注册到了distributor。 如果是在非Publisher数据库上执行,则会出现下面的错误:
Msg 18757, Level 16, State 1, Procedure sp_MSrepl_addpublication_snapshot, Line 76
Unable to execute procedure. The database is not published. Execute the procedure in a database that is published for replication.

  • Sp_addarticle来往publication里面添加需要复制的对象和数据
    这么说来,publication与article之间是一对多的关系。而article是肯定不能被包含在不同的publication之间的,如果被包含在不同的publication之间,同步逻辑该怎么写?

我们以MSDN上的一个例子来阐述:
https://msdn.microsoft.com/en-us/library/ms173857.aspx
为sp_addarticle指定的参数主要有:
@publication: 一个数据库可能有多个publication,在这里我们要指定某一个具体的数据库里已有的publication;
@article: 指定一个 article名称;
@source_object:来指定要同步的对象
@destination_table: 如果target table/stored procedure不是同一个名字,就需要指定;
@type有很多种,比如logbased,同步表的时候就要指定logbased了;
@ins_cmd, @del_cmd,@upd_cmd就是用来更改同步逻辑的。详细参考:Specify How Changes Are Propagated for Transactional Articles ( https://msdn.microsoft.com/en-us/library/ms152489.aspx).

DECLARE @publication    AS sysname;
DECLARE @table AS sysname;
DECLARE @filterclause AS nvarchar(500);
DECLARE @filtername AS nvarchar(386);
DECLARE @schemaowner AS sysname;
SET @publication = N'AdvWorksProductTran'; 
SET @table = N'Product';
SET @filterclause = N'[DiscontinuedDate] IS NULL'; 
SET @filtername = N'filter_out_discontinued';
SET @schemaowner = N'Production';
-- Add a horizontally and vertically filtered article for the Product table.
-- Manually set @schema_option to ensure that the Production schema 
-- is generated at the Subscriber (0x8000000).
EXEC sp_addarticle 
  @publication = @publication, 
  @article = @table, 
  @source_object = @table,
  @source_owner = @schemaowner, 
  @schema_option = 0x80030F3,
  @vertical_partition = N'true', 
  @type = N'logbased',
  @filter_clause = @filterclause;
-- (Optional) Manually call the stored procedure to create the 
-- horizontal filtering stored procedure. Since the type is 
-- 'logbased', this stored procedures is executed automatically.
EXEC sp_articlefilter 
  @publication = @publication, 
  @article = @table, 
  @filter_clause = @filterclause, 
  @filter_name = @filtername;
-- Add all columns to the article.
EXEC sp_articlecolumn 
  @publication = @publication, 
  @article = @table;
-- Remove the DaysToManufacture column from the article
EXEC sp_articlecolumn 
  @publication = @publication, 
  @article = @table, 
  @column = N'DaysToManufacture', 
  @operation = N'drop';
-- (Optional) Manually call the stored procedure to create the 
-- vertical filtering view. Since the type is 'logbased', 
-- this stored procedures is executed automatically.
EXEC sp_articleview 
  @publication = @publication, 
  @article = @table,
  @filter_clause = @filterclause;
GO

Msg 156, Level 15, State 1: Incorrect syntax near the keyword ‘from’.
Msg 21745, Level 16, State 1, Procedure sp_MSrepl_articleview, Line 301
Cannot generate a filter view or procedure. Verify that the value specified for the @filter_clause parameter of sp_addarticle can be added to the WHERE clause of a SELECT statement to produce a valid query.
Msg 20027, Level 11, State 1, Procedure sp_MSrepl_articlecolumn, Line 181
The article ‘region’ does not exist.
如果没有使用任何的条件筛选,我们就不要在sp_addarticle里面指定filter,要不然就出现上面的错误。

  • Sp_startpubliaction_snapshot来生成一份publication snapshot
    这份publication snapshot是由什么来生成的,如果是snapshot replication,那么可以理解每次replication都是全量抽取的,如果是transactional repliaction,则这份snapshot应该只生成一次。
    由于snapshot生成一次耗时,耗网络宽带,如果基于数据库作snapshot还要锁表,不适合用来做日常的同步更新,这里我们要设置snapshot Agent job只跑一次。

在执行这段脚本的时候,特别要注意 job的执行者对working directory有可读可写权限,如果没有,我们可以修改这working directory :

exec sp_changedistpublisher
@publisher = 'VM-B9CB-CD02\MSSQLSERVER2014',
@property = 'working_directory',
@value = 'E:\Data_BU\snapshot' ;
--D:\data\data_srv\dbagroup\SQL2014\MSSQL12.MSSQLSERVER2014\MSSQL\ReplData

上面的脚本就是用来改写working directory.

  • Transactional Publication, Snapshot Publication是怎么驱动replication的
    我们知道replication,可以用push,pull的方式来进行,那么在publisher服务器上我们定义好publication, 添加完我们需要同步的对象与数据, 到这里配置Publication的工作就结束了
    当subscription也配置好之后,我们就可以启动相应的Agent Job来启用replication.

**

3 Subscriber:

**从订阅者的角度来说,主要做的事情就是pull publication,就是主动拉取publication. 那么Publisher要推publication该怎么配置? 一份subscription把publisher与subscriber连接起来,首先在distributor上肯定是定义一些subscribers,subscriber上是不是也需要授权distributor可以访问? 然后为每个subscriber配置一些subscription,在这个步骤中应该可以指定这份subscription是pull还是push形式,频率有多少。
- Synchronization agent : 同步代理Job在pull subscription时候,运行在subscriber服务器上,在push subcription的时候,运行在distributor服务器上:

不管哪种subscription方式,总有一样事情是先要在publisher上验证的,那就是要subscribe的publication, 它是不是支持pull或者push : 在publisher 上,执行 exec sp_helppublication来确定,需要修改这份publication使其支持push, pull,那么需要执行sp_changepublication

Pull subscription: 在subscriber上,执行sp_addpullsubscription. 这一步仅仅是定义subscription,要完成同步,还需要一个自动化的Job,这个Job就需要sp_addpullscription_agent来定义了,这个时候可以定义Job的同步频率。表面上看pull subscription是subscriber单方面的动作,但从完整性可追溯性上来看,还需要在publisher中保存一份他俩之间的“业务关系”,sp_addsubscription。

-- This script uses sqlcmd scripting variables. They are in the form
-- $(MyVariable). For information about how to use scripting variables  
-- on the command line and in SQL Server Management Studio, see the 
-- "Executing Replication Scripts" section in the topic
-- "Programming Replication Using System Stored Procedures".
-- Execute this batch at the Subscriber.
DECLARE @publication AS sysname;
DECLARE @publisher AS sysname;
DECLARE @publicationDB AS sysname;
SET @publication = N'AdvWorksProductTran';
SET @publisher = $(PubServer);
SET @publicationDB = N'AdventureWorks2012';
-- At the subscription database, create a pull subscription 
-- to a transactional publication.
USE [AdventureWorks2012Replica]
EXEC sp_addpullsubscription 
  @publisher = @publisher, 
  @publication = @publication, 
  @publisher_db = @publicationDB;
-- Add an agent job to synchronize the pull subscription.
EXEC sp_addpullsubscription_agent 
  @publisher = @publisher, 
  @publisher_db = @publicationDB, 
  @publication = @publication, 
  @distributor = @publisher, 
  @job_login = $(Login), 
  @job_password = $(Password);
GO

这个地方还要补一个 sp_addsubscription来在publisher上注册下这份subscription.

Push Subscription:在publisher上,执行sp_addsubscription, sp_addpushsubscription_agent,定义一份push subscription, 并且在distributor上增加一个同步的Job用来将publication推送到subscriber上面。

-

- This script uses sqlcmd scripting variables. They are in the form
-- $(MyVariable). For information about how to use scripting variables  
-- on the command line and in SQL Server Management Studio, see the 
-- "Executing Replication Scripts" section in the topic
-- "Programming Replication Using System Stored Procedures".
DECLARE @publication AS sysname;
DECLARE @subscriber AS sysname;
DECLARE @subscriptionDB AS sysname;
SET @publication = N'AdvWorksProductTran';
SET @subscriber = $(SubServer);
SET @subscriptionDB = N'AdventureWorks2012Replica';
--Add a push subscription to a transactional publication.
USE [AdventureWorks2012]
EXEC sp_addsubscription 
  @publication = @publication, 
  @subscriber = @subscriber, 
  @destination_db = @subscriptionDB, 
  @subscription_type = N'push';
--Add an agent job to synchronize the push subscription.
EXEC sp_addpushsubscription_agent 
  @publication = @publication, 
  @subscriber = @subscriber, 
  @subscriber_db = @subscriptionDB, 
  @job_login = $(Login), 
  @job_password = $(Password);
GO

对于每一个subscriber来说,针对每一份Publication只能订阅一次,不管是push方式还是pull方式,重复订阅就会有错误:
Msg 14058, Level 16, State 1, Procedure sp_MSrepl_addsubscription, Line 983
Cannot create the subscription because the subscription already exists in the subscription database. Only one subscription to the same publication is allowed in each subscription database. Drop the subscription and add it again if necessary. If the problem persists, replication metadata might be incorrect; see Books Online for troubleshooting information.

Push Subscription的distributor agent是建立在 Publisher上面的,一旦启用就continous的执行,所以只要每一次产生新的snapshot,或者transaction都能直接同步到subscriber database中去。

我们来看看这个push subscription distributor agent有哪些步骤:
Step1 Distribution Agent Startup Message: 在distribution database中的MSdistribution_history
记录这次的同步。
Step2 Run Agent – Replication Distributor: 没有T-SQL命令来执行这一步骤,我们从Agent Job 上唯一能得到的信息是: -Subscriber [VM-B9CB-CD02\MSSQLSERVER2014] -SubscriberDB [lenistest5] -Publisher [VM-B9CB-CD02\MSSQLSERVER2014] -Distributor [VM-B9CB-CD02\MSSQLSERVER2014] -DistributorSecurityMode 1 -PublisherDB [lenistest] -Continuous.
这个地方用distrib.exe来执行这一步。详细的概念这里有:
https://msdn.microsoft.com/en-us/library/ms147886.aspx.
Step3 Detect nonlogged agent shutdown

当我们需要给publication新加一个article时,整个过程是这样的:
1. sp_addarticle新加一个article 到publication
2. sp_changepublication修改immediate_sync为false, @force_invalidate_snapshot为1, @force_reinit_subscription为1
3. sp_addsubscription给subscription database新加这个article的subscription.

为存储过程添加article时,格式是这样的:

exec sp_addarticle
@publication = 'lenistest_snapshot',
@article = 'dbo.getregionname',
@source_object = 'getregionname',
@source_owner = 'dbo',
@type = 'proc schema only'
  • 对象及其数据的同步方式: 数据的同步机理与对象的同步机理。数据是通过什么逻辑被同步到subscriber对应的对象上,而对象的定义修改是怎么同步的
    Snapshot repliaction: 读取publication的完整性数据,调用bcp命令写到distributor的snapshot文件夹里面,然后同步到subscription database上面 ,重写所有 同步对象及其数据

Transaction replication:先 执行一边snapshot replication, 然后log reader agent会读取每一次的publication的事务操作,读到distribution database里面 ,等subscriber来拉或者publisher的push时间来推,这个 pull, push频率都是在建立Agent Job的时候都可以控制好了。 这里的transaction repliation可以看作是增量操作,先前的snapshot replication是一次全量操作,在执行第一次全量操作之前,transaction replication是会等待他的第一次snapshot repliaction完成的。我们要监控的就是哪些 subscriber正在等待完成它的第一次snapshot replication . 这种replication需要一个限制,就是在source table上必须定一个pk.

  • 同步审计: 什么方法用来确保同步对象与数据是健康的,延迟是可以接受的,一致性也没有问题?Continuous的设置方法可以持续的接收snapshot或者transaction replication.

4 Summary

  • Roles and Databases: 角色与数据库
    Publisher – Publication database
    Distributor – Distribution database
    Subscriber – Subscription database

  • Replication Agents: replication中使用到的Job: 这里少一个pull subscription的Job
    Snapshot Agent:
    Log Reader Agent :只在transactional replication中使用
    Distribution Agent: 只在transactional replication中使用
    Merge Agent:
    Queue Reader Agent bi-directional transactional replication
    Replication maintenance jobs:

5 Issues in replication

  • 当有数据库被用作repliaction的时候,我们不能drop掉这个数据库:
    Msg 3724, Level 16, State 3, Line 78
    Cannot drop the database ‘lenistest’ because it is being used for replication.
    这个时候,我们要移除这个数据库在replication中的角色:
    exec sp_replicationdboption
    @dbname = ‘lenistest’,@optname = ‘publish’,@value = false;
    在replication架构下移除数据库的角色,需要用到replication一系列的命令,也需要符合一定的条件,比如移掉所有的publication, subscription,最后才能禁用数据库的publication角色,最后才能被drop.

在搭建distributor的时候,我们要为数据库指定授权认证方式,这需要重新回顾SQL SERVER安全相关内容。

  • 安全认证: 解决的是访问数据库服务的权限。使用Windows账户认证和使用SQL Server账户认证。两种方式最后还是会对应到SQL SERVER内置的一些账户或新建一些对应帐户。

  • 授权访问:解决的是使用数据库对象及其数据的权限。可以只读,可以只写,也可以两者都有。核心解决方案是采用角色与用户映射机制。

如果让我作为一名DBA来设计一套授权方案,我会设计出这些角色:
只读访问者:只能访问已有数据;
可读可写访问者:可以读取所有数据,也可以修改任何数据;
数据库所有者:可以读取,修改任何数据,还可以创建其他数据库对象,比如表,试图,存储过程,用户,给用户赋予一定的权限,比如读写,创建对象等。

当这些角色设计好之后,我们再创建相关的用户,比如John,Lewis等,给他们赋予上面创建的这些角色。这些用户如果是用SQL SERVER账户登陆的,登录名就是用户名;如果是用Windows账户登陆的,登录名需要和用户名做映射。

赋予到用户或者角色上面的操作权限,我们可以认为是固定的,比如读,写,修改,删除,完全控制等。仅有操作权限也不够,这些权限是作用在数据库对象上面的,因为我们还要对每个角色或者用户的可访问数据库对象也有个限制。所以授权的过程其实就是对数据库对象及其权限作了一个矩阵。我们可以假设数据库对象也分门别类在矩阵里面,可以指定某一类数据库对象也可以特指某一个数据库对象。
我们看到数据库里会有一些内置角色,这些角色在我们创建数据库的时候就已经自动创建好了,那么我就来分析下这些内置角色的功能:通常我们将这些内置角色称之为fixed database roles,可以自定义的那些角色就称之为flexible database roles. 这些内置的fixed database roles分别有: db_owner, db_securityadmin, db_accessadmin, db_backupoperator, db_ddladmin, db_datawriter, db_datareader, db_denydatawriter, db_denydatareader. 已经在这些角色里面的用户可以将别的用户加入到这个角色里面。有趣的是,我们可以建立flexible database role, 将这些fixed database roles加到这些flexible database roles上面,这样更加灵活的来添加新用户。
通过下面命令,我们可以基本可以看到这些内置角色对应的权限了:

exec sp_helpdbfixedrole
exec sp_dbfixedrolepermission ;
exec sp_helprole ;
exec sp_helprolemember ;

上面的sp_helprolemember只能显示有member的role而没有member的role就不显示了。 接下来的脚本可以显示所有role的membership.

SELECT DP1.name AS DatabaseRoleName,
isnull (DP2.name, 'No members') AS DatabaseUserName
FROM sys.database_role_members AS DRM
RIGHT OUTER JOIN sys.database_principals AS DP1
ON DRM.role_principal_id = DP1.principal_id
LEFT OUTER JOIN sys.database_principals AS DP2
ON DRM.member_principal_id = DP2.principal_id
ORDER BY DP1.name;

如果需要判断自己是不是属于某个role,只要 is_member(), 比如:
select is_member(‘db_owner’);

我们改写下,如果要知道自己的账户到底归属于哪些role,可以这样写:

set nocount on ;
if not exists( select 1 from tempdb.sys.tables where upper(name) like '%TEMPMYROLE%')
create table #tempMyRole(RoleName varchar(200),RoleId int, IsAppRole int) ;
truncate table #tempMyRole ;
insert into #tempMyRole (RoleName,RoleId,IsAppRole)
exec sp_helprole ;
declare @rolemember bit = 0;
declare @rolename varchar(200) ;
declare my_rolemem cursor
for select RoleName as rolemember from #tempMyRole ;
open my_rolemem ;
fetch next from my_rolemem into @rolename ;
while @@fetch_status = 0
begin
set @rolemember = 0 ;
select @rolemember = is_member(@rolename) ;
select @rolename +': '+convert(varchar,@rolemember) ;
fetch next from my_rolemem into @rolename ;
end
close my_rolemem
deallocate my_rolemem ;
drop table #tempMyRole ;

创建role, 增加role member的命令有 CREATE Role, Alter Role, Drop Role, sp_addRole, sp_dropRole, sp_addRoleMember, sp_dropRoleMember. 接着我们就可以用Grant, Deny, Revoke来给角色赋予权限。所有的角色和用户都可以用sys.database_role_members, sys.database_principals来查询到。

增加一个Role Member的脚本可以这么写,但是加了这个Role Member之后,并且给这个Role Member加入了db_owner角色,并不意味着这个用户就可以立即登陆数据库使用了,因为SQL Server服务器端的认证还没有给这个用户加上:

create user regionowner for login [NAM\LEWIS2258] ;
go
exec sp_addrolemember 'db_owner','regionowner' ;

上面我们为windows account [NAM\LEWIS2258]创建一个数据库用户regionowner,并给这个用户赋予db_owner的角色。但是这个windows account并不能够登陆数据库服务器。

上面是在数据库层面来设计 角色与用户的架构,这可以为数据应用层面的权限关系。除此之外我们还需要考虑SERVER层面的授权认证关系。和数据库层面的权限架构一样,Server层面的权限架构也有角色与用户之分,只不过名字更绕口一些, server role与log in .

为数据库增加一个使用者,第一步就是要新建一个login, 下面我们模拟下步骤:

如果是windows账户 :

create login [NAM\LEWIS2258] from windows ;
go
如果是SQL SERVER账户:
create login huangyun
with password = 'Hoover123456.'

有了login,我们就可以为这个login赋予server role和database role了:

alter server role sysadmin add [NAM\LEWIS2258]
go
exec sp_addrolemember @rolename = 'sysadmin',@membername = 'regionowner' ;

用下面的脚本来验证,我们的新用户是不是已经拥有这个权限了:

SELECT SRM.role_principal_id, SP.name AS Role_Name,
SRM.member_principal_id, SP2.name AS Member_Name
FROM sys.server_role_members AS SRM
JOIN sys.server_principals AS SP
ON SRM.Role_principal_id = SP.principal_id
JOIN sys.server_principals AS SP2
ON SRM.member_principal_id = SP2.principal_id
ORDER BY SP.name, SP2.name
SELECT
perms.state_desc AS State,
permission_name AS [Permission],
obj.name AS [on Object],
dPrinc.name AS [to User Name],
sPrinc.name AS [who is Login Name]
FROM sys.database_permissions AS perms
right JOIN sys.database_principals AS dPrinc
ON perms.grantee_principal_id = dPrinc.principal_id
left JOIN sys.objects AS obj
ON perms.major_id = obj.object_id
LEFT OUTER JOIN sys.server_principals AS sPrinc
ON dPrinc.sid = sPrinc.sid;

在这个地方我们尤其要注意的就是理解server role所对应的database role:

SELECT
perms.state_desc AS State,
permission_name AS [Permission],
obj.name AS [on Object],
dPrinc.name AS [to User Name],
sPrinc.name AS [who is Login Name]
FROM sys.database_permissions AS perms
right JOIN sys.database_principals AS dPrinc
ON perms.grantee_principal_id = dPrinc.principal_id
left JOIN sys.objects AS obj
ON perms.major_id = obj.object_id
LEFT OUTER JOIN sys.server_principals AS sPrinc
ON dPrinc.sid = sPrinc.sid;
  • 先解决授权方案,再决定安全认证。授权方案创建了角色与用户,授予读写权限。安全认证 就是将各种Windows账户对应到各个角色或者用户上来。

我们一起来看下MSDN上这文章,可以很好的帮助我们理解怎么从头到尾的来创建一个数据库用户:牢记一个概念,login必须要映射(mapped to)到一个数据库用户才能访问这个数据库
https://msdn.microsoft.com/en-us/library/aa337562.aspx?f=255&MSPPError=-2147217396

SQL SERVER可以创建2中大类的用户,一是windows认证用户,二是SQL SERVER认证用户。Windows用户可以分为Active Directory组用户,和Windows组用户。如果SQL SERVER Instance两种都支持,那在安装实例的时候就要支持mixed认证。实际上wnidows认证总是启用的,所以认证模式主要是配置是否支持SQL SERVER认证。

这里引出来一个问题,我们怎么对一个现存的SQL SERVER实例来确定他到底支持哪种认证方式?

SELECT CASE SERVERPROPERTY('IsIntegratedSecurityOnly')
WHEN 1 THEN 'Windows Authentication'
WHEN 0 THEN 'Windows and SQL Server Authentication'
END as [Authentication Mode]

有一种读取注册表的方式来验证:

DECLARE @AuthenticationMode INT
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
N'Software\Microsoft\MSSQLServer\MSSQLServer',
N'LoginMode', @AuthenticationMode OUTPUT
SELECT CASE @AuthenticationMode
WHEN 1 THEN 'Windows Authentication'
WHEN 2 THEN 'Windows and SQL Server Authentication'
ELSE 'Unknown'
END as [Authentication Mode]

以上的方法来源于这篇文章:
https://www.mssqltips.com/sqlservertip/2191/how-to-check-sql-server-authentication-mode-using-t-sql-and-ssms/

既然 有server 和database权限之分,那么各自对应的权限分别有哪些?
比如server上有alter login, drop user等,database级别有alter index, create table等。我们怎么知道我们有了哪些权限,或者我们被赋予的角色都有哪些权限?
检查数据库用户的权限:

exec sp_dbfixedrolepermission

SELECT distinct DB_NAME() AS 'DBName'
,p.[name] AS 'PrincipalName'
,p.[type_desc] AS 'PrincipalType'
,p2.[name] AS 'GrantedBy'
,dbp.[permission_name]
,dbp.[state_desc]
,so.[Name] AS 'ObjectName'
,so.[type_desc] AS 'ObjectType'
FROM [sys].[database_permissions] dbp LEFT JOIN [sys].[objects] so
ON dbp.[major_id] = so.[object_id] LEFT JOIN [sys].[database_principals] p
ON dbp.[grantee_principal_id] = p.[principal_id] LEFT JOIN [sys].[database_principals] p2
ON dbp.[grantor_principal_id] = p2.[principal_id]

检查服务器级别的权限:

SELECT * FROM sys.fn_builtin_permissions('SERVER') ORDER BY permission_name;

上面的脚本用来查看服务器有哪些权限。

exec sp_helpsrvrole ;
exec sp_helpsrvrolemember ;
exec sp_srvrolepermission ;
select is_srvrolemember('sysadmin') ;

上面的脚本用来查看login的权限。

简单讨论下如何给用户授权:Grant

Simplified syntax for GRANT

GRANT { ALL [ PRIVILEGES ] }
| permission [ ( column [ ,...n ] ) ] [ ,...n ]
[ ON [ class :: ] securable ] TO principal [ ,...n ]
[ WITH GRANT OPTION ] [ AS principal ]

这里要注意的是with grant option, 就是把“授权给别人”这个权限也付给这个principal . 比如:

USE AdventureWorks2012;
GRANT REFERENCES (BusinessEntityID) ON OBJECT::HumanResources.vEmployee
TO Wanida WITH GRANT OPTION;

更详细的介绍可以看这里:
https://msdn.microsoft.com/en-us/library/ms187965.aspx

我们再简单得回顾下整个搭建的过程,不涉及具体的代码,只有完整的步骤,而且我们以 snapshot replication为例,其他两种类型的replication, transactional replication, merge replication我们会再开段落来讨论。

Distributor角色的配置:

· 在指定的SQL Server instance上面,指定当前的数据库服务器为distributor角色,配置相应的远程用户密码,心跳等属性;
· 在已经指定为distributor的SQL Server Instance上面, 指定特定的数据库为distribution数据库;
· 添加远程的SQL SERVER Instance为publisher

Publisher角色的配置:

· 在指定的SQL Server Instance上面,指定特定的数据库为publictation的数据库;
· 添加刚才配置的Distributor,测试刚才在distributor上创建的帐户密码可用;
· 创建一份publication,设定具体的replication类型为snapshot replication,允许Publication支持pull, push;
· 将所有用于replication的数据库对象以及数据作为article添加到publication里面去,如果需要新增article,则需要使原先的snapshot实效,并且重新生成snapshot ;
· 为新加的publication,生成一份snapshot,同时建立这个snapshot的agent job, 新建立的agent job就是源源不断产生snapshot的Job,并且将snapshot 写入到distributor上的snapshot working directory

use lenistest
go
exec sp_replicationdboption
@dbname='lenistest'
,@optname='publish'
,@value=true ;
go
exec sp_addpublication
@publication = 'lenistest_snapshot'
,@allow_push = N'true'
,@allow_pull = N'true'
,@repl_freq = N'snapshot'
,@status = N'active'
,@independent_agent = N'false'
,@immediate_sync =N'false';
go
exec sp_addarticle
@publication = 'lenistest_snapshot'
,@article='dbo_country'
,@source_object='country'
,@source_owner='dbo'
,@type='logbased' ;
go
exec sp_addpublication_snapshot
@publication = 'lenistest_snapshot'
,@publisher_security_mode = 1
,@job_login = 'NAM\HY88348'
,@job_password='Hoover123456.' ;
go

Subscriber角色的配置:

· 在指定SQL Server Instance上面,开启特定数据库的subscriber功能;
· 在publisher的publication数据库上,执行添加一份subscription的操作,在这份subscription中指定Publication , article, subscriber, subscription database,并且指定使用push方式;
· 在publisher上面,添加一个push subscription的agent job, 用来接收snapshot然后推送到subscriber上。

use lenistest5
go
exec sp_replicationdboption
@dbname='lenistest5'
,@optname='subscribe'
,@value=true ;
go
--在publisher上面执行下面脚本
use lenistest
go
exec sp_addsubscription
@publication = 'lenistest_snapshot'
,@subscriber = 'VM-B9CB-CD02\MSSQLSERVER2014'
,@destination_db = 'lenistest5'
,@sync_type='automatic'
,@status ='subscribed'
,@subscription_type = 'push'
go
exec sp_addpushsubscription_agent
@publication = 'lenistest_snapshot'
,@subscriber = 'VM-B9CB-CD02\MSSQLSERVER2014'
,@subscriber_db='lenistest5'
,@subscriber_security_mode = 1
,@job_login='NAM\HY88348'
,@job_password = 'Hoover123456.' ;
go

在实际使用过程中,如果我们需要新建新加一个article,那么怎么同步,这里在创建publication的时候,使用sp_addpublication有两个参数值的注意:

*@independent_agent,@immediate_sync
Specifies if the synchronization files for the publication are created each time the Snapshot Agent runs. immediate_synchronization is nvarchar(5), with a default of FALSE. If true, the synchronization files are created or re-created each time the Snapshot Agent runs. Subscribers are able to get the synchronization files immediately if the Snapshot Agent has completed before the subscription is created. New subscriptions get the newest synchronization files generated by the most recent execution of the Snapshot Agent. independent_agent must be true for immediate_synchronization to be true. If false, the synchronization files are created only if there are new subscriptions. You must call sp_addsubscription for each subscription when you incrementally add a new article to an existing publication. Subscribers cannot receive the synchronization files after the subscription until the Snapshot Agents are started and completed.*

上面这段描述我们可以这样理解:如果我们专为某一个snapshot publication指定一个Agent Job, 这样我们就可以指定@immediate_sync为true, 这么做的作用在于,任何一个新添加的article,在下一次snapshot agent job运行得时候就会自动生成同步文件。反过来,必须使用sp_addsubscription才能生成新加article的同步文件。

如果我们需要将publish subscribe都停掉,怎么办?

use lenistest5
go
exec sp_replicationdboption
@dbname='lenistest5'
,@optname='subscribe'
,@value=false;
go

use lenistest
go
exec sp_replicationdboption
@dbname='lenistest'
,@optname='publish'
,@value=false;
go

我们来看下是怎么给现存的publication添加一份新的article:我们建立的subscription是push类型的,所以新建article的时候,我们都需要在publication数据库上执行。 尽管我们创建subscription的时候指定订阅publication下所有的article,事实上这份subscription并不包含新建的的这份article,所以我们还是要为这份article创建下新的subscription. 所以上面两个参数的作用就显现了。我们将@immediate_sync设置为false了,所以必须执行sp_addsubscription来重生成新加article的snapshot了。

use lenistest
go
create table dbo.region(regionid int, regionname varchar(20)) ;
go
insert into dbo.region(regionid,regionname) values(1,'APAC') ;
go
exec sp_addarticle
@publication = 'lenistest_snapshot'
,@article='dbo_region'
,@source_object='region'
,@source_owner='dbo'
,@type='logbased' ;
go
use lenistest
go
exec sp_addsubscription
@publication = 'lenistest_snapshot'
,@article = 'dbo_region'
,@subscriber = 'VM-B9CB-CD02\MSSQLSERVER2014'
,@destination_db = 'lenistest5'
,@sync_type='automatic'
,@status ='subscribed'
,@subscription_type = 'push'

我们来看下,如果设置@immediate_sync为true,那么新加的article是不是会立即同步? 要注意当@immediate_sync为true的时候,@independent_agent也要为true.

use lenistest
go
exec sp_replicationdboption
@dbname='lenistest'
,@optname='publish'
,@value=true ;
go
exec sp_addpublication
@publication = 'lenistest_snapshot'
,@allow_push = N'true'
,@allow_pull = N'true'
,@repl_freq = N'snapshot'
,@status = N'active'
,@independent_agent = N'true'
,@immediate_sync =N'true';
go
exec sp_addarticle
@publication = 'lenistest_snapshot'
,@article='dbo_country'
,@source_object='country'
,@source_owner='dbo'
,@type='logbased' ;
go
exec sp_addpublication_snapshot
@publication = 'lenistest_snapshot'
,@publisher_security_mode = 1
,@job_login = 'NAM\YH73868'
,@job_password='Hoover123456.' ;
go
use lenistest5
go
exec sp_replicationdboption
@dbname='lenistest5'
,@optname='subscribe'
,@value=true ;
go
--在publisher上面执行下面脚本
use lenistest
go
exec sp_addsubscription
@publication = 'lenistest_snapshot'
,@subscriber = 'VM-B9CB-CD02\MSSQLSERVER2014'
,@destination_db = 'lenistest5'
,@sync_type='automatic'
,@status =null
,@subscription_type = 'push'
go
exec sp_addpushsubscription_agent
@publication = 'lenistest_snapshot'
,@subscriber = 'VM-B9CB-CD02\MSSQLSERVER2014'
,@subscriber_db='lenistest5'
,@subscriber_security_mode = 1
,@job_login='NAM\YH73868'
,@job_password = 'Hoover123456.' ;
go

这里有两个地方要注意:

1 sp_addsubscription中的@status要设置为NULL:

Msg 14129, Level 16, State 1, Procedure sp_MSrepl_addsubscription_article, Line 291
The @status parameter value must be NULL for ‘automatic’ sync_type when you add subscriptions to an immediate_sync publication.

2 当新加一个article的时候,需要强制先前的snpshot失效: 这一步执行完后,snapshot就重新开始同步了

exec sp_addarticle
@publication = ‘lenistest_snapshot’
,@article=’dbo_bookshops’
,@source_object=’bookshops’
,@source_owner=’dbo’
,@type=’logbased’
,@force_invalidate_snapshot = 1;
go

Msg 20607, Level 16, State 1, Procedure sp_MSreinit_article, Line 189
Cannot make the change because a snapshot is already generated. Set @force_invalidate_snapshot to 1 to force the change and invalidate the existing snapshot.

下面来讨论下如何搭建transactional replication, snapshot replication是transactional replication, merge replication的基础,要实现其他两个replication,必须先由snapshot来为整个数据库产生一份snapshot. 以这份snapshot为基础,其它两种replication才能做增量。

Snapshot replication在产生的时候,会不会对性能有影响呢,如果整个数据库体量很大,并发很频繁,长时间的锁住很多表作snapshot,肯定对并发有一定影响。但是如果这份snapshot可以建立在database snapshot上,是不是并发就没有问题呢?Snapshot replication在产生的时候,如果采用了read uncommitted的隔离级别,那也就不存在锁的影响;如果访问的是database snapshot呢,会不会对原表有影响,特别是锁的处理?

关于merge replication, 在目前移动开发技术成熟的条件下,我想不出这类replication的使用场景了。

use lenistest
go
exec sp_replicationdboption
@dbname='lenistest'
,@optname='publish'
,@value=true ;
go
exec sp_addlogreader_agent
@job_login = 'NAM\YH73868'
,@job_password ='Hoover123456.'
,@publisher_security_mode = 1 ;
go
exec sp_addpublication
@publication = 'lenistest_snapshot'
,@allow_push = N'true'
,@allow_pull = N'true'
,@repl_freq = N'continuous'
,@status = N'active'
,@independent_agent = N'true'
,@immediate_sync =N'true';
go
exec sp_addarticle
@publication = 'lenistest_snapshot'
,@article='dbo_country'
,@source_object='country'
,@source_owner='dbo'
,@type='logbased' ;
go
exec sp_addpublication_snapshot
@publication = 'lenistest_snapshot'
,@publisher_security_mode = 1
,@job_login = 'NAM\YH73868'
,@job_password='Hoover123456.' ;
go
use lenistest5
go
exec sp_replicationdboption
@dbname='lenistest5'
,@optname='subscribe'
,@value=true ;
go

-

-在publisher上面执行下面脚本
use lenistest
go
exec sp_addsubscription
@publication = 'lenistest_snapshot'
,@subscriber = 'VM-B9CB-CD02\MSSQLSERVER2014'
,@destination_db = 'lenistest5'
,@sync_type='automatic'
,@status =null
,@subscription_type = 'push'
go
exec sp_addpushsubscription_agent
@publication = 'lenistest_snapshot'
,@subscriber = 'VM-B9CB-CD02\MSSQLSERVER2014'
,@subscriber_db='lenistest5'
,@subscriber_security_mode = 1
,@job_login='NAM\YH73868'
,@job_password = 'Hoover123456.' ;
go

上面的脚本用来创建transactional replication。这里有几个地方要注意 :

1 sp_addlogreader_agent必须放在sp_addpublication之前,原因是transaction replication会隐式得创建一个log reader agent job。否则会出现下面的错误:

Msg 21831, Level 16, State 1, Procedure sp_MSrepl_addlogreader_agent, Line 184
The logreader agent already exists. Use ‘sp_changelogreader_agent’ to change any settings/properties.

2 在移除这类replication的时候,要加上sp_replflush,否则出现:

Msg 18752, Level 16, State 1, Procedure sp_replcmds, Line 37
Only one Log Reader Agent or log-related procedure (sp_repldone, sp_replcmds, and sp_replshowcmds) can connect to a database at a time.
If you executed a log-related procedure, drop the connection over which the procedure was executed or execute sp_replflush over that connection before starting the Log Reader Agent or executing another log-related procedure.

3 新增加article的时候我们要注意给同步的表加上primary key,同样也要使得snapshot失效

exec sp_addarticle
@publication = 'lenistest_snapshot'
,@article='dbo_bookshops'
,@source_object='bookshops'
,@source_owner='dbo'
,@type='logbased'
,@force_invalidate_snapshot = 1;
go

Msg 14088, Level 16, State 1, Procedure sp_MSrepl_addarticle, Line 1622
The table ‘[dbo].[bookshops]’ must have a primary key to be published using the transaction-based method.

比如 :

alter table dbo.bookshops alter column bookshopid int not null ;
alter table dbo.bookshops add constraint pk_bookshopid primary key (bookshopid) ;

下面我们要讨论下Snapshot Replication在生成snaphot的时候,会用到哪些锁 ,这些锁对我们整体数据库运行的影响?

· 部署Snapshot Replication环境
· 监控数据库的各种活动以及活动产生的事务及锁

SQL Profiler比较直观,我们只要限定指定的数据库,就可以了。只要让snaphot足够大,执行时间足够长,那么肯定能捕捉到。第二种方法我们可以查询sys.dm_exec_requests,每间隔一段时间做一次镜像,我们就可以捕捉到当时数据库的各种活动。

declare @search_database varchar(200) = 'lenistest5' ;
if not exists( select 1 from tempdb.sys.tables where upper(name) like '%TEMPSPWHO%' )
begin
    create table #tempspwho(spid bigint, ecid bigint,status varchar(200),loginname varchar(200),hostname varchar(200), blockedby bigint, dbname varchar(200),command varchar(200),request_id bigint ) ;
end
if not exists( select 1 from tempdb.sys.tables where upper(name) like '%TEMPSPWHO2%' )
begin
    create table #tempspwho2(spid bigint, status varchar(200),loginname varchar(200),hostname varchar(200), blockedby varchar(200), dbname varchar(200),command varchar(200),cputime bigint, diskio bigint,lastbatch varchar(200),
    programname nvarchar(200) , spid2 varchar(200),requestid varchar(200)) ;
end
truncate table #tempspwho ;
truncate table #tempspwho2 ;
insert into #tempspwho(spid,ecid,status,loginname,hostname,blockedby,dbname,command,request_id)
exec sp_who ;
insert into #tempspwho2
exec sp_who2 ;
select * from #tempspwho where upper(dbname) like '%' + upper(@search_database)+'%' ;
select * from #tempspwho2 where upper(dbname) like '%' + upper(@search_database)+'%' ;
drop table #tempspwho ;
drop table #tempspwho2 ;

更详细的数据可以通过下面的脚本分析:

select db_name(dbt.database_id) as databaseName
, at.name as transactionName
, at.transaction_id
,at.transaction_begin_time
, case
when at.transaction_type = 1 then 'read and write transaction'
when at.transaction_type = 2 then 'read only transaction'
when at.transaction_type = 3 then 'system transaction'
when at.transaction_type = 4 then 'distributed transaction'
end as transaction_type
, case
when at.transaction_state = 0 then 'The transaction has not been completely initialized yet.'
when at.transaction_state = 1 then 'The transaction has been initialized but has not started.'
when at.transaction_state = 2 then 'The transaction is active'
when at.transaction_state = 3 then 'The transaction has ended. This is used for read-only transactions'
when at.transaction_state = 4 then 'The commit process has been initiated on the distributed transaction. This is for distributed transactions only. The distributed transaction is still active but further processing cannot take place'
when at.transaction_state = 5 then 'The transaction is in a prepared state and waiting resolution'
when at.transaction_state = 6 then 'The transaction has been committed.'
when at.transaction_state = 7 then 'The transaction is being rolled back.'
when at.transaction_state = 8 then 'The transaction has been rolled back'
end as transaction_staus
, trl.request_session_id
, trl.request_mode
, trl.request_type
, trl.request_status
, trl.request_owner_type
, datediff(ss,at.transaction_begin_time, getdate()) as request_lifetime_s
, trl.resource_type
, trl.resource_description
, trl.resource_associated_entity_id
, case
when trl.resource_type = 'OBJECT' then object_name(convert(varchar,trl.resource_associated_entity_id))
else
'other objects not table'
end as objectName
, logs.host_name
, logs.program_name
, logs.login_name
, req.blocking_session_id
from sys.dm_tran_database_transactions dbt
left join sys.dm_tran_active_transactions at on dbt.transaction_id = at.transaction_id
left join sys.dm_tran_session_transactions st on st.transaction_id = dbt.transaction_id
left join sys.dm_tran_locks trl on trl.request_session_id = st.session_id
left join sys.dm_exec_sessions logs on logs.session_id = st.session_id
left join sys.dm_exec_requests req on req.session_id = st.session_id
where dbt.database_id = db_id(N'lenistest5')

接下来的活是蛮有意思也蛮耗时的,就是观察我们在同步一张100万条数据的表需要花费的时间。当然这100万条数据的表有很多column,占的snapshot空间大概有2G左右。实际上我们要看的就是这2G的数据同步以下需要花费的时间 。

use lenistest
go
create table dbo.loghistory_publish
( logid bigint not null identity(1,1) primary key ,
    logcommand varchar(200),
    logbegintime datetime ,
    logendtime datetime,
    logduration bigint ,
    logcompleted int );
go
create procedure dbo.snapshotreplicationlog_begin
as
begin
begin try
    insert into dbo.loghistory_publish (logcommand,logbegintime,logendtime,logduration,logcompleted)
    values('snapshot generating',getutcdate(),getutcdate(),0,0)
end try
begin catch
    return error_message()
end catch
end ;
go
create procedure dbo.snapshotreplicationlog_end
as
begin
    begin try
        update a
        set logendtime = getutcdate(),logduration = datediff(ss,logbegintime,getutcdate()),logcompleted = 1
        from dbo.loghistory_publish a
        where logcompleted = 0
    end try
    begin catch
        return error_message()
    end catch
end ;
go

我们只要把这些log加到snapshot generating中去,就能看到大概的时间了。平均时间49s.
我们再给这个数据库加一些读写,看看对snapshot的影响:

create table dbo.databaseoperations_log
( operationid bigint not null identity(1,1) primary key ,
operationtype varchar(5) not null,
operationcommand varchar(200) not null,
operation_begintime datetime not null,
operation_endtime datetime not null,
operation_duration bigint not null,
operation_completed int not null ) ;
go

create procedure dbo.read_log
as
begin
    declare @scopeid bigint = 0 ;
    declare @sqlcmd nvarchar(max) = 'select count(*) from dbo.country where SEQUENCE between 8 and 24 AND CREATED between ''2008-01-01'' and ''2009-12-31'' ';
    begin try
        insert into dbo.databaseoperations_log (operationtype,operationcommand,operation_begintime,operation_endtime,operation_duration,operation_completed)
        values('read','select count(*) as rs from dbo.country where SEQUENCE between 8 and 24 AND CREATED between ''2008-01-01'' and ''2009-12-31''',getutcdate(),getutcdate(),0,0) ;
        set @scopeid = SCOPE_IDENTITY() ;
        exec sp_executesql @sqlcmd ;
        update dbo.databaseoperations_log
        set operation_endtime = getutcdate(),operation_duration = datediff(ss,operation_begintime,getutcdate()), operation_completed = 1
        where operationid = @scopeid and operation_completed = 0
    end try
    begin catch
        return error_message() ;
    end catch
end ;
go

create procedure dbo.write_log
as
begin
    declare @scopeid bigint = 0 ;
    declare @sqlcmd nvarchar(max) = 'update dbo.country set SEQUENCE = SEQUENCE * 1 where SEQUENCE between 8 and 24 AND CREATED between ''2008-01-01'' and ''2009-12-31'' ';
    begin try
        insert into dbo.databaseoperations_log (operationtype,operationcommand,operation_begintime,operation_endtime,operation_duration,operation_completed)
        values('WRITE','update dbo.country set SEQUENCE = SEQUENCE * 1 where SEQUENCE between 8 and 24 AND CREATED between ''2008-01-01'' and ''2009-12-31''',getutcdate(),getutcdate(),0,0) ;
        set @scopeid = SCOPE_IDENTITY() ;
        exec sp_executesql @sqlcmd ;
        update dbo.databaseoperations_log
        set operation_endtime = getutcdate(),operation_duration = datediff(ss,operation_begintime,getutcdate()), operation_completed = 1
        where operationid = @scopeid and operation_completed = 0
    end try
    begin catch
    return error_message() ;
    end catch
end ;
go

设置一个配置表让读写开始运行,只不过这里就运行一个读线程和一个写线程 :

CREATE TABLE ##log_process(logtype varchar(5),logprocess bit ) ;
insert into ##log_process values('read',1), ('write',1) ;
declare @readprocess bit = 1 ;
while @readprocess = 1
    begin
        exec dbo.read_log;
        select @readprocess = logprocess from ##log_process where logtype = 'read' ;
    end ;
declare @writeprocess bit = 1 ;
while @writeprocess = 1
    begin
        exec dbo.write_log;
        select @writeprocess = logprocess from ##log_process where logtype = 'write' ;
    end ;

然后我们看这个snapshot replication生成的速度:

use lenistest
go
select * from dbo.loghistory_publish;
go

很明显,增加了一倍以上的时间,并且snapshot生成的线程反过来也拖慢了读写线程 。

select * from dbo.databaseoperations_log order by operation_begintime asc

特别是写操作。

最后一点,生成snapshot同步文件的job是5分钟执行间隔,我们当然也可以设置为每天业务系统比较闲的时候执行。但是针对7*24的业务环境,这种replication是不能满足性能要求的。

我们接着看transaction replication对数据库性能的影响:

· 部署transaction replication环境
· 模拟读写环境,监控性能

在transactional replication架构中,一旦删除了数据后,snapshot就不再重新作更新。这个时候我们需要对订阅数据库做一次refresh或者reinitiate.最快的方式恐怕应该是属于重新指定一个新的订阅库。 当然两个方向我们都可以来做一次尝试

· Refresh 或者 reinitiate subscription database
· 重新创建一个订阅库,订阅我们的Publication

理论上来说,重新发布一个订阅要相对简单一些,比如:

CREATE DATABASE [lenistest6]
ON
PRIMARY
( NAME = N'lenistest6',
FILENAME = N'E:\Data_BU\lenistest6.mdf' ,
SIZE = 10240KB ,
MAXSIZE = 10240000KB ,
FILEGROWTH = 1024KB )
, filegroup maindatagroup
( NAME = N'lenistest6_data01',
FILENAME = N'E:\Data_BU\lenistest6_data01.ndf' ,
SIZE = 10240KB ,
MAXSIZE = 10240000KB ,
FILEGROWTH = 1024KB )
, filegroup backupdatafg
( NAME = N'lenistest6_bk_data01',
FILENAME = N'E:\Data_BU\lenistest6_bk_data01.ndf' ,
SIZE = 10240KB ,
MAXSIZE = 10240KB ,
FILEGROWTH = 1024KB )
LOG ON
( NAME = N'lenistest6_log',
FILENAME = N'E:\Data_BU\lenistest6_log.ldf' ,
SIZE = 10240KB ,
MAXSIZE = 10240KB ,
FILEGROWTH = 1024KB )
GO
use lenistest6
go
exec sp_replicationdboption
@dbname='lenistest6'
,@optname='subscribe'
,@value=true ;
go
--在publisher上面执行下面脚本
use lenistest
go
exec sp_addsubscription
@publication = 'lenistest_snapshot'
,@subscriber = 'VM-B9CB-CD02\MSSQLSERVER2014'
,@destination_db = 'lenistest6'
,@sync_type='automatic'
,@status =null
,@subscription_type = 'push'
go
exec sp_addpushsubscription_agent
@publication = 'lenistest_snapshot'
,@subscriber = 'VM-B9CB-CD02\MSSQLSERVER2014'
,@subscriber_db='lenistest6'
,@subscriber_security_mode = 1
,@job_login='NAM\HU88378'
,@job_password = 'Hoover123456.' ;
go

上面的脚本用来发布一个新的订阅库,没问题。这里面有几个问题需要明晰 :

· 在transactional replication中publisher一直在不停的产生一个snapshot, 是否有必要?
· 新加入一个订阅库,怎么判断这个数据库是否已经被initiate过了?
· 上面的新订阅库也迟迟进不来数据,是不是每一个新订阅的库需要refresh或者reinitiate?

经过试验, 无论是新建的订阅库或者是往已订阅库修复数据,都需要经历两步

· Reinitiate subscription 重新刷新订阅库:
· 同步transaction publication, 这一步可选:如果在已有distributor agent job存在的情况下,可以不用自己手动或编程实现。如果没有,可以用distrib.exe或者建立Job来同步。

我们来看下reinitiate subscription的方法:

sp_reinitsubscription [ [ @publication = ] 'publication' ]
[ , [ @article = ] 'article' ]
, [ @subscriber = ] 'subscriber'
[ , [ @destination_db = ] 'destination_db']
[ , [ @for_schema_change = ] 'for_schema_change']
[ , [ @publisher = ] 'publisher' ]
[ , [ @ignore_distributor_failure = ] ignore_distributor_failure ]
[ , [ @invalidate_snapshot = ] invalidate_snapshot ]

如果对整个订阅库做reinitiate,@article就不需要特别指定。@publisher也只有在非SQL Server的publisher情况下才需要指定。@ignore_distributor_failure就是不管distributor instance的连线状态,都重新reinitiate订阅库。@invalidate_snapshot就是指定当前的snapshot是否有效,如果指定为无效,我们就在下次生成snapshot的时候,重新生成一份snapshot,只有在有publication中的article有更改的情况下才使用。

use lenistest
go
exec sp_reinitsubscription
@publication = 'lenistest_snapshot'
,@subscriber = 'VM-B9CB-CD02\MSSQLSERVER2014'
,@destination_db = 'lenistest5';
go
exec sp_reinitsubscription
@publication = 'lenistest_snapshot'
,@subscriber = 'VM-B9CB-CD02\MSSQLSERVER2014'
,@destination_db = 'lenistest6';
go

上面的脚本就实现了refinitiate两个订阅库。

现在就剩下一个问题,就是在transactional replication中,生成snapshot的agent job是不是需要一直开启运行,持续不断的生成snapshot。 答案是必须的。因为我们的数据库是连续运行的,如果随时都会有新的subscription进来,只有最新的snapshot才能用来initiate订阅库,Log reader读取的日志才能进一步的更新到订阅库中。

上面讨论的是基本点实现方法,需要慎重考虑的是同步文件的配置。比如体量很大的一个数据仓库 ,有20T, 如果把所有的表都放在一个publication里面,那一旦一个数据表的结构更改了,我们需要重新initiate订阅库,就要同步20T的文件,这耗时巨长。这个时候我们就要考虑publication的配置了。 一个大表一个article,单独放一个publication.

现在的问题就是要考虑如何做产品发布,比如批量修改一个article, 新加一个article,考虑到耗时的问题,尤其在配置上要控制

· 针对同一个subscription database做发布
· 针对不同的subscription database做发布

两者都可以通过配置来解决,比如:

/*
publication article subscriber subscriber_db initialized agent_created new_subscription
‘lenistest_dbo_country’ ‘dbo_country’ ‘VM-B9CB-CD02\MSSQLSERVER2014’ ‘lenistest6’ 0 0 1
‘lenistest_dbo_region’ ‘dbo_region’ ‘VM-B9CB-CD02\MSSQLSERVER2014’ ‘lenistest6’ 0 1 0
*/

create table dbo.subscriptionmanagement ( publication varchar(200) not null, article varchar(200), subscriber varchar(200),subscriber_db varchar(200), initialized int, agent_created int, new_subscription int)

if not exists(select 1 from dbo.subscriptionmanagement )
begin
    insert into dbo.subscriptionmanagement values
    ('lenistest_dbo_country2', 'dbo_country2' , 'VM-B9CB-CD02\MSSQLSERVER2014' , 'lenistest6' , 0 , 0 , 1),
    ('lenistest_dbo_region' , 'dbo_region' , 'VM-B9CB-CD02\MSSQLSERVER2014', 'lenistest6' , 0 , 1 , 0)
    ;
end
;
create procedure dbo.initiate_subscription
as
begin
    declare @publication varchar(200) , @article varchar(200), @subscriber varchar(200),@subscriber_db varchar(200), @initialized int, @agent_created int, @new_subscription int;
    declare @sqlbody nvarchar(max) ;
    declare sub_cur cursor for
    select publication,article,subscriber,subscriber_db,initialized,agent_created,new_subscription from dbo.subscriptionmanagement ;
    open sub_cur
    fetch next from sub_cur into @publication,@article,@subscriber,@subscriber_db,@initialized,@agent_created,@new_subscription ;
    begin try
    while @@fetch_status =0
    begin
    set transaction isolation level read committed
    begin transaction
        if @new_subscription = 0
        begin
            exec sp_addsubscription
            @publication = @publication
            ,@article = @article
            ,@subscriber = @subscriber
            ,@destination_db = @subscriber_db
            ,@sync_type='automatic'
            ,@status =null
            ,@subscription_type = 'push'
            exec sp_addpushsubscription_agent
            @publication = @publication
            ,@subscriber = @subscriber
            ,@subscriber_db=@subscriber_db
            ,@subscriber_security_mode = 1
            ,@job_login='NAM\HU88378'
            ,@job_password = 'Hoover123456.' ;
        end
        if @initialized = 0
        begin
            exec sp_reinitsubscription
            @publication = @publication
            ,@article = @article
            ,@subscriber = @subscriber
            ,@destination_db = @subscriber_db;
        end
        update dbo.subscriptionmanagement set new_subscription = 1, initialized = 1, agent_created = 1
        where publication = @publication and article = @article and subscriber = @subscriber and subscriber_db = @subscriber_db
    commit transaction
    end
    end try
begin catch
    if @@TRANCOUNT > 0
    rollback transaction ;
    return error_message() ;
end catch

欢迎关注个人微信公众号【有关SQL】

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dbLenis

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值