web-dev-qa-db-ja.com

データベース間呼び出しがジョブで失敗するが、SSMSで成功する

2つのデータベースを作成します。2番目のデータベースにテーブルを作成し、最初のデータベースにストアドプロシージャを作成します。ストアドプロシージャクロスデータベースがテーブルにアクセスします。 SQLサーバーログインを作成し、このログインを各データベースのユーザーにマップします。ユーザーにdb_owner権限を与えます。これを実行するスクリプトは次のとおりです(スクリプトを実行すると、SQL sysadminとして接続されます)。

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

これが行われると、TestUserのIDでSSMSを使用してサーバーに接続します。 SSMSでTestSpストアドプロシージャを実行すると、成功します。

次に、同じストアドプロシージャを実行するジョブを作成します。私は次のようにしています(スクリプトを実行するときにSQL sysadminとして接続しています)。

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

ジョブが作成されたら、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.

上記のスクリプトによって作成されたデータベースオブジェクトを削除するクリーンアップスクリプトを次に示します。

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

質問:

  1. ジョブとSSMSの結果が異なるのはなぜですか?
  2. (失敗するのではなく)どのようにして仕事を成功させるのですか?

更新1

高度なgoogle-fuを使用して、質問2の回答の1つがこれである可能性があると判断できました。

ALTER DATABASE TestDatabase1 SET TRUSTWORTHY ON;
GO
RECONFIGURE WITH OVERRIDE;
GO

質問1は未回答のままです

更新2

わかりました。わかったので、以下に回答を投稿しました。ただし、1つはまだ明確ではありません。ジョブからのこのエラー。SQL2005では発生しません。SQL2008では発生しますが、SQL 2005では発生しません。明らかにsomethingが変更されました。誰が変更が正確に何だったか知っていますか?

5
Andrew Savinykh

誰も答えとしてフィードバックを投稿しないので:

ジョブとSSMSの結果が異なるのはなぜですか?

どうやらSQLエージェントは、@database_user_nameパラメータをsp_add_jobstepストアドプロシージャに指定した場合、 EXECUTE AS USER を使用してジョブステップを実行します。上記の私の例では、sysadminとしてログインしてこのスクリプトを実行することにより、この動作をSSMSで再現できます。

use TestDatabase1;
GO
execute as user = 'TestUser';
GO
select top 1 * from TestDatabase2.dbo.TestTable;
GO
REVERT
GO

このコードスニペットでexecute as userexecute as loginに変更すると、エラーはなくなりますが、SQLエージェントはexecute as userを使用しているようです。

MSDNによると:データベースユーザーへの[EXECUTE AS USER]コンテキストスイッチがアクティブである間、データベース外のリソースにアクセスしようとすると、ステートメントが失敗します。これには、USEデータベースステートメント、分散クエリ、3つまたは4つの部分からなる識別子を使用する別のデータベースを参照するクエリが含まれます。

詳細については、@ RemusRusanuのリンクを参照してください。 sp_send_mailのデータベースアクセス許可に関する問題

(失敗するのではなく)どうやってジョブを機能させるのですか?

ALTER DATABASE TestDatabase1 SET TRUSTWORTHY ON;
GO
RECONFIGURE WITH OVERRIDE;
GO

上記のRemus Rusanuのリンクでは、他のオプションも検討されています。

5
Andrew Savinykh

私は同じ問題に直面し、最初に、データベースのTRUSTWORTHYプロパティをオンにすることで問題を解決しました。しかし、私はそれがセキュリティの観点から安全な方法ではないことを発見しました。

この問題に取り組むためのベストプラクティスは、ジョブステップ「-> Advanced->」に移動して、「ユーザーとして実行」の下で空白にすることです。 「ユーザーとして実行」でログインアカウントを指定すると、SQLエージェントにそのアカウントを偽装するように指示することになります。これが、このエラー応答を受け取る主な理由です。

マイクロソフトの設計により、偽装は無効になっています。偽装を有効にするには、次に示すように信頼できるものを有効にします。

ALTER DATABASE TestDatabase1 SET TRUSTWORTHY ON;
GO
RECONFIGURE WITH OVERRIDE;
GO

これがお役に立てば幸いです。

1

ジョブを実行していて、ジョブを実行するユーザーを指定していない場合、ジョブはSQL Serverサービスの下で実行されます。SQLServerサービスは、データベースに対する権限を持っている場合と持っていない場合があります。

SSMSでスクリプトを実行しているときは、ログインしているユーザーとして実行されます。これには、データベースに対する権限がある場合とない場合があります。

これが結果の違いです。ジョブで使用するユーザー資格情報を指定する必要があります。

0
J.D. Walker