如何返回在另一个数据库中调用存储过程的远程数据库名称?

问题描述 投票:6回答:3

我在单个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”。

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

sql sql-server stored-procedures
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

3
投票

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

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

USE DB_A
EXEC db_b.dbo.sproc

我发现获取“调用数据库”id的唯一方法是在存储过程内对sys.dm_tran_locks运行select。 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权限。在我的情况下,这不是一个问题,但..


0
投票

基于迈克的答案,这是一个在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 - 仅供参考:这是有效的,因为除了master或tempdb之外的每个与(或使用)dbcat的连接都将具有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,并且要恢复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_A之间正确选择DB_B。)

最后,该解决方案也适用于CLR方法。您必须记住执行具有足够权限的“wrapper”Sql函数以使用db_tran_locks,并且SqlFunction属性必须具有“SystemDataAccess = SystemDataAccessKind.Read”。 “上下文连接”使用与调用者相同的SPID,它位于程序集所在的dbcat中(因此,包装函数/ sproc的生命周期暴露了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;
}
© www.soinside.com 2019 - 2024. All rights reserved.