我在单个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”。
不幸的是,我找不到任何能够做到这一点的元数据功能。关于如何做到这一点的任何想法?
要使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
我知道这个帖子很老了,但我发现了一些至少帮助我的东西。
如果您正在使用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权限。在我的情况下,这不是一个问题,但..
基于迈克的答案,这是一个在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;
}