SQL Server 2005 資料庫鏡像的高可用性模式支援單一資料庫自動故障轉移。但在多資料庫應用系統中,若僅主庫故障轉移而其他關聯資料庫留在原主體,將導致應用層連線字串指向錯誤、跨資料庫查詢失敗、分散式交易不一致。本文實作基於 WMI 事件警示 + Service Broker 的協調機制,實現「一庫倒,全庫倒」的編排故障轉移。
應用系統
│
├── DB_Orders (主體: NodeA, 鏡像: NodeB, 見證: NodeW)
├── DB_Inventory (主體: NodeA, 鏡像: NodeB, 見證: NodeW)
├── DB_Accounting (主體: NodeA, 鏡像: NodeB, 見證: NodeW)
└── DB_Reporting (主體: NodeA, 鏡像: NodeB, 見證: NodeW)
-- 1. 檢查現狀
SELECT name, is_broker_enabled FROM sys.databases WHERE name IN ('msdb', 'master', 'DB_Orders', 'DB_Inventory');
-- 2. 啟用 msdb (Agent 警示需要) 及各業務資料庫
ALTER DATABASE msdb SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE;
ALTER DATABASE DB_Orders SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE;
-- ... 其他資料庫
-- 3. 建立訊息類型、合約、佇列、服務、路由 (簡化版,實際需雙向)
CREATE MESSAGE TYPE [Mirroring/FailoverCommand] VALIDATION = WELL_FORMED_XML;
CREATE CONTRACT [Mirroring/FailoverContract] ([Mirroring/FailoverCommand] SEND BY INITIATOR);
CREATE QUEUE [Mirroring/FailoverQueue];
CREATE SERVICE [Mirroring/FailoverService] ON QUEUE [Mirroring/FailoverQueue] ([Mirroring/FailoverContract]);
-- 路由需指向見證/夥伴實例的 Service Broker 端點
SELECT * FROM __InstanceModificationEvent WITHIN 10 WHERE TargetInstance ISA 'Database_Mirroring_State_Change' AND TargetInstance.NewState = 8
EXEC msdb.dbo.sp_add_alert
@name = N'Mirroring_State_Change_Failover',
@wmi_namespace = N'\\.\root\Microsoft\SqlServer\ServerEvents\MSSQLSERVER',
@wmi_query = N'SELECT * FROM __InstanceModificationEvent WITHIN 10 WHERE TargetInstance ISA ''Database_Mirroring_State_Change'' AND TargetInstance.NewState = 8',
@job_name = N'Job_Coordinated_Failover',
@enabled = 1;
-- Job_Coordinated_Failover 步驟 1: 掃描所有需協調的鏡像資料庫
DECLARE @Databases TABLE (DbName SYSNAME PRIMARY KEY, PartnerServer SYSNAME);
INSERT INTO @Databases VALUES
('DB_Orders', 'NodeB'),
('DB_Inventory', 'NodeB'),
('DB_Accounting', 'NodeB'),
('DB_Reporting', 'NodeB');
-- 步驟 2: 逐一發起故障轉移 (可平行化,這裡示範序列)
DECLARE @DbName SYSNAME, @Partner SYSNAME, @Sql NVARCHAR(MAX);
DECLARE cur CURSOR LOCAL FAST_FORWARD FOR SELECT DbName, PartnerServer FROM @Databases;
OPEN cur;
FETCH NEXT FROM cur INTO @DbName, @Partner;
WHILE @@FETCH_STATUS = 0
BEGIN
BEGIN TRY
-- 確認目前在主體端才執行 FAILOVER
IF (SELECT mirroring_role_desc FROM sys.database_mirroring WHERE database_id = DB_ID(@DbName)) = 'PRINCIPAL'
BEGIN
SET @Sql = QUOTENAME(@DbName) + N'.sys.sp_executesql N''ALTER DATABASE ' + QUOTENAME(@DbName) + N' SET PARTNER FAILOVER'';';
EXEC @Sql; -- 實際需動態 SQL 或連結伺服器
-- 記錄成功
INSERT INTO dbo.FailoverLog (DbName, Action, Status, RunTime) VALUES (@DbName, 'FAILOVER', 'SUCCESS', SYSUTCDATETIME());
END
END TRY
BEGIN CATCH
INSERT INTO dbo.FailoverLog (DbName, Action, Status, ErrorMsg, RunTime) VALUES (@DbName, 'FAILOVER', 'FAILED', ERROR_MESSAGE(), SYSUTCDATETIME());
END CATCH
FETCH NEXT FROM cur INTO @DbName, @Partner;
END
CLOSE cur; DEALLOCATE cur;
-- 發送端 (觸發警示的主體實例)
DECLARE @msg XML = (
SELECT DbName, 'FAILOVER' AS Action, SYSUTCDATETIME() AS Timestamp
FROM @Databases
FOR XML PATH('Database'), ROOT('FailoverCommand')
);
BEGIN DIALOG CONVERSATION @handle
FROM SERVICE [Mirroring/FailoverService]
TO SERVICE N'Mirroring/FailoverService'
ON CONTRACT [Mirroring/FailoverContract]
WITH ENCRYPTION = OFF;
SEND ON CONVERSATION @handle MESSAGE TYPE [Mirroring/FailoverCommand] (@msg);
END CONVERSATION @handle;
-- 接收端 (各資料庫實例上的啟動佇列預存程序)
CREATE PROCEDURE dbo.usp_ProcessFailoverCommand
AS
BEGIN
DECLARE @handle UNIQUEIDENTIFIER, @msg XML;
WHILE (1=1)
BEGIN
BEGIN TRAN;
WAITFOR (RECEIVE TOP(1) @handle = conversation_handle, @msg = message_body
FROM [Mirroring/FailoverQueue]), TIMEOUT 5000;
IF @@ROWCOUNT = 0 ROLLBACK TRAN; ELSE COMMIT TRAN;
IF @@ROWCOUNT = 0 BREAK;
-- 解析 XML 執行故障轉移
DECLARE @DbName SYSNAME;
DECLARE cur CURSOR FOR
SELECT T.c.value('(DbName)[1]', 'SYSNAME')
FROM @msg.nodes('/FailoverCommand/Database') AS T(c);
OPEN cur;
FETCH NEXT FROM cur INTO @DbName;
WHILE @@FETCH_STATUS = 0
BEGIN
EXEC ('ALTER DATABASE ' + QUOTENAME(@DbName) + ' SET PARTNER FAILOVER');
FETCH NEXT FROM cur INTO @DbName;
END
CLOSE cur; DEALLOCATE cur;
END CONVERSATION @handle;
END
END;
| 策略 | 實作要點 | 優缺點 |
|---|---|---|
| 連線字串 Failover Partner | 每個資料庫連線字串皆設 `Failover Partner=NodeB` | 簡單,但無法解決跨庫查詢主體分離問題 |
| 應用層感知故障轉移事件 | 訂閱 Service Broker 訊息或輪詢監控表,重建連線池 | 需修改應用,但能精確控制 |
| 中介層代理 (Proxy) | 應用連線至 Proxy (如 HAProxy/ProxySQL),Proxy 感知主體漂移自動路由 | 零代碼改動,單點風險需 Proxy HA |
| 可用性群組聽眾 (AG Listener) - 升級路徑 | 遷移至 AlwaysOn AG,單一 Listener 名稱掩蓋多資料庫故障轉移 | 根治方案,需 SQL 2012+ 企業版 |
| 測試案例 | 預期結果 | 驗證方式 |
|---|---|---|
| 單一資料庫主體強制關機 | 該庫故障轉移 → 觸發協調 → 其餘庫同步故障轉移 | 檢查 `mirroring_role_desc`、應用查詢正常 |
| 見證伺服器離線 | 無自動故障轉移 (Quorum 維持),僅記錄警示 | 確認無誤觸發 |
| 網路分區 (主體-見證斷開,主體-鏡像正常) | 主體保持主體 (Quorum 維持),無故障轉移 | 確認業務無中斷 |
| 協調作業執行中第二次觸發 | 冪等處理,不重複故障轉移 | 日誌檢查 |
WMI + Agent 警示 + Service Broker 協調機制,是在 SQL Server 2005/2008/2008R2 時代實現多資料庫協同故障轉移的標準做法。但其複雜度高、維護成本大、邊界情況難測盡。
長期演進路徑:規劃遷移至 AlwaysOn 可用性群組 (SQL 2012+),利用可用性群組聽眾 (Listener) 自動解決多資料庫故障轉移協調、唯讀路由、備份卸載等問題。若必須維持舊版本,請嚴格執行上述測試清單、建立完整操作手冊、定期演練。