6

我在一个 SQL Server 2008 R2 上有许多不同的数据库。为了论证起见,我们称它们为 DB_A、DB_B 和 DB_C。我被要求开发为存储在 DB_A 上的存储过程。此存储过程将用于删除和创建索引,并将有关索引的一些额外信息存储在 DB_A 上的表中。当从 DB_C 或 DB_C 调用此存储过程时,它将能够在调用数据库上删除和创建索引,但将有关索引的额外信息存储在 DB_A 上的表中。

这就是我想做的事情:我希望存储的过程能够获得调用数据库的名称,而不必请求数据库名称作为参数。

这是一个简单的例子:

USE [DB_A]

CREATE PROC sp_WhatDatabaseAmICallingFrom
AS 
BEGIN
      DECLARE @calling_db NVARCHAR(128)
      SET @calling_db = DB_NAME()
      PRINT 'calling database: ' + @calling_db
END

当我在 DB_A 中执行存储过程时...

EXEC sp_WhatDatabaseAmICallingFrom

...它返回:“调用数据库:DB_A

当我在 DB_B 中执行存储过程...

USE DB_B
GO

EXEC DB_A.dbo.sp_WhatDatabaseAmICallingFrom

...它返回:“调用数据库:DB_A ”。

在阅读了各种 SQL Server 元数据函数之后,这正是它应该做的。但我想要更改代码,以便将 @calling_db 设置为调用数据库的名称,以便我的示例存储过程将打印:“调用数据库:DB_B ”。

不幸的是,我找不到任何可以做到这一点的元数据函数。关于如何做到这一点的任何想法?

4

3 回答 3

4

要使 SP 在当前连接的上下文中运行,您需要在master数据库上创建 SP使其成为系统对象。

USE MASTER 
GO 

CREATE PROC sp_WhatDatabaseAmICallingFrom
AS 
BEGIN
      DECLARE @calling_db NVARCHAR(128)
      SET @calling_db = DB_NAME()
      PRINT 'calling database: ' + @calling_db
END
GO

EXEC sp_ms_marksystemobject 'sp_WhatDatabaseAmICallingFrom'
GO

检查它是如何工作的:

USE [DB_A]
GO

EXEC sp_WhatDatabaseAmICallingFrom
GO
于 2013-06-13T12:28:46.967 回答
3

我知道这个线程已经很老了,但我发现了一些至少对我有帮助的东西。

如果您使用 DB_A 并在 DB_B 中调用存储过程,例如:

USE DB_A
EXEC db_b.dbo.sproc

我发现获取“调用数据库”ID 的唯一方法是在存储过程中对 sys.dm_tran_locks 运行选择。request_session_id 应该是您的 spid,resource_type 应该是 DATABASE,request_owner_type 应该是 SHARED_TRANSACTION_WORKSPACE。对于每个连接的会话,数据库上始终存在这样的共享锁。查询将类似于:

SELECT resource_database_id FROM sys.dm_tran_locks WHERE request_session_id = @@SPID and resource_type = 'DATABASE' and request_owner_type = 'SHARED_TRANSACTION_WORKSPACE'

尽管这要求执行用户至少具有服务器上的 VIEW SERVER STATE 权限。就我而言,这不是问题..

于 2015-09-02T14:10:11.057 回答
0

基于 Mike 的回答,这是一个适用于 Sql2012 的解决方案示例。

您必须在以具有足够权限('VIEW SERVER STATE')的用户身份登录时创建该函数,但使用该函数的人只需要执行该函数本身的权限。

USE [DB_A];
GO

CREATE FUNCTION dbo.GetCallingDbCatName()
RETURNS nvarchar(128) 
WITH EXECUTE AS SELF
AS
BEGIN
    DECLARE @result nvarchar(128);
    SELECT TOP 1 @result = DB_NAME(resource_database_id) 
        FROM sys.dm_tran_locks 
        WHERE request_session_id = @@SPID 
            AND resource_type = 'DATABASE' 
            AND request_owner_type = 'SHARED_TRANSACTION_WORKSPACE' 
        ORDER BY IIF(resource_database_id != DB_ID(), 0, 1);
    RETURN @result;
END
GO

USE [DB_A];
SELECT DB_A.dbo.GetCallingDbCatName(); --"DB_A"
USE [DB_B];
SELECT DB_A.dbo.GetCallingDbCatName(); --"DB_B"
USE [DB_C];
SELECT DB_A.dbo.GetCallingDbCatName(); --"DB_C"

从那里,您应该能够构建您需要的实际功能,而无需污染dbcat。

编辑 2019-07-26 - 仅供参考:这是有效的,因为除了mastertempdb之外的每个dbcat 连接(或使用)都将具有 SHARED_TRANSACTION_WORKSPACE 锁。下面的解释来自这个页面 (和另一个类似解释的页面)

SHARED_TRANSACTION_WORKSPACE 锁所有者的目的是防止 SQL Server 获取 EXCLUSIVE_TRANSACTION_WORKSPACE 锁,即防止进程在数据库正在使用时删除、恢复或更改数据库的可读性状态。SQL Server 没有为 master 和 tempdb 数据库获取这些锁的原因是这些数据库不能被删除,或者它们的可读性状态被改变。此外,我们从不恢复 tempdb,要恢复 master 数据库,我们必须以单用户模式启动整个服务器,因此,SHARED_TRANSACTION_WORKSPACE 锁是不必要的。

因此,当您从 DB_B 调用位于 DB_A 中的函数时,该查询返回 2 行;一种用于运行查询的 DB_B,另一种用于运行/存在函数的 DB_A。这也意味着上述查询不适用于更复杂的调用堆栈(DB_A 在 DB_B 中调用 func,然后在 DB_C 中调用 func 来尝试确定调用者的 dbcat;ORDER BY 将无法在 DB_A 和DB_B。)

最后,此解决方案也适用于 CLR 方法。一定要记得执行“包装器”Sql函数有足够的使用权限db_tran_locksSqlFunction属性必须有“SystemDataAccess = SystemDataAccessKind.Read”。“上下文连接”使用与调用者相同的 SPID,它位于程序集所在的 dbcat 中(因此,包装函数/存储过程所在的位置暴露了 CLR 方法。)

作为参考,这是我的测试 CLR 方法:

[SqlFunction(IsDeterministic = false, DataAccess = DataAccessKind.Read, SystemDataAccess = SystemDataAccessKind.Read), SqlMethod(OnNullCall = true)]
public static SqlString GetCallingDbcatName()
{
    string sqlResult = "";
    using (var connection = new System.Data.SqlClient.SqlConnection("context connection=true"))
    {
        connection.Open();
        var sqlCmd = connection.CreateCommand();
        sqlCmd.CommandText = @"
SELECT TOP 1 DB_NAME(resource_database_id) 
  FROM sys.dm_tran_locks
  WHERE request_session_id = @@SPID
    AND resource_type = 'DATABASE'
    AND request_owner_type = 'SHARED_TRANSACTION_WORKSPACE'
  ORDER BY IIF(resource_database_id != DB_ID(), 0, 1); ";
        sqlResult = sqlCmd.ExecuteScalar().ToString();
    }
    return sqlResult;
}
于 2019-02-22T20:24:05.353 回答