SQL Server 部分包含数据库的原理

原文链接:https://blog.csdn.net/zhoujunah/java/article/details/103513426

 

包含是针对数据库独立性而来的,非包含数据库(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下执行如下脚本:

    点赞
    收藏
    分享

————————————————
版权声明:本文为CSDN博主「三空道人」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhoujunah/java/article/details/103513426

posted @ 2020-03-28 11:07  LuoCore  阅读(266)  评论(0编辑  收藏  举报