跨伺服器備份還原資料庫時,資料庫內的用戶與伺服器層級的登入因 SID 不匹配而斷裂,形成「孤立用戶」。應用連線報錯 `Login failed` 或 `User already exists`,實為 SID 不符。本文提供從偵測、修復、預防的完整閉環方案,涵蓋 `sp_change_users_login`、Contained Database User、遷移腳本自動化。
| 層級 | 物件 | 識別碼 | 用途 | |
|---|---|---|---|---|
| 伺服器層級 | Login (登入) | SID (Security Identifier, 二進位) | 身分驗證、連線權限、伺服器角色 | |
| 資料庫層級 | User (用戶) | SID (同登入 SID 建立關聯) | 資料庫權限、結構擁有者、角色成員 |
| 方法 | 適用場景 | 優點 | 缺點/風險 | 指令範例 |
|---|---|---|---|---|
| sp_change_users_login 'Auto_Fix' | 大量孤立用戶、名稱完全一致 | 一鍵修復、無需逐一指定 | 名稱不一致時會錯誤映射、可能授予錯誤權限、不建議生產使用 | `EXEC sp_change_users_login 'Auto_Fix', 'user1', NULL, 'pwd';` |
| sp_change_users_login 'Update_One' | 精確控制、名稱不同、需指定密碼 | 明確映射、可重設密碼、安全可控 | 逐一操作、繁瑣 | `EXEC sp_change_users_login 'Update_One', 'db_user', 'server_login';` |
| ALTER USER ... WITH LOGIN (SQL 2005+ 推薦) | 現代標準語法、支援 Windows 登入 | 語意清晰、不需密碼 (Windows 登入)、支援含資料庫用戶 | 需 SQL 2005+ | `ALTER USER db_user WITH LOGIN = server_login;` |
| 重建用戶 (Drop/Create) | 用戶權限簡單、可接受權限重授 | 徹底乾淨、無歷史包袱 | 需腳本化權限重授、可能破壞結構擁有權 | `DROP USER u; CREATE USER u FOR LOGIN l;` |
根治孤立用戶的終極方案:Contained Database User (包含資料庫用戶)。用戶認證資訊 (密碼雜湊) 直接儲存在資料庫內,無需伺服器層級登入,跨伺服器還原即可直接連線,零孤立風險。
-- 伺服器層級
EXEC sp_configure 'contained database authentication', 1; RECONFIGURE;
-- 資料庫層級
ALTER DATABASE YourDB SET CONTAINMENT = PARTIAL;
-- 新建
CREATE USER AppUser WITH PASSWORD = 'ComplexP@ssw0rd!', DEFAULT_SCHEMA = dbo;
-- 從 SQL Login 遷移 (SQL 2012+)
-- 步驟 1: 建立同名含資料庫用戶 (密碼同步)
DECLARE @sql NVARCHAR(MAX) = N'';
SELECT @sql += 'CREATE USER ' + QUOTENAME(name) + ' WITH PASSWORD = ''Migrate_' + name + '_' + CONVERT(NVARCHAR(36), NEWID()) + ''', DEFAULT_SCHEMA = ' + QUOTENAME(default_schema_name) + ';'
FROM sys.database_principals
WHERE type = 'S' AND principal_id > 4; -- SQL 用戶
EXEC sp_executesql @sql;
-- 步驟 2: 移除舊用戶 (權限已由含資料庫用戶繼承)
-- 注意:需先轉移結構擁有權
EXEC sp_change_users_login 'Report'; -- 確認無孤立
-- 批次轉移結構擁有權 (若有)
-- 最後 DROP 舊用戶
-- 原來
Server=OldServer;Database=YourDB;User Id=AppUser;Password=...;
-- 含資料庫用戶 (同樣語法,但驗證發生在資料庫層級)
Server=NewServer;Database=YourDB;User Id=AppUser;Password=...;
-- 無需在 NewServer 建立 Login!
-- usp_FixOrphanedUsers_PostRestore
CREATE PROCEDURE dbo.usp_FixOrphanedUsers_PostRestore
@TargetLoginPrefix NVARCHAR(50) = N'' -- 若登入名稱有前綴差異
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
-- 1. 偵測孤立 SQL 用戶
DECLARE @Orphans TABLE (DbUser SYSNAME, DbSID VARBINARY(85), LoginName SYSNAME);
INSERT INTO @Orphans
SELECT dp.name, dp.sid, sp.name
FROM sys.database_principals dp
LEFT JOIN sys.server_principals sp ON dp.sid = sp.sid
WHERE dp.type = 'S' AND dp.principal_id > 4 AND sp.sid IS NULL;
-- 2. 嘗試按名稱匹配現有登入 (含前綴)
UPDATE o SET o.LoginName = sp.name
FROM @Orphans o
JOIN sys.server_principals sp ON sp.name = @TargetLoginPrefix + o.DbUser
WHERE sp.type = 'S';
-- 3. 逐一修復 (Update_One)
DECLARE @DbUser SYSNAME, @LoginName SYSNAME;
DECLARE cur CURSOR LOCAL FAST_FORWARD FOR
SELECT DbUser, LoginName FROM @Orphans WHERE LoginName IS NOT NULL;
OPEN cur;
FETCH NEXT FROM cur INTO @DbUser, @LoginName;
WHILE @@FETCH_STATUS = 0
BEGIN
BEGIN TRY
EXEC sp_change_users_login 'Update_One', @DbUser, @LoginName;
PRINT 'Fixed: ' + @DbUser + ' -> ' + @LoginName;
END TRY
BEGIN CATCH
PRINT 'FAILED: ' + @DbUser + ' - ' + ERROR_MESSAGE();
END CATCH
FETCH NEXT FROM cur INTO @DbUser, @LoginName;
END
CLOSE cur; DEALLOCATE cur;
-- 4. 報告剩餘無法自動修復的
SELECT * FROM @Orphans WHERE LoginName IS NULL;
END;
| 動作 | 工具/腳本 | 驗收 |
|---|---|---|
| 匯出登入腳本 (含 SID、密碼雜湊) | sp_help_revlogin (Microsoft 官方腳本) / dbatools `Export-DbaLogin` | 目標伺服器建立同 SID 登入 |
| 啟用含資料庫認證 | `sp_configure 'contained database authentication', 1` | 目標伺服器支援 |
| 資料庫設定 PARTIAL CONTAINMENT | `ALTER DATABASE ... SET CONTAINMENT = PARTIAL` | 還原後保持設定 |
| 遷移用戶為含資料庫用戶 | 上述遷移腳本 | 零孤立用戶 |
| 驗證應用連線 | 測試連線字串、權限矩陣 | 全功能正常 |
孤立用戶本質是 SID 身分識別斷裂。短期用 `ALTER USER ... WITH LOGIN` 精確修復;長期必須推行 Contained Database User 徹底消除跨伺服器身分依賴。遷移自動化腳本 + `sp_help_revlogin` 匯出登入 + 含資料庫認證,構成「零孤立」鐵三角。