【问题标题】:How to return the remote database name which is calling a stored proc in a different database?如何返回调用不同数据库中存储过程的远程数据库名称?
【发布时间】:2019-07-26 22:34:11
【问题描述】:

我在一个 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 设置为调用数据库的名称,以便我的示例存储过程将打印:“calling database: DB_B”。

很遗憾,我找不到任何可以执行此操作的元数据函数。关于如何做到这一点的任何想法?

【问题讨论】:

  • 非常有趣的问题。出于好奇,您是否有理由不希望调用 sproc 的进程提供 dbname 作为参数(除了必须支持参数并提供它)?如果没有直接的方法来做到这一点,一种想法是使参数可选 @CallingDB NVARCHAR(128) = NULL 并检查值是否为空。如果是这样,默认使用 DB_NAME()。当然,这意味着来自其他数据库的调用需要提供名称...
  • 我不想使用参数的唯一真正原因是我认为必须有办法做到这一点,而且我讨厌在没有找到解决方案的情况下离开这样的挑战。
  • 仅供参考,同样的问题是asked on the DBA site,尽管尚未得到解答。提出的建议是使用扩展事件、SQL CLR 或使用 CONTEXT_INFO 维护您自己的调用堆栈。
  • 不确定这是否有帮助。但是,如果您在存储过程名称前加上“sp_”并将其放入主数据库,则 db_name() 将返回调用存储过程的数据库名称。当您调用存储过程时,不要使用数据库名称(即 exec master.. sp_WhatDatabaseAmICallingFrom)调用它,只需调用 exec sp_WhatDatabaseAmICallingFrom 。

标签: sql sql-server stored-procedures


【解决方案1】:

要使 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

【讨论】:

    【解决方案2】:

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

    如果您正在使用 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 权限。就我而言,这不是问题..

    【讨论】:

    • 这个问题是你的 SELECT,如给定的,返回 2 行;一个用于调用数据库目录,一个用于定义存储过程的数据库目录。您需要将“AND DB_ID() != resource_database_id”添加到您的 SELECT 中。 (此外,这种方法在 function 中效果很好。)
    【解决方案3】:

    根据 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"
    
    

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

    EDIT 2019-07-26 - 仅供参考:这是因为 每个 连接到(或使用)除 master 之外的 dbcat 或tempdb 将有一个 SHARED_TRANSACTION_WORKSPACE 锁。 The explanation below is from this page(和another page that explains it similarly)

    SHARED_TRANSACTION_WORKSPACE 锁拥有者的目的是 阻止 SQL Server 获取 EXCLUSIVE_TRANSACTION_WORKSPACE 锁,即防止进程删除、恢复或 更改数据库的可读性状态,而数据库在 利用。 SQL Server 没有为 master 获取这些锁的原因 和 tempdb 数据库是这些数据库不能被删除,或者 更改其可读性状态。此外,我们从不恢复 tempdb,而要恢复主数据库,我们必须启动整个 服务器处于单用户模式,因此再次使用 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 方法。一定要记得使用db_tran_locks的足够权限执行“包装器”Sql函数,并且SqlFunction属性必须有“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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-26
      • 1970-01-01
      相关资源
      最近更新 更多