在链接服务器上执行SP并将其放在临时表中

时间:2014-12-31 21:44:25

标签: sql-server stored-procedures linked-server

在以下问题上需要一些帮助:

案例1 :存储过程在服务器1上 - 来自server1的调用

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   无法执行操作,因为OLEDB提供程序" SQLNCLI" for linkedserver" Server2_Database2"无法开始分布式交易。

案例3

但是当试图单独执行存储过程[没有临时表插入]时,如下所示

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

结果:执行存储过程没有任何错误并返回数据。


我忘了提到我正在使用SQL Server 2005.根据服务器管理员的说法,您使用的功能在2005年没有提供。

2 个答案:

答案 0 :(得分:12)

你有(我相信)两个选项:

  1. 使用these行集功能

    尝试避免使用MSDTC(以及与分布式事务相关的所有OPENQUERY不愉快的事情)

    / 假设(此处及以下)[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

答案 1 :(得分:0)

可以完全避免链接服务器。您可以创建一个SQLCLR存储过程,该过程与远程实例(即Database1)建立标准连接。

以下C#代码适用于SQLCLR存储过程:

  • 允许使用可选的数据库名称。如果为空,则当前数据库将是默认数据库,或者如果提供,它将在连接后更改为该数据库(以便当前数据库可以与默认数据库不同)

  • 允许选择使用模拟。没有模拟(默认行为),连接是由运行SQL Server服务的Windows登录进行的(即"登录为"帐户在"服务")。这可能不是所希望的,因为它通常提供比调用者通常具有的更高级别的权限。如果Login与Windows登录相关联,则使用Impersonation将维护Login执行存储过程的安全上下文。 SQL Server登录没有安全上下文,因此在尝试使用模拟时会出错。

    在此处提供的代码中打开和关闭模拟的功能仅用于测试目的,因此更容易看到使用模拟和不使用模拟之间的差异。在实际项目中使用此代码时,通常没有理由允许最终用户(即调用者)更改设置。使用模拟通常更安全。但是,使用模拟的主要困难在于它仅限于本地计算机,除非在Active Directory中为委派启用了Windows登录。

  • 应该在Server1

    中调用Server2Database2的实例上创建
  • 需要PERMISSION_SET EXTERNAL_ACCESS。最好通过以下方式处理:

    • 在Visual Studio中签署程序集
    • [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;

    每次执行后,运行以下内容并注意前两个字段:

    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;
    }
}