Context
语境
As SQL Server database administrators, we should all know that, most of the time, a database user is linked to a SQL Server login. We do this to tell SQL Server that a SQL login LoginA has access to database Db1 using the context and permissions of UserA database user.
作为SQL Server数据库管理员,我们都应该知道,大多数时候,数据库用户都链接到SQL Server登录名。 我们这样做是为了告诉SQL Server SQL登录LoginA使用UserA数据库用户的上下文和权限来访问数据库Db1 。
There are exceptions like the two following ones:
有如下两个例外:
We might think that this linkage is done by matching names, but it’s not the case. Normalization comes in and this mapping is actually stored using a unique identifier called “SID”:
我们可能认为这种链接是通过匹配名称完成的,但事实并非如此。 进行标准化,该映射实际上是使用称为“ SID”的唯一标识符存储的:
We can check SIDs for all our server principals using the following query :
我们可以使用以下查询来检查所有服务器主体的SID:
select principal_id,[sid],name
from sys.server_principals
;
If we look at documentation page of the sys.server_principals view, we can see that :
如果查看sys.server_principals视图的文档页面 ,我们可以看到:
If a mapping is defined for a given SQL Login, then its corresponding SID can be found in sys.database_principals using the following query:
如果为给定SQL登录名定义了映射,则可以使用以下查询在sys.database_principals中找到其对应的SID:
select [sid],name
from sys.database_principals
For simplicity, it’s common, but not mandatory, to use the exact same name for both SQL Login and Database User.
为简单起见,对SQL登录名和数据库用户使用完全相同的名称是常见的,但不是强制性的。
As you could expect, this linkage can be “broken” in many different ways that makes this definition “inconsistent”, that leaves database users without any link to a login. When this happens, we refer to such a database user as an orphaned database user.
如您所料,此链接可以通过许多不同的方式“断开”,从而使该定义“不一致”,从而使数据库用户无法登录。 发生这种情况时,我们将这种数据库用户称为孤立的数据库用户。
The situation is summarized in following figure.
下图总结了这种情况。
Let’s enlist some use cases that lead to orphaned database users:
让我们列举一些导致孤立数据库用户的用例:
As you can see above, there are two possible ways to manage these orphaned users:
如您在上面看到的,有两种可能的方法来管理这些孤立用户:
In the following sections, we will see how to list those orphaned database users and how to handle both cases listed above. Finally, we will review stored procedures that will be helpful in that situation.
在以下各节中,我们将看到如何列出那些孤立的数据库用户以及如何处理上面列出的两种情况。 最后,我们将回顾对这种情况有帮助的存储过程。
To be able to get a list of orphaned users for every databases of a given SQL Server instance, you have to run the following statement against each of them:
为了能够获得给定SQL Server实例的每个数据库的孤立用户列表,您必须针对每个数据库运行以下语句:
Use [YourDbName] ;
GO
exec sp_change_users_login @Action='Report' ;
GO
This stored procedure will return a two-columns dataset with firstly the name of an orphaned database user and secondly its corresponding security identifier.
该存储过程将返回一个两列的数据集,其第一是孤立数据库用户的名称,第二是其相应的安全标识符。
Alternatively, we can get the list of SIDs that are defined in sys.database_principals but not in sys.server_principals as follows:
或者,我们可以获取在sys.database_principals中定义但不在sys.server_principals中定义的SID列表,如下所示:
select p.name,p.sid
from sys.database_principals p
where p.type in ('G','S','U')
and p.sid not in (select sid from sys.server_principals)
and p.name not in (
'dbo',
'guest',
'INFORMATION_SCHEMA',
'sys',
'MS_DataCollectorInternalUser'
) ;
As we said previously, there are two ways to handle orphaned database users: either we drop or remap them.
如前所述,有两种方法可以处理孤立的数据库用户:要么删除它们,要么重新映射它们。
Database User Drop
数据库用户删除
Dropping a database user seems pretty straight forwards: we simply need to run the DROP USER statement and it’s over. But what if this database user owns objects inside the database? It’s not that simple…
删除数据库用户似乎很简单:我们只需要运行DROP USER语句就可以了。 但是,如果此数据库用户拥有数据库内部的对象怎么办? 不是那么简单...
If it’s really the action we want to perform, we will need not only to take a look at that aspect and correct it, but also to check that this user is not used as the execution context of one or more stored procedures, functions or event notifications. Here is the error we could get if that’s the case:
如果这确实是我们要执行的操作,我们不仅需要查看并纠正该方面,还需要检查该用户是否未被用作一个或多个存储过程,函数或事件的执行上下文。通知。 如果是这种情况,这是我们可能得到的错误:
Msg 15136, Level 16, State 1
消息15136,第16级,状态1
We will also need to remove group membership of that user and there are maybe some other aspects that I don’t cover it because I’ve never been confronted to them.
我们还需要删除该用户的组成员身份,也许还有其他一些我不涉及的方面,因为我从未遇到过他们。
An analysis of these aspects can be quite long and has to be done for all orphaned users. Plus, this action can be used in other database management tasks. So, this is a good candidate for automation and you will find attached to this article a script with a stored procedure that takes care of dropping a database user and performs some pre-checks before that. The script is called
对这些方面的分析可能相当长,必须对所有孤立用户进行分析。 另外,此操作可以在其他数据库管理任务中使用。 因此,这是自动化的一个很好的候选者,并且您会在本文中找到带有存储过程的脚本,该脚本负责删除数据库用户并在此之前执行一些预检查。 该脚本称为
Let’s review the details of this stored procedure.
让我们查看此存储过程的详细信息。
First, let’s see its parameters
首先,让我们看看它的参数
ALTER PROCEDURE [Administration].[DropDatabaseUser] (
@DbName VARCHAR(256),
@UserName VARCHAR(256),
@ResetOwnership BIT = 1,
@NewObjectOwner VARCHAR(256) = 'dbo',
@Debug BIT = 0
)
As you can see, it takes the name of the database in which we want to perform the drop and the name of the database user we want to drop. There is a flag bit to tell the procedure to assign database objects owned by @UserName to another database user @NewObjectOwner. By default, this user is dbo. Finally, there is a flag bit that will enable a debug mode and make this procedure more talkative.
如您所见,它采用了我们要在其中执行删除操作的数据库的名称以及我们要删除的数据库用户的名称。 有一个标志位告诉该过程将@UserName拥有的数据库对象分配给另一个数据库用户@NewObjectOwner 。 默认情况下,该用户是dbo 。 最后,有一个标志位将启用调试模式并使此过程更加健谈。
The first part of the stored procedure is a set of pre-requisities checks :
存储过程的第一部分是一组先需求检查:
Then it will get the list of owned schemas and owned roles and raise an error whenever @ResetOwnership parameter is not set to 1 and it founds something.
然后,只要@ResetOwnership参数未设置为1且找到了内容,它将获取拥有的架构和拥有的角色的列表,并引发错误。
Finally it will stack roles of which @UserName is member.
最后,它将堆叠@UserName成员的角色。
Once the list is complete, it will loop and either :
列表完成后,它将循环播放,并且:
When all that is done, we can finally run the DROP USER statement.
完成所有这些操作后,我们最终可以运行DROP USER语句。
Database User Remapping
数据库用户重新映射
For remapping, we must use the sp_change_users_login stored procedure we already used to report orphaned users.
为了进行重新映射,我们必须使用已经用于报告孤立用户的sp_change_users_login存储过程。
We can run it for a single database user mapping as follows:
我们可以为单个数据库用户映射运行它,如下所示:
EXEC dbo.sp_change_users_login
@Action = 'update_one',
@UserNamePattern = 'UserA',
@LoginName = 'LoginA'
;
Alternatively, we can try the autofix parameter :
另外,我们可以尝试使用autofix参数:
EXEC dbo.sp_change_users_login 'auto_fix', 'UserA';
But running this requires that the name of SQL Login to be the exact same as the database user we want to remap.
但是运行此命令要求SQL登录名与我们要重新映射的数据库用户完全相同。
This procedure also allows to create the login when it doesn’t exist.
此过程还允许创建不存在的登录名。
EXEC dbo.sp_change_users_login 'auto_fix', 'UserA,
null,'aVeryStrongP@ssw0rd' ;
In any of the methods presented above, this has to be performed one database at a time.
在上面介绍的任何方法中,都必须一次在一个数据库中执行此操作。
Components of the solution
解决方案的组成部分
In the previous section, we saw how to manage orphaned users for one database at a time. While this is good for testing purpose, we might think that it’s not affordable for production environments where we sometimes have 10 or more databases that have been restored and that we must take care of.
在上一节中,我们了解了如何一次管理一个数据库的孤立用户。 尽管这对于测试目的是有好处的,但我们可能会认为对于生产环境而言,这是负担不起的,因为生产环境中有时已经还原了10个或更多数据库,因此我们必须加以照顾。
That’s the reason why I developed a stored procedure called Administration.FindOrphanUser that will loop across all databases of the instance and optionally try to fix orphaned users (which means remapping) and/or drop them.
这就是为什么我开发了一个称为Administration.FindOrphanUser的存储过程的原因,该过程将遍历实例的所有数据库,并有选择地尝试修复孤立的用户(这意味着重新映射)和/或删除它们。
It’s built on a stored procedure called Common.RunQueryAcrossDatabases that runs a T-SQL statement across a set of databases. It’s like the undocumented sp_MSforeachdb stored procedure, but with its differences.
它基于称为Common.RunQueryAcrossDatabases的存储过程建立,该存储过程跨一组数据库运行T-SQL语句。 就像未记录的sp_MSforeachdb存储过程一样,但是有所不同。
Both Administration.FindOrphanUser and Common.RunQueryAcrossDatabases procedures are attached to this article.
本文附带了Administration.FindOrphanUser和Common.RunQueryAcrossDatabases过程。
As it’s not the aim of this article, I won’t insist on Common.RunQueryAcrossDatabases procedure and we will focus on the other one.
由于这不是本文的目的,因此我不会坚持使用Common.RunQueryAcrossDatabases过程,而我们将专注于另一过程。
So, what’s its interface? It takes 5 parameters:
那么,它的界面是什么? 它包含5个参数:
This gives the following in T-SQL:
这在T-SQL中提供了以下内容:
ALTER PROCEDURE [Administration].[FindOrphanUsers] (
@DatabaseName VARCHAR(256) = NULL,
@TryToFix BIT = 0,
@DropUnfixable BIT = 0,
@OutputType VARCHAR(16) = 'TABLE',
@Debug BIT = 0
)
This stored procedure will create a temporary table #OrphanUsersData that will contain all the information (and a little more) that are manipulated during execution.
此存储过程将创建一个临时表#OrphanUsersData ,其中将包含在执行过程中操纵的所有信息(以及更多信息)。
Here is the creation statement for this table.
这是此表的创建语句。
CREATE TABLE #OrphanUsersData (
DbName VARCHAR(256),
UserName VARCHAR(256),
UserType VARCHAR(256),
DefaultSchemaName VARCHAR(256),
CreationDate DATE,
ModifiedDate DATE,
FixIssue VARCHAR(16),
DropIssue VARCHAR(16),
DDL2Drop VARCHAR(MAX),
DDL2Remap AS 'Use ' + QUOTENAME(DbName) + ';' + CHAR(13) + CHAR(10) +
'EXEC dbo.sp_change_users_login @Action = ''update_one'', @UserNamePattern = ''' + UserName + ''', @LoginName = ''' + UserName + ''';' + CHAR(13) + CHAR(10)
);
Let’s review each column.
让我们回顾一下每一列。
We have obviously columns for database name and user name. Then we have some general information on that user:
我们显然有数据库名称和用户名的列。 然后,我们获得了有关该用户的一些常规信息:
The next two columns are related to the execution of Administration.FindOrphanUser stored procedure. It will contain the outcome of the execution that is implied by @TryToFix and @DropUnfixable parameters.
接下来的两列与Administration.FindOrphanUser存储过程的执行有关。 它包含@TryToFix和@DropUnfixable参数所隐含的执行结果。
Finally, we have the last two columns that will contain DDL statements. The first one will be used to drop the database user (it will actually be a call to former [Administration].[DropDatabaseUser] stored procedure). The second one will contain the statement for database user to login re-mapping. As this statement is pretty simple, we created it as a computed column.
最后,我们有最后两列将包含DDL语句。 第一个将用于删除数据库用户(实际上是对先前的[Administration]。[DropDatabaseUser]存储过程的调用)。 第二个语句将包含供数据库用户登录重新映射的语句。 由于该语句非常简单,因此我们将其创建为计算列。
This table will be used as destination in the query we will provide to Common.RunQueryAcrossDatabases stored procedure. Actually, this query will be formed as follows:
该表将在我们提供给Common.RunQueryAcrossDatabases存储过程的查询中用作目标。 实际上,此查询将形成如下:
INSERT INTO #OrphanUsersData
Once this stored procedure has completed the discovery of orphaned database users, if @TryToFix parameter is set to 0, then it wil loop on each database user and run the corresponding DDL2Remap statement. The outcome of this operation will be stored in FixIssue column.
一旦此存储过程完成了孤立数据库用户的发现,如果@TryToFix参数设置为0,则它将在每个数据库用户上循环并运行相应的DDL2Remap语句。 该操作的结果将存储在FixIssue列中。
Same goes for the database user removal if @DropUnfixable is set to 1.
如果@DropUnfixable设置为1,则删除数据库用户的操作也是如此。
Finally, the stored procedure returns a dataset that is either the content of #OrphanUsersData temporary table or a report based on values of FixIssue and DropIssue columns.
最后,存储过程返回一个数据集,该数据集要么是#OrphanUsersData临时表的内容,要么是基于FixIssue和DropIssue列的值的报告。
How to install?
如何安装?
Prior to installation, you must first select or create a database in which you will install this solution. You must also create following database schemas:
在安装之前,您必须首先选择或创建一个数据库,您将在其中安装此解决方案。 您还必须创建以下数据库架构:
You will find below the list of files that are attached to this article and must be run in that order.
您将在本文所附的文件列表下方找到,并且必须按此顺序运行。
How to use the solution?
如何使用解决方案?
This is, to me, the best way to manage orphaned users. You will find below the way I recommend to use these stored procedures.
对我来说,这是管理孤立用户的最佳方法。 您将在下面找到我建议使用这些存储过程的方式。
First, run with defaults. This will just list all orphaned users with everything we need to fix the situation.
首先,使用默认值运行。 这只会列出所有孤立的用户,并提供解决问题所需的一切。
EXEC [Administration].[FindOrphanUsers] @Debug = 0 ;
This call should also be automated so that a notification is sent to database administrators when one or more orphaned users are found. This automation could also be used by checking the TotalCount column of the dataset returned by following call:
此调用也应该是自动化的,以便在找到一个或多个孤立用户时将通知发送给数据库管理员。 也可以通过检查以下调用返回的数据集的TotalCount列来使用此自动化:
EXEC [Administration].[FindOrphanUsers]
@OutputType = 'REPORT',
@Debug = 0 ;
If some orphaned users are reported, then we can run the procedure with @TryToFix parameter set to 1:
如果报告了一些孤立用户,那么我们可以将@TryToFix参数设置为1来运行该过程:
EXEC [Administration].[FindOrphanUsers] @Debug = 0, @TryToFix = 1
If any error is returned by the procedure, check it and try to solve. Don’t hesitate to send me a message with details of the error so that I can try to update these stored procedures to the situation you found.
如果该过程返回了任何错误,请检查并尝试解决。 请随时向我发送错误详细信息,以便我可以尝试将这些存储过程更新为您发现的情况。
I don’t recommend running with @DropUnfixable parameter set to 1 if you are not sure of what you are doing.
如果不确定执行的操作,建议不要将@DropUnfixable参数设置为1。
翻译自: https://www.sqlshack.com/how-to-discover-and-handle-orphaned-database-users-in-sql-server/