跨数据库调用在作业中失败,但在 SSMS 中成功

And*_*ykh 6 security sql-server sql-server-2008-r2

我创建了两个数据库,第二个数据库中的一个表和第一个数据库中的一个存储过程。存储过程跨数据库访问表。我创建了一个 sql server 登录名,并将此登录名映射到每个数据库中的一个用户。我向用户授予 db_owner 权限。这是完成它的脚本(我在运行脚本时作为 SQL 系统管理员连接):

USE [master]
GO

CREATE DATABASE [TestDatabase1] ON  PRIMARY 
( NAME = N'TestDatabase1', FILENAME = N'd:\database\TestDatabase1.mdf' , SIZE = 3072KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
 LOG ON 
( NAME = N'TestDatabase1_log', FILENAME = N'd:\database\TestDatabase1_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
GO

CREATE DATABASE [TestDatabase2] ON  PRIMARY 
( NAME = N'TestDatabase2', FILENAME = N'd:\database\TestDatabase2.mdf' , SIZE = 3072KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
 LOG ON 
( NAME = N'TestDatabase2_log', FILENAME = N'd:\database\TestDatabase2_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
GO

USE [TestDatabase2]
GO

CREATE TABLE [dbo].[TestTable](
    [Test] [int] NULL
) ON [PRIMARY]
GO

USE [master]
GO
CREATE LOGIN [TestUser] WITH PASSWORD=N'password', DEFAULT_DATABASE=[TestDatabase1], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
GO
USE [TestDatabase1]
GO
CREATE USER [TestUser] FOR LOGIN [TestUser]
GO
USE [TestDatabase1]
GO
ALTER USER [TestUser] WITH DEFAULT_SCHEMA=[dbo]
GO
USE [TestDatabase2]
GO
CREATE USER [TestUser] FOR LOGIN [TestUser]
GO
USE [TestDatabase2]
GO
ALTER USER [TestUser] WITH DEFAULT_SCHEMA=[dbo]
GO
USE [TestDatabase2]
GO
EXEC sp_addrolemember N'db_owner', N'TestUser'
GO
USE [TestDatabase1]
GO
EXEC sp_addrolemember N'db_owner', N'TestUser'
GO
Run Code Online (Sandbox Code Playgroud)

完成后,我以 TestUser 的身份使用 SSMS 连接到服务器。我在 SSMS 中执行 TestSp 存储过程并成功。

现在我继续创建一个执行相同存储过程的作业。我是这样做的(我在运行脚本时作为 SQL 系统管理员连接):

USE [msdb]
GO

BEGIN TRANSACTION
DECLARE @ReturnCode INT
SELECT @ReturnCode = 0

IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1)
BEGIN
EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

END

DECLARE @jobId BINARY(16)
EXEC @ReturnCode =  msdb.dbo.sp_add_job @job_name=N'TestJob', 
        @enabled=1, 
        @notify_level_eventlog=0, 
        @notify_level_email=0, 
        @notify_level_netsend=0, 
        @notify_level_page=0, 
        @delete_level=0, 
        @description=N'No description available.', 
        @category_name=N'[Uncategorized (Local)]', 
        @owner_login_name=N'sa', @job_id = @jobId OUTPUT
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'TestStep', 
        @step_id=1, 
        @cmdexec_success_code=0, 
        @on_success_action=1, 
        @on_success_step_id=0, 
        @on_fail_action=2, 
        @on_fail_step_id=0, 
        @retry_attempts=0, 
        @retry_interval=0, 
        @os_run_priority=0, @subsystem=N'TSQL', 
        @command=N'SELECT TOP 1 * FROM TestDatabase2.dbo.TestTable', 
        @database_name=N'TestDatabase1', 
        @database_user_name=N'TestUser', 
        @flags=0
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
COMMIT TRANSACTION
GOTO EndSave
QuitWithRollback:
        IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION
EndSave:

GO
Run Code Online (Sandbox Code Playgroud)

创建作业后,我从 SSMS 运行它(在执行此操作时,我以 SQL 系统管理员身份连接)。作业失败并出现以下错误:

Date        10/04/2012 3:26:31 p.m.
Log     Job History (TestJob)

Step ID     1
Server      obfuscated
Job Name        TestJob
Step Name       TestStep
Duration        00:00:00
Sql Severity        14
Sql Message ID      916
Operator Emailed        
Operator Net sent       
Operator Paged      
Retries Attempted       0

Message
Executed as user: TestUser. The server principal "TestUser" is not able to access the database "TestDatabase2" under the current security context. [SQLSTATE 08004] (Error 916).  The step failed.
Run Code Online (Sandbox Code Playgroud)

这是删除由上述脚本创建的数据库对象的清理脚本:

USE [master]
GO

alter database TestDatabase1 set single_user with rollback immediate
GO

alter database TestDatabase1 set multi_user
GO

alter database TestDatabase2 set single_user with rollback immediate
GO

alter database TestDatabase2 set multi_user
GO

drop database TestDatabase1
GO

drop database TestDatabase2
GO

USE [msdb]
GO

declare @job_id uniqueidentifier
SELECT @job_id = job_id FROM msdb.dbo.sysjobs_view WHERE name = N'TestJob'

EXEC msdb.dbo.sp_delete_job @job_id=@job_id, @delete_unused_schedule=1
GO

DROP LOGIN [TestUser]
GO
Run Code Online (Sandbox Code Playgroud)

问题:

  1. 为什么工作和 SSMS 的结果不同?
  2. 我如何使工作发挥作用(而不是失败)?

更新 1

使用高级 google-fu 我能够确定问题 2 的答案之一可能是这样的:

ALTER DATABASE TestDatabase1 SET TRUSTWORTHY ON;
GO
RECONFIGURE WITH OVERRIDE;
GO
Run Code Online (Sandbox Code Playgroud)

问题 1 仍未得到解答

更新 2

好的,我想我明白了,我在下面发布了答案。然而有一件事还不清楚。作业中的这个错误,它不会发生在 SQL 2005 中。它发生在 SQL 2008 中,但不会发生在 SQL 2005 中。显然有些事情发生了变化。有谁知道具体是什么变化?

And*_*ykh 5

由于没有人发布反馈作为答案:

为什么工作和 SSMS 的结果不同?

显然,如果您使用存储过程的参数指定用户,则SQL 代理使用EXECUTE AS USER来运行作业步骤。在我上面的示例中,可以通过以系统管理员身份登录并运行此脚本在 SSMS 中复制此行为:@database_user_namesp_add_jobstep

use TestDatabase1;
GO
execute as user = 'TestUser';
GO
select top 1 * from TestDatabase2.dbo.TestTable;
GO
REVERT
GO
Run Code Online (Sandbox Code Playgroud)

请注意,如果我们在此代码片段中更改execute as userexecute as login,错误就会消失,但显然 sql 代理使用execute as user.

根据 MSDN:当 [EXECUTE AS USER] 上下文切换到数据库用户处于活动状态时,任何访问数据库外部资源的尝试都将导致语句失败。这包括 USE 数据库语句、分布式查询以及引用另一个使用三部分或四部分标识符的数据库的查询。

更多信息位于有用的@RemusRusanu 链接:sp_send_mail 的数据库权限问题

我如何使工作发挥作用(而不是失败)?

ALTER DATABASE TestDatabase1 SET TRUSTWORTHY ON;
GO
RECONFIGURE WITH OVERRIDE;
GO
Run Code Online (Sandbox Code Playgroud)

上面 Remus Rusanu 的链接中还考虑了其​​他选项。