Eu crio dois bancos de dados, uma tabela no segundo banco de dados e um procedimento armazenado no primeiro banco de dados. O banco de dados cruzado do procedimento armazenado acessa a tabela. Eu crio um login do sql server e também mapeio esse login para um usuário em cada um dos bancos de dados. Dou permissão db_owner aos usuários. Aqui está o script que o realiza (estou conectado como administrador do sistema SQL ao executar o script):
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
Feito isso, eu me conecto ao servidor com SSMS sob a identidade do TestUser. Eu executo o procedimento armazenado TestSp no SSMS e ele é bem-sucedido.
Agora vou em frente e crio um trabalho que executa o mesmo procedimento armazenado. Eu faço assim (estou conectado como administrador de sistema SQL ao executar o script):
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
Depois que o trabalho é criado, eu o executo do SSMS (estou conectado como um administrador de sistema SQL ao fazer isso). O trabalho falha com o seguinte erro:
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.
E aqui está o script de limpeza para remover os objetos de banco de dados criados pelos scripts acima:
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
Perguntas:
- Por que resultados diferentes de um trabalho e do SSMS?
- Como faço para o trabalho funcionar (em vez de falhar)?
ATUALIZAÇÃO 1
Usando o google-fu avançado, consegui determinar que uma das respostas para a pergunta 2 pode ser esta:
ALTER DATABASE TestDatabase1 SET TRUSTWORTHY ON;
GO
RECONFIGURE WITH OVERRIDE;
GO
A pergunta 1 continua sem resposta
ATUALIZAÇÃO 2
Ok, acho que entendi, postei uma resposta abaixo. No entanto, uma coisa ainda não está clara. Esse erro do job, não acontece no SQL 2005. Acontece no SQL 2008 mas não no SQL 2005. Obviamente algo mudou. Alguém sabe o que exatamente foi a mudança?
Como ninguém postou feedback como resposta:
Por que resultados diferentes de um trabalho e do SSMS?
Aparentemente, o SQL Agent usa EXECUTE AS USER para executar etapas de trabalho se você especificar um usuário com
@database_user_name
parâmetro para osp_add_jobstep
procedimento armazenado. No meu exemplo acima, esse comportamento pode ser replicado no SSMS fazendo login como um administrador de sistema e executando este script:Observe que, se mudarmos
execute as user
paraexecute as login
neste trecho de código, o erro desaparece, mas aparentemente o agente sql usaexecute as user
.De acordo com o MSDN: Enquanto a opção de contexto [EXECUTE AS USER] para o usuário do banco de dados estiver ativa, qualquer tentativa de acessar recursos fora do banco de dados fará com que a instrução falhe. Isso inclui instruções de banco de dados USE, consultas distribuídas e consultas que fazem referência a outro banco de dados que usa identificadores de três ou quatro partes.
Mais informações estão no link útil @RemusRusanu: Problemas com permissões de banco de dados para sp_send_mail
Como faço para o trabalho funcionar (em vez de falhar)?
Outras opções também são consideradas no link de Remus Rusanu acima.
Eu enfrentei o mesmo problema e, a princípio, resolvi o problema ativando a
TRUSTWORTHY
propriedade no banco de dados. Mas, acho que não é uma maneira segura em termos de segurança.A prática recomendada para resolver esse problema é ir para as etapas de trabalho "-->Avançado-->" e deixá-lo em branco em "Executar como usuário". Ao fornecer uma conta de login em "Executar como usuário", você está instruindo o SQL Agent a representar essa conta e esse é o principal motivo pelo qual você está recebendo essa resposta de erro.
Por design da Microsoft, a representação está desativada. Para ativar a representação, você pode ativar o confiável, conforme mostrado abaixo:
Espero que isso ajude você.
Quando você está executando um trabalho e não especificou um usuário com o qual o trabalho será executado, o trabalho é executado no serviço SQL Server, que pode ou não ter permissões em seu banco de dados.
Quando você está executando um script no SSMS, ele é executado como o usuário com o qual você está conectado, que pode ou não ter permissões em seu banco de dados.
Esta é a diferença nos resultados. Você deve especificar no trabalho quais credenciais de usuário ele usará.