web-dev-qa-db-ja.com

リンクサーバーでExec SP)を実行し、それを一時テーブルに配置します

以下の問題についてサポートが必要です。

ケース1:ストアドプロシージャはサーバー1にあります-呼び出しはサーバー1からです

declare @tempCountry table (countryname char(50))
insert into @tempCountry
    exec [database1_server1].[dbo].[getcountrylist]
Select * from @tempCountry

結果:正常に実行されました

Case2:iこの同じストアドプロシージャが、次のようなリンクサーバーを使用して別のサーバーから呼び出されている場合:

declare @tempCountry table (countryname char(50))
insert into @tempCountry
    exec [database2_server2].[database1_server1].[dbo].[getcountrylist]
Select * from @tempCountry

結果

メッセージ7391、レベル16、状態2、行2
リンクサーバー「Server2_Database2」のOLEDBプロバイダー「SQLNCLI」が分散トランザクションを開始できなかったため、操作を実行できませんでした。

ケース

しかし、以下のように[一時テーブルを挿入せずに]ストアドプロシージャを個別に実行しようとすると、

exec [database2_server2].[database1_server1].[dbo].[getcountrylist]

結果:エラーなしでストアドプロシージャを実行し、データを返します。


SQL Server 2005を使用していることを忘れました。サーバー管理者によると、私が使用することを提案した機能は2005では使用できません。

7
user1431921

ここには2つの選択肢があります(私は信じています):

  1. [〜#〜] openquery [〜#〜] 行セット関数を使用して、MSDTC(およびすべての これら 分散トランザクションに関連する不快なもの)の使用を回避しようとする

    /[database2_server2]がリンクサーバーの名前であると仮定します(ここと以下)/

    declare @tempCountry table (countryname char(50)) insert into @tempCountry select * from openquery([database2_server2], '[database1_server1].[dbo].[getcountrylist]') select * from @tempCountry

[〜#〜]または[〜#〜]

  1. リンクサーバーのオプション_Enable Promotion Of Distributed Transaction_をFalseに設定して、ローカルトランザクションが分散トランザクションを促進し、MSDTCを使用しないようにすることができます。

    _EXEC master.dbo.sp_serveroption @server = N'database2_server2', @optname = N'remote proc transaction promotion', @optvalue = N'false'_

    元のクエリは正常に機能するはずです。

    declare @tempCountry table (countryname char(50)) insert into @tempCountry exec [database2_server2].[database1_server1].[dbo].[getcountrylist] select * from @tempCountry

    Enable Promotion Of Distributed Transaction=False

12
Andrey Morozov

リンクサーバーを完全に回避することは可能です。リモートインスタンス(つまり、Database1)への標準接続を確立するSQLCLRストアドプロシージャを作成できます。

以下のC#コードは、次のようなSQLCLRストアドプロシージャ用です。

  • オプションのデータベース名を使用できます。空の場合、現在のデータベースがデフォルトのデータベースになります。提供されている場合は、接続後にそのデータベースに変更されます(現在のデータベースがデフォルトのデータベースと異なる場合があります)。

  • オプションで偽装を使用できます。偽装(デフォルトの動作)がない場合、接続はSQL Serverサービスが実行されているWindowsログイン(つまり、[サービス]の[ログオン]アカウント)によって行われます。これは通常、呼び出し元が通常持っているよりも高いレベルのアクセス許可を提供するため、望ましくない場合があります。ログインがWindowsログインに関連付けられている場合、偽装を使用すると、ストアドプロシージャを実行するログインのセキュリティコンテキストが維持されます。 SQL Serverログインにはセキュリティコンテキストがないため、偽装を使用しようとするとエラーが発生します。

    ここに記載されているコードで偽装のオンとオフを切り替える機能はテストを目的としているため、偽装を使用する場合と使用しない場合の違いを簡単に確認できます。実際のプロジェクトでこのコードを使用する場合、通常、エンドユーザー(つまり呼び出し元)が設定を変更できるようにする理由はありません。一般に、偽装を使用する方が安全です。ただし、偽装を使用する際の主な問題は、WindowsログインでActive Directoryの委任が有効になっていない限り、ローカルマシンに制限されることです。

  • Server1を呼び出すインスタンスで作成する必要があります:Server2 in Database2

  • PERMISSION_SETEXTERNAL_ACCESSが必要です。これは、次の方法で処理するのが最適です。

    • visualStudioでアセンブリに署名する
    • [master]で、DLLから非対称キーを作成します
    • [master]で、この新しい非対称キーからログインを作成します
    • 新しいキーベースのログインにEXTERNAL ACCESS Assembly権限を付与します
    • [Database2]で、以下を実行します。
      ALTER Assembly [NoLinkedServer] WITH PERMISSION_SET = EXTERNAL_ACCESS;
  • 次のように実行する必要があります。
    EXEC dbo.RemoteExec N'Server1', N'Database1', 0;

    そして:
    EXEC dbo.RemoteExec N'Server1', N'Database1', 1;

    各実行後、以下を実行し、最初の2つのフィールドに注意してください。

    SELECT [login_name], [original_login_name], *
    FROM sys.dm_exec_sessions
    WHERE LEFT([program_name], 14) = N'Linked Server?';
    

C#コード:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Security.Principal;
using Microsoft.SqlServer.Server;

public class LinkedServersSuck
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void RemoteExec(
        [SqlFacet(MaxSize = 128)] SqlString RemoteInstance,
        [SqlFacet(MaxSize = 128)] SqlString RemoteDatabase,
                                  SqlBoolean UseImpersonation)
    {
        if (RemoteInstance.IsNull)
        {
            return;
        }

        SqlConnectionStringBuilder _ConnectionString =
            new SqlConnectionStringBuilder();
        _ConnectionString.DataSource = RemoteInstance.Value;
        _ConnectionString.Enlist = false;
        _ConnectionString.IntegratedSecurity = true;
        _ConnectionString.ApplicationName =
            "Linked Server? We don't need no stinkin' Linked Server!";

        SqlConnection _Connection =
            new SqlConnection(_ConnectionString.ConnectionString);
        SqlCommand _Command = new SqlCommand();
        _Command.CommandType = CommandType.StoredProcedure;
        _Command.Connection = _Connection;
        _Command.CommandText = @"[dbo].[getcountrylist]";

        SqlDataReader _Reader = null;
        WindowsImpersonationContext _SecurityContext = null;

        try
        {
            if (UseImpersonation.IsTrue)
            {
                _SecurityContext = SqlContext.WindowsIdentity.Impersonate();
            }

            _Connection.Open();

            if (_SecurityContext != null)
            {
                _SecurityContext.Undo();
            }

            if (!RemoteDatabase.IsNull && RemoteDatabase.Value != String.Empty)
            {
                // do this here rather than in the Connection String
                // to reduce Connection Pool Fragmentation
                _Connection.ChangeDatabase(RemoteDatabase.Value);
            }

            _Reader = _Command.ExecuteReader();

            SqlContext.Pipe.Send(_Reader);
        }
        catch
        {
            throw;
        }
        finally
        {
            if (_Reader != null && !_Reader.IsClosed)
            {
                _Reader.Close();
            }

            if (_Connection != null && _Connection.State != ConnectionState.Closed)
            {
                _Connection.Close();
            }
        }

        return;
    }
}
0
Solomon Rutzky