首先說一下咱們的環境html
咱們使用的是事務複製,複製是單向的,主服務器和從服務器都在同一個機房,固然不一樣機房也能夠,只須要改一下IP和端口數據庫
下面的腳本在咱們的SQLServer 2008上已經應用,暫時沒有發現問題,固然,若是你們使用過程當中有發現問題歡迎向我反饋o(∩_∩)o 服務器
首先,咱們爲什麼要校驗呢?網絡
咱們知道由於網絡延遲,或者從庫有寫入的狀況(固然通常咱們在訂閱端會設置爲db_datareader,不容許寫)會形成主從數據不一致的狀況分佈式
不管是SQL Server仍是MySQL,因此咱們就須要進行數據校驗,以便大概知道咱們的數據何時開始不一致ide
而校驗是不可能每時每刻都作校驗的,由於須要讀取全表數據,對性能會有影響性能
下面的過程只須要遠程上去從服務器,也就是訂閱服務器上面作就能夠了,徹底不須要遠程主服務器也就是發佈服務器ui
線上咱們作複製的表都比較小,數據量也不大spa
咱們作複製的最大一個表是600MB的表 3d
600MB的表 校驗時間是1 分鐘,那麼能夠推算 50000MB(50GB)的表 大概80分鐘 ,至於這個時間根據不一樣的環境 硬件和軟件 所需的校驗時間可能會有所不一樣
咱們使用的服務器是DELL R720
這個腳本原理很簡單,就是利用SQL Server的job天天定時執行來獲取主從上面的數據,從而判斷主從數據是否一致
廢話不說了,上腳本
一、在訂閱端執行查看哪些表作了複製
首先你須要知道你如今哪些表是作了複製的,固然有些人會到發佈服務器上去看,點擊幾下按鈕,其實在訂閱端是有視圖能夠看出
當前哪些表作了複製的
--在訂閱端執行 use [Task] -- 要複製的庫 GO select article from dbo.MSreplication_objects group by article GO
有9個表作了複製
二、創建linkedserver
--創建linkedserver USE [master] GO DECLARE @IP NVARCHAR(MAX) DECLARE @Login NVARCHAR(MAX) DECLARE @PWD NVARCHAR(MAX) SET @Login = N'xxx' --★Do SET @PWD = N'xxx' --★Do SET @IP ='192.168.100.6,1433' EXEC master.dbo.sp_addlinkedserver @server = @IP,@srvproduct = N'SQL Server' EXEC master.dbo.sp_serveroption @server = @IP,@optname = N'collation compatible', @optvalue = N'false' EXEC master.dbo.sp_serveroption @server = @IP,@optname = N'data access', @optvalue = N'true' EXEC master.dbo.sp_serveroption @server = @IP, @optname = N'dist',@optvalue = N'false' EXEC master.dbo.sp_serveroption @server = @IP, @optname = N'pub',@optvalue = N'false' EXEC master.dbo.sp_serveroption @server = @IP, @optname = N'rpc',@optvalue = N'true' EXEC master.dbo.sp_serveroption @server = @IP, @optname = N'rpc out',@optvalue = N'true' EXEC master.dbo.sp_serveroption @server = @IP, @optname = N'sub',@optvalue = N'false' EXEC master.dbo.sp_serveroption @server = @IP,@optname = N'connect timeout', @optvalue = N'0' EXEC master.dbo.sp_serveroption @server = @IP,@optname = N'collation name', @optvalue = NULL EXEC master.dbo.sp_serveroption @server = @IP,@optname = N'lazy schema validation', @optvalue = N'false' EXEC master.dbo.sp_serveroption @server = @IP,@optname = N'query timeout', @optvalue = N'0' EXEC master.dbo.sp_serveroption @server = @IP,@optname = N'use remote collation', @optvalue = N'true' EXEC master.dbo.sp_serveroption @server = @IP,@optname = N'remote proc transaction promotion',@optvalue = N'true' USE [master] EXEC master.dbo.sp_addlinkedsrvlogin @rmtsrvname = @IP, @locallogin = NULL, @useself = N'False', @rmtuser = @Login, @rmtpassword = @PWD
創建linkedserver的目的是鏈接到發佈服務器獲取數據,若是是不一樣機房,那麼只須要改IP爲公網IP和端口就能夠了
三、在訂閱服務器上建表
在訂閱端創建兩個表,這兩個表的做用是保存校驗數據
我說一下Repl_NeedMonitor表的need_monitor 字段,若是你有一天不想監控某個表了,你須要將那個表的need_monitor 字段改成0就能夠了
Repl_NeedMonitor表須要預先插入你要監控的表,在這裏第一步的「在訂閱端執行查看哪些表作了複製」爲了這一步作鋪墊的
執行完第一步,你知道有哪些表須要作監控,而後插入數據到Repl_NeedMonitor表就能夠了
---建表 USE [Task] --★Do GO --要監控的表 IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Repl_NeedMonitor]') AND type in (N'U')) BEGIN DROP TABLE [dbo].[Repl_NeedMonitor] END CREATE TABLE [dbo].[Repl_NeedMonitor] ( id INT IDENTITY(1, 1) PRIMARY KEY , tbname NVARCHAR(400) UNIQUE , need_monitor INT , --是否須要監控 update_time DATETIME ) --監控狀況表 IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Repl_MonitorStatus]') AND type in (N'U')) BEGIN DROP TABLE [dbo].[Repl_MonitorStatus] END CREATE TABLE [dbo].[Repl_MonitorStatus] ( id INT IDENTITY(1, 1) PRIMARY KEY , tbname NVARCHAR(500) , is_Consistency INT , -- 一致爲1, 不一致爲0 master_record BIGINT , --主庫表記錄數 slave_record BIGINT , --從庫表記錄數 update_time DATETIME --更新時間 ) --插入要監控的表數據 INSERT INTO [Repl_NeedMonitor] --★Do ( [tbname] , [need_monitor] , [update_time] ) VALUES ( N'Site' , -- tbname - nvarchar(500) 1 , -- need_monitor - int GETDATE() -- update_time - datetime ) SELECT * FROM [Repl_NeedMonitor]
Repl_NeedMonitor表
四、建立執行數據一致性校驗存儲過程
USE [Task] GO /****** Object: StoredProcedure [dbo].[usp_ConsistencyCheck] Script Date: 03/19/2015 15:36:36 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <樺仔> -- Create date: <2015.03.08> -- Description: <執行數據一致性校驗> -- ============================================= CREATE PROCEDURE [dbo].[usp_ReplConsistencyCheck] ( @tbname NVARCHAR(500) ) AS BEGIN DECLARE @is_Consistency INT --是否一致 DECLARE @master_record INT DECLARE @slave_record INT DECLARE @SQL NVARCHAR(MAX) DECLARE @LinkServer NVARCHAR(100) DECLARE @DBName NVARCHAR(100) DECLARE @SQLCountMaster NVARCHAR(MAX) DECLARE @SQLCountSlave NVARCHAR(MAX) SET @LinkServer = '192.168.100.6,1433' --★Do SET @DBName = 'Task' --★Do --獲取主庫表的記錄數 SET @SQLCountMaster = ' SELECT TOP 1 sysindx.[rowcnt] FROM ' + '[' + @LinkServer + '].' + '[' + @DBName + '].' + '[sys].[sysobjects] AS sysobj INNER JOIN [' + @LinkServer + '].' + '[' + @DBName + '].' + '[sys].[sysindexes] AS sysindx ON sysobj.[id] = sysindx.[id] AND sysobj.[xtype] = ''u'' AND sysobj.[name] =' + '''' + @tbname + '''' --獲取從庫表的記錄數 SET @SQLCountSlave = ' SELECT TOP 1 sysindx.[rowcnt] FROM ' + '[' + @DBName + '].' + '[sys].[sysobjects] AS sysobj INNER JOIN [' + @DBName + '].' + '[sys].[sysindexes] AS sysindx ON sysobj.[id] = sysindx.[id] AND sysobj.[xtype] = ''u'' AND sysobj.[name] =' + '''' + @tbname + '''' --建立臨時表保存臨時結果 IF EXISTS ( SELECT * FROM [tempdb]..sysobjects WHERE id = OBJECT_ID('tempdb..#tmptb1') ) BEGIN DROP TABLE [tempdb].[#tmptb1] END IF EXISTS ( SELECT * FROM [tempdb]..sysobjects WHERE id = OBJECT_ID('tempdb..#tmptb2') ) BEGIN DROP TABLE [tempdb].[#tmptb2] END IF EXISTS ( SELECT * FROM [tempdb]..sysobjects WHERE id = OBJECT_ID('tempdb..#tmptb3') ) BEGIN DROP TABLE [tempdb].[#tmptb3] END CREATE TABLE [#tmptb1] ( [is_Consistency] INT )-- 一致爲1, 不一致爲0 CREATE TABLE [#tmptb2]([master_record] BIGINT)--主庫記錄數 CREATE TABLE [#tmptb3]([slave_record] BIGINT) --從庫記錄數 INSERT INTO [#tmptb2]( [master_record]) EXEC ( @SQLCountMaster) INSERT INTO [#tmptb3]( [slave_record]) EXEC ( @SQLCountSlave) SELECT TOP ( 1 ) @master_record = [master_record] FROM [#tmptb2] SELECT TOP ( 1 ) @slave_record = [slave_record] FROM [#tmptb3] IF ( @master_record <> @slave_record ) BEGIN SET @is_Consistency = 0 END ELSE BEGIN --顯示訂閱表裏面有的記錄不在發佈表裏面的記錄有多少 若是不爲0 即數據不一致 SET @SQL = 'SELECT COUNT(*) FROM ( SELECT * FROM [dbo].[' + @tbname + ']' --發佈表 + ' EXCEPT ' + 'SELECT * FROM [' + @LinkServer + '].' + '[' + @DBName + '].' + '[dbo].[' + @tbname + ']' --訂閱表 + ') AS T;' INSERT INTO [#tmptb1]([is_Consistency]) EXEC (@SQL) IF ( SELECT TOP 1 [is_Consistency] FROM [#tmptb1]) <> 0 BEGIN SET @is_Consistency = 0 END ELSE BEGIN SET @is_Consistency = 1 END END INSERT INTO [Repl_MonitorStatus] ( [tbname] , [is_Consistency] , [master_record] , [slave_record] , [update_time] ) SELECT @tbname , @is_Consistency , @master_record , @slave_record , GETDATE() END
注意:腳本中凡有--★Do 的都是你須要結合本身狀況去修改的變量
這個腳本的原理很簡單,是讀取主庫表的記錄數,而後讀取從庫表的記錄數,而後進行比較
當兩邊的記錄數是一致的,那麼再用EXCEPT 減法歸零的方法比較兩邊表數據的內容是否一致
若是也是一致的,那麼兩邊表的數據就是一致的,不然就是不一致的,這裏有一個效率問題,就是首先判斷記錄數是否一致
若是不一致就沒有必要再去比較內容一致了,最後把數據插入到表Repl_MonitorStatus
五、建立掃描要監控的表存儲過程
這裏用遊標檢查哪個表須要進行校驗,而後調用usp_ReplConsistencyCheck存儲過程進行校驗
USE [Task] --★Do GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <樺仔> -- Create date: <2015.03.08> -- Description: <掃描要監控的表> -- ============================================= CREATE PROCEDURE [dbo].[usp_ReplScanMonitorTb] AS BEGIN DECLARE @TBNAME NVARCHAR(100) DECLARE CurTBName CURSOR FOR --獲取須要監控的表的表名 SELECT tbname FROM [dbo].[Repl_NeedMonitor] WHERE need_monitor = 1 OPEN CurTBName FETCH NEXT FROM CurTBName INTO @TBNAME WHILE @@FETCH_STATUS = 0 BEGIN EXEC [dbo].[usp_ReplConsistencyCheck] @TBNAME FETCH NEXT FROM CurTBName INTO @TBNAME END CLOSE CurTBName DEALLOCATE CurTBName END
六、建立定時校驗複製主從數據一致性JOB
每隔13個小時調用一次存儲過程,固然這個調用頻率能夠結合實際狀況進行修改
USE [msdb] GO -- ============================================= -- Author:<樺仔> -- Create date: <2015.03.8> -- Description: <定時校驗複製主從數據一致性JOB> -- ============================================== --以什麼登陸用戶身份運行做業 DECLARE @login_name NVARCHAR(100) SET @login_name=N'sa' --★Do BEGIN TRANSACTION DECLARE @ReturnCode INT SELECT @ReturnCode = 0 /****** Object: JobCategory [[Uncategorized (Local)]]] Script Date: 03/16/2015 15:18:09 ******/ IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1) BEGIN EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback END DECLARE @jobId BINARY(16) EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name=N'定時校驗複製主從數據一致性JOB', @enabled=1, @notify_level_eventlog=0, @notify_level_email=0, @notify_level_netsend=0, @notify_level_page=0, @delete_level=0, @description=N'定時校驗複製主從數據一致性JOB', @category_name=N'[Uncategorized (Local)]', @owner_login_name=@login_name, @job_id = @jobId OUTPUT IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** Object: Step [ResetLoginPassword] Script Date: 03/16/2015 15:18:10 ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'ReplScanMonitorTb', @step_id=1, @cmdexec_success_code=0, @on_success_action=1, @on_success_step_id=0, @on_fail_action=2, @on_fail_step_id=0, @retry_attempts=0, @retry_interval=0, @os_run_priority=0, @subsystem=N'TSQL', @command=N'exec [dbo].[usp_ReplScanMonitorTb]', @database_name=N'Task', @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'執行頻率', @enabled=1, @freq_type=4, @freq_interval=1, @freq_subday_type=8, @freq_subday_interval=13, @freq_relative_interval=0, @freq_recurrence_factor=0, @active_start_date=20110316, @active_end_date=99991231, @active_start_time=0, @active_end_time=235959, @schedule_uid=N'ddbd2dbc-ab05-4d0a-a4ca-60becc2620ac' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback COMMIT TRANSACTION GOTO EndSave QuitWithRollback: IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION EndSave: GO
看一下執行結果
SELECT * FROM [Repl_MonitorStatus]
從做業歷史裏看一下總執行時間
從執行結果裏面也能夠看到執行時間
腳本缺陷
這個腳本是有缺陷的,若是你是複製表裏面的幾個字段而不是整表複製的話,那麼他就不能比較兩邊的一致性了
狀況一:只複製表裏的幾個字段,並只須要監控一張表
解決辦法:在第一個存儲過程裏面《執行數據一致性校驗》存儲過程 修改一下下面的代碼只select複製的字段,而不是select *
--顯示訂閱表裏面有的記錄不在發佈表裏面的記錄有多少 若是不爲0 即數據不一致 SET @SQL = 'SELECT COUNT(*) FROM ( SELECT 字段1,字段2。。。 FROM [dbo].[' + @tbname + ']' --發佈表 + ' EXCEPT ' + 'SELECT 字段1,字段2。。。 FROM [' + @LinkServer + '].' + '[' + @DBName + '].' + '[dbo].[' + @tbname + ']' --訂閱表 + ') AS T;'
狀況二:只複製表裏的幾個字段,而且須要監控幾張表,這些表中,有些表是整表複製,有些表只複製幾個字段
因爲腳本里面沒有加入判斷複製項目,那麼對於這種狀況,這個腳本無能爲力
總結
在線上使用了事務複製這麼久不知道有多少人會按期的進行一下數據校驗,當主庫發生宕機的時候,你的從庫的數據是不是一致的
若是你的主庫由於硬件問題宕機,而且不能在最短的時間以內修復好,那麼你這時再作主從數據的一致性校驗已經沒有可能了
這時候你有兩個選擇
一、冒險使用從庫的數據,將從庫變爲主庫
二、放棄使用從庫,所有數據不要(固然了,所有數據不要是沒有可能的!)
固然,這樣直接在主庫上進行select查詢,會影響到主庫的業務,嚴重的話,可能會遇到死鎖
咱們能夠變通一下,使用快照數據庫來解決,linkedserver鏈接到快照數據庫進行數據內容對比
--進行數據對比以前先創建主庫的數據庫快照,再進行對比 CREATE DATABASE sss_ss ON (NAME = N'sss', FILENAME = N'E:\DataBase\sss.ss' ) AS SNAPSHOT OF [sss]; GO --linkedserver直接鏈接到快照數據庫進行對比 USE [sss_ss] go SELECT * FROM [dbo].[test] ------------------------------------------------ --對比完以後,再刪除快照數據庫 USE [master] GO DROP DATABASE [sss_ss]
至於在SQL Server中比較兩張表的數據一致性的方法和性能,能夠參考下面這篇文章
若有任何問題,歡迎你們向我反饋o(∩_∩)o
2015-3-23 補充:今天發現主從數據出現了不一致的狀況
能夠看到即便兩邊的數據記錄是同樣的,可是也不表明兩邊的數據是一致的,表裏面的數據內容也有可能不一致
Dest這張表兩邊都是82條記錄
咱們用SQL語句來檢查一下
SELECT * FROM [dbo].[Dest] EXCEPT SELECT * FROM [192.168.100.116].[Task].[dbo].[Dest] SELECT * FROM [192.168.100.116].[Task].[dbo].[Dest] EXCEPT SELECT * FROM [dbo].[Dest]
發現了ID爲36的這條記錄的Pause字段不同,第6臺是主,第11臺是從
詢問開發,開發說主那邊是正確的,而後對從那邊進行update回去正確的數據
注意:從數據庫是設置了db_datareader的,只有DBA才能夠對從數據庫進行update操做!
2015-3-24 補充:若是要複製的表中包含了smalldatetime數據類型,那麼在except比較的時候會出現不一致的狀況
表定義
CREATE TABLE [dbo].[Extension]( [ID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL, [AddOn] [smalldatetime] NOT NULL, [Hash] [nchar](32) NULL)
linkserver查出來的smalldatetime數據類型帶秒小數部分,而在本地查詢不會出現這種狀況
後來查了資料發現這中間有一個數據類型映射問題
解決方法有兩個:
一、表定義的時候不用smalldatetime 而用datetime
二、腳本里對用了smalldatetime 類型的字段作一下數據類型轉換
第二個解決方法要寫更加多的動態SQL判斷字段所用數據類型,拼接更加多的SQL
固然第一個解決方案會浪費空間,若是業務是不須要記錄秒級的時間的,那麼就浪費4個字節的空間了
詳細能夠查看MSDN