包含是針對數據庫獨立性而來的,非包含數據庫(SQL Server 2012以前版本的所有數據庫,以及SQL Server 2012及以後的數據庫屬性CONTAINMENT 設置爲NONE的數據庫)是在實例master庫下進行登陸賬戶密碼驗證的;而包含數據庫可以通過本身帶有密碼的用戶直接與應用進行交互。
應用場景
部分包含數據庫和非包含數據庫最大的差異在於排序規則,即如果數據庫的排序規則和實例排序規則不一致,並且跨庫操作較少,則使用部分包含數據庫能極大的減輕開發者的負擔。這也是部分包含數據庫的應用場景之一。
非實例級故障轉移,如Always On、日誌傳送等如果使用包含數據庫,將登錄賬戶全部變爲包含用戶,就不需要額外的同步登陸賬戶及權限,可以極大限度的減少賬戶同步帶來的故障恢復時間,及DBA日常維護登陸賬戶權限同步的額外工作及時間。這是部分包含數據庫的應用場景之二。
創建部分包含數據庫
可以在創建數據庫時指定CONTAINMENT=PARTIAL直接創建部分包含數據庫,如下:
CREATE DATABASE test3
CONTAINMENT = PARTIAL
查看數據庫是否啓用部分包含屬性
SELECT name, containment, containment_desc
FROM sys.databases
WHERE name='test3'
從結果來看containment值爲0,containment_desc值爲NONE 表示數據庫並未啓用部分包含數據庫。下面我將啓用部分包含數據庫。
SSMS中啓用部分包含數據庫
可以直接從對象資源管理器中,右擊對應數據庫(本文使用的test3數據庫)→屬性→選項→包含類型→部分,如下圖:
點擊確定,即可。
T-SQL 啓用部分包含數據庫
當然,更改數據庫包含類型設置時,不能有用戶連接該數據庫,如果有用戶連接,可以使用如下腳本查出並kill掉相關進程:
USE master
GO
SELECT 'kill '+CONVERT(varchar(3),spid)
FROM sys.sysprocesses
WHERE dbid = DB_ID('test3')
下面給出啓用部分包含數據庫的T-SQL:
--啓用包含數據庫
USE [master]
GO
EXEC sp_configure 'show advanced options', 1
RECONFIGURE WITH OVERRIDE
EXEC sp_configure 'contained database authentication', 1
RECONFIGURE WITH OVERRIDE
ALTER DATABASE [test3] SET CONTAINMENT = PARTIAL WITH NO_WAIT
GO
此時再次查看sys.databases視圖,如下:
可以看到數據庫test3 部分包含已經開啓(contaiment值變爲1,containment_desc 值變爲 PARTIAL。
包含用戶連接
接下來我將在部分包含數據庫 test3 中創建測試包含用戶。當然除包含用戶可以連接外,登陸賬戶仍可以連接。
在部分包含數據庫test3中創建包含用戶Jack(帶密碼的 SQL 用戶) 並賦予其連接數據庫的權限,腳本如下:
USE test3
GO
--創建包含用戶Jack
CREATE USER Jack WITH PASSWORD = 'a8ea v*(Rd##+'
--賦予用戶Jack 連接數據庫的權限
GRANT CONNECT TO Jack
從SSMS的資源管理器中使用用戶Jack連接數據庫,如下:
直接點擊連接,會報如下錯誤:
這是因爲使用包含用戶連接包含數據庫時,需要指定對應的包含數據庫,在連接屬性中,指定要連接的部分包含數據庫test3,如下圖:
再點擊連接時,可以成功連接。從資源管理器中可以查看連接後的情況,如下圖:
下面我們回過頭來看一下 Jack 用戶屬性
帶密碼的 SQL 用戶屬性截圖:
帶登陸名SQL用戶
從兩張屬性截圖上來看,兩者之間的主要差異在於是否有密碼、是否有登陸名及默認語言。
SELECT password
FROM sys.sysusers
WHERE name = 'Jack'
有密碼的包含數據庫用戶的密碼哈希值存儲在包含的數據庫中。但不在系統視圖中存儲,如sys.sysusers、sys.database_principals均不存在密碼信息。
SELECT name, type, type_desc
, authentication_type
, authentication_type_desc
FROM sys.database_principals
WHERE name = 'Jack'
從這個視圖中可以看到,包含用戶的授權類型是數據庫級的(登陸賬戶授權是實例級的)。
測試與包含數據庫用戶同名的登陸名Jack
如果創建了一個有密碼的包含數據庫用戶,所使用的名稱與 SQL Server 登錄名相同,而且在 SQL Server 登錄名進行連接時將包含的數據庫指定爲初始目錄,則 SQL Server 登錄名將無法連接。該連接將被判定爲包含數據庫上的具有密碼主體的包含數據庫用戶發起,而不是基於 SQL Server 登錄名的用戶發起。這可能導致 SQL Server 登錄名遭遇到拒絕服務。
CREATE LOGIN Jack WITH PASSWORD='Password'
, CHECK_POLICY=OFF,CHECK_EXPIRATION=OFF
ALTER SERVER ROLE [sysadmin] ADD MEMBER [Jack]
GO
直接使用Jack登陸名連接實例,會報如下密碼不匹配錯誤:
最優的方案是創建包含用戶時,不要和登陸賬戶名重複。
遷移登陸賬戶爲帶有密碼的包含用戶
將現有的與登陸賬戶關聯的數據庫用戶改爲包含數據庫用戶,腳本如下:
USE [test]
GO
DECLARE @username SYSNAME;
DECLARE user_cursor CURSOR
FOR
SELECT dp.name
FROM sys.database_principals AS dp
JOIN sys.server_principals AS sp ON dp.sid = sp.sid
WHERE dp.authentication_type = 1
AND sp.is_disabled = 0;
OPEN user_cursor
FETCH NEXT FROM user_cursor INTO @username
WHILE @@FETCH_STATUS = 0
BEGIN
EXECUTE sp_migrate_user_to_contained @username = @username,
@rename = N'keep_name', @disablelogin = N'disable_login';
FETCH NEXT FROM user_cursor INTO @username
END
CLOSE user_cursor;
DEALLOCATE user_cursor;
包含數據庫的跨庫操作
只有包含數據庫才能進行跨庫操作,某個包含用戶如果需要做跨庫操作,需要做如下準備:
在每個數據庫中創建相同的包含的數據庫用戶來做到這點。在創建帶密碼的第二個用戶時,使用 SID 選項。下面的示例在兩個數據庫中創建兩個完全相同的用戶。
--返回SID,用於創建同名包含用戶
USE test3
SELECT SID
FROM sys.database_principals
WHERE name = 'Jack';
USE test2
CREATE USER Jack WITH PASSWORD = 'a8ea v*(Rd##+'
,sid=0x010500000000000903000000301792D81CCF6B4093A36928EE829FF0
這裏的密碼和test3中包含用戶Jack密碼相同。
接下來使用Jack用戶連接 test3,並在test3下執行如下腳本:
select * from test2.dbo.test
報如下錯誤:
消息 916,級別 14,狀態 1,第 1 行
服務器主體 "Jack" 無法在當前安全上下文下訪問數據庫 "test2"。
這是因爲我的test3、test2兩個數據庫的擁有者不同,test2的擁有者是sa,而test3的擁有者是創建該數據庫的登陸賬戶。將test3的擁有者改爲sa,腳本如下:
USE [test3]
GO
EXEC dbo.sp_changedbowner @loginame = N'sa', @map = false
GO
再在test3庫下執行跨庫查詢的腳本,即可成功:
接下來使用Jack用戶連接數據庫 test2,並在數據庫test2下執行如下腳本: