为什么这种显式转换只会导致链接服务器出现问题?

kry*_*tah 21 sql-server sql-server-2008-r2 linked-server sql-server-2012

我正在通过源服务器上的视图从链接服务器查询数据。该视图必须包含几个标准化列,例如Created,ModifiedDeleted,但在这种情况下,源服务器上的表没有任何合适的信息。因此,列被显式转换为它们各自的类型。我更新了视图,从

NULL AS Modified
Run Code Online (Sandbox Code Playgroud)

CAST(NULL as DateTime) as Modified
Run Code Online (Sandbox Code Playgroud)

但是,执行此更新后,视图会触发以下错误消息:

消息 7341,级别 16,状态 2,第 3 行无法从链接服务器“”的 OLE DB 提供程序“SQLNCLI11”获取列“(用户生成的表达式)。Expr1002”的当前行值。

我们已经在源服务器上完成了这种“显式转换” - 无需担心,我怀疑这个问题可能与所涉及的服务器版本有关。我们真的不需要应用这个演员表,但感觉更干净。现在我只是好奇为什么会发生这种情况。

服务器版本(来源):

Microsoft SQL Server 2012 - 11.0.5058.0 (X64) 2014 年 5 月 14 日 18:34:29 版权所有 (c) Microsoft Corporation Enterprise Edition(64 位),Windows NT 6.1(内部版本 7601:Service Pack 1)(管理程序)

服务器版本(链接):

Microsoft SQL Server 2008 R2 (SP1) - 10.50.2500.0 (X64) 2011 年 6 月 17 日 00:54:03 版权所有 (c) Microsoft Corporation Enterprise Edition(64 位),Windows NT 6.1(内部版本 7601:Service Pack 1)(管理程序) )

编辑
我刚刚意识到我没有发布所有有问题的专栏是一个错误,我必须为遗漏一个重要细节而道歉。我不知道我怎么没有早点注意到这一点。不过,这个问题仍然存在。

转换为 DateTime 时不会发生错误转换,而是将列转换为 UniqueIdentifier。

是罪魁祸首:

CAST(NULL AS UniqueIdentifier) AS [GUID]
Run Code Online (Sandbox Code Playgroud)

SQL Server 2008 R2 支持 UniqueIdentifiers,并且如评论中所述,视图执行的查询在链接服务器上运行良好。

Sol*_*zky 13

因此,在意识到CAST是在本地而不是在远程实例上完成后,我能够重现该错误。我之前曾建议升级到 SP3,希望能解决这个问题(部分原因是无法在 SP3 上重现错误,部分原因是无论如何它都是一个好主意)。但是,现在我可以重现该错误,很明显升级到 SP3 虽然仍然可能是一个好主意,但并不能解决这个问题。而且我还在 SQL Server 2008 R2 RTM 和 2014 SP1 中重现了错误(在所有三种情况下都使用“环回”本地链接服务器)。

看来,这个问题与做在那里执行查询时,或至少其中一部分(S)的它正在执行。我这样说是因为我能够使CAST操作工作,但只能通过包含对本地 DB 对象的引用:

SELECT rmt.*, CAST(NULL AS UNIQUEIDENTIFIER) AS [GUID]
FROM [Local].[database_name].[dbo].[table_name] rmt
CROSS JOIN (SELECT TOP (1) 1 FROM [sys].[data_spaces]) tmp(dummy);
Run Code Online (Sandbox Code Playgroud)

这确实有效。但以下得到原始错误:

SELECT rmt.*, CAST(NULL AS UNIQUEIDENTIFIER) AS [GUID]
FROM [Local].[database_name].[dbo].[table_name] rmt
CROSS JOIN (VALUES (1)) tmp(dummy);
Run Code Online (Sandbox Code Playgroud)

我猜测,当没有本地引用时,整个查询都会发送到远程系统执行,并且由于某些原因NULLs 无法转换为UNIQUEIDENTIFIER,或者NULLOLE DB 驱动程序可能错误地转换了 s 。


根据我所做的测试,这似乎是一个错误,但我不确定该错误是否在 SQL Server 或 SQL Server Native Client / OLEDB 驱动程序中。但是,转换错误发生在 OLEDB 驱动程序中,因此不一定是从INTto转换的问题UNIQUEIDENTIFIER(SQL Server 中不允许的转换),因为驱动程序没有使用 SQL Server 进行转换(SQL Server 也没有允许转换INTDATE,但 OLEDB 驱动程序成功处理,如其中一项测试所示)。

我跑了三个测试。对于成功的两个,我查看了 XML 执行计划,其中显示了正在远程执行的查询。对于这三个,我通过 SQL Profiler 捕获了任何异常或 OLEDB 事件:

事件:

  • 错误和警告
    • 注意力
    • 例外
    • 执行警告
    • 用户错误信息
  • 有机发光二极管
    • 全部
  • TSQL
    • 所有的除外
      • SQL:Stmt重新编译
      • XQuery 静态类型

列过滤器:

  • 应用名称
    • 不喜欢%Intellisense%
  • PID
    • 大于或等于50

测试

  • 测试 1

    • CAST(NULL AS UNIQUEIDENTIFIER) 那个有效

    SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
                 , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
    FROM [Local].[TEMPTEST].[sys].[objects] rmt;
    
    Run Code Online (Sandbox Code Playgroud)

    XML 执行计划的相关部分:

    SELECT rmt.*, CAST(NULL AS UNIQUEIDENTIFIER) AS [GUID]
    FROM [Local].[database_name].[dbo].[table_name] rmt
    CROSS JOIN (SELECT TOP (1) 1 FROM [sys].[data_spaces]) tmp(dummy);
    
    Run Code Online (Sandbox Code Playgroud)
  • 测试 2

    • CAST(NULL AS UNIQUEIDENTIFIER) 失败了

    SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
             --  , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
    FROM [Local].[TEMPTEST].[sys].[objects] rmt;
    
    Run Code Online (Sandbox Code Playgroud)

    (注意:我将子查询保留在那里,注释掉了,这样当我比较 XML 跟踪文件时就会减少一个差异)

  • 测试 3

    • CAST(NULL AS DATE) 那个有效

    SELECT TOP (2) CAST(NULL AS DATE) AS [Something]
             --  , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
    FROM [Local].[TEMPTEST].[sys].[objects] rmt;
    
    Run Code Online (Sandbox Code Playgroud)

    (注意:我将子查询保留在那里,注释掉了,这样当我比较 XML 跟踪文件时就会减少一个差异)

    XML 执行计划的相关部分:

    SELECT rmt.*, CAST(NULL AS UNIQUEIDENTIFIER) AS [GUID]
    FROM [Local].[database_name].[dbo].[table_name] rmt
    CROSS JOIN (VALUES (1)) tmp(dummy);
    
    Run Code Online (Sandbox Code Playgroud)

如果您查看测试 #3,它是SELECT TOP (2) NULL在“远程”系统上进行的。SQL Profiler 跟踪显示该远程字段的数据类型实际上是INT. 跟踪还显示客户端(即我从中运行查询的位置)的字段是DATE,正如预期的那样。从INT到的转换DATE,在 SQL Server 中会出错,在 OLEDB 驱动程序中工作得很好。远程值是NULL,因此直接返回,因此<ColumnReference Column="Expr1002" />.

如果您查看测试 #1,它是SELECT 1在“远程”系统上进行的。SQL Profiler 跟踪显示该远程字段的数据类型实际上是INT. 跟踪还显示客户端(即我从中运行查询的位置)的字段是GUID,正如预期的那样。从INTto的转换GUID(请记住,这是在驱动程序中完成的,OLEDB 将其称为“GUID”),这会在 SQL Server 中出现错误,但在 OLEDB 驱动程序中工作正常。远程值不是 NULL,因此它被替换为文字NULL,因此<Const ConstValue="NULL" />.

测试 #2 失败,因此没有执行计划。但是,它确实成功查询了“远程”系统,但无法传回结果集。SQL Profiler 捕获的查询是:

SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
             , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
Run Code Online (Sandbox Code Playgroud)

这是在测试#1 中完成的完全相同的查询,但在这里它失败了。还有其他细微的差异,但我无法完全解释 OLEDB 通信。但是,远程字段仍显示为INT(wType = 3 = adInteger / 四字节有符号整数 / DBTYPE_I4),而“客户端”字段仍显示为GUID(wType = 72 = adGUID / 全局唯一标识符 / DBTYPE_GUID)。OLE DB 文档没有多大帮助,因为GUID 数据类型转换DBDATE 数据类型转换I4 数据类型转换表明从I4转换为GUIDDBDATE不受支持,但DATE查询有效。

三个测试的 Trace XML 文件位于 PasteBin 上。如果您想查看每个测试与其他测试不同之处的详细信息,您可以将它们保存在本地,然后对它们进行“差异”。这些文件是:

  1. NullGuidSuccess.xml
  2. NullGuidError.xml
  3. 空日期成功.xml

尔格?

该怎么办?考虑到 SQL Native Client --SQLNCLI11从 SQL Server 2012 起已被弃用,可能只是我在顶部部分提到的解决方法。关于 SQL Server Native Client 主题的大多数 MSDN 页面在最佳:

警告

SQL Server Native Client (SNAC) 在 SQL Server 2012 之后不受支持。避免在新的开发工作中使用 SNAC,并计划修改当前使用它的应用程序。在Microsoft ODBC驱动程序的SQL Server提供了Microsoft SQL Server和微软Azure SQL数据库从Windows的本地连接。

有关更多信息,请参阅:


ODBC ??

我通过以下方式设置了 ODBC 链接服务器:

          <DefinedValue>
            <ColumnReference Column="Expr1002" />
            <ScalarOperator ScalarString="NULL">
              <Const ConstValue="NULL" />
            </ScalarOperator>
          </DefinedValue>
  ...
<RemoteQuery RemoteSource="Local" RemoteQuery=
 "SELECT 1 FROM &quot;TEMPTEST&quot;.&quot;sys&quot;.&quot;objects&quot; &quot;Tbl1001&quot;"
 />
Run Code Online (Sandbox Code Playgroud)

然后尝试:

SELECT TOP (2) CAST(NULL AS UNIQUEIDENTIFIER) AS [Something]
         --  , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
Run Code Online (Sandbox Code Playgroud)

并收到以下错误:

链接服务器“LocalODBC”的 OLE DB 提供程序“MSDASQL”返回消息“不支持请求的转换。”。
消息 7341,级别 16,状态 2,第 53
行无法从链接服务器“LocalODBC”的 OLE DB 提供程序“MSDASQL”获取列“(用户生成的表达式)。Expr1002”的当前行值。


聚苯乙烯

由于它涉及在远程和本地服务器之间传输 GUID,因此通过特殊语法处理非 NULL 值。我在运行时注意到 SQL Profiler 跟踪中的以下 OLE DB 事件信息CAST(0x00 AS UNIQUEIDENTIFIER)

SELECT TOP (2) CAST(NULL AS DATE) AS [Something]
         --  , (SELECT COUNT(*) FROM sys.[data_spaces]) AS [lcl]
FROM [Local].[TEMPTEST].[sys].[objects] rmt;
Run Code Online (Sandbox Code Playgroud)

缴费灵

我还通过OPENQUERY以下查询进行了测试:

          <DefinedValue>
            <ColumnReference Column="Expr1002" />
            <ScalarOperator ScalarString="[Expr1002]">
              <Identifier>
                <ColumnReference Column="Expr1002" />
              </Identifier>
            </ScalarOperator>
          </DefinedValue>
 ...
<RemoteQuery RemoteSource="Local" RemoteQuery=
 "SELECT TOP (2) NULL &quot;Expr1002&quot; FROM &quot;TEMPTEST&quot;.&quot;sys&quot;.&quot;objects&quot; &quot;Tbl1001&quot;" 
 />
Run Code Online (Sandbox Code Playgroud)

即使没有本地对象引用,它也成功了。SQL Profiler 跟踪 XML 文件已发布到 PasteBin:

NullGuidSuccessOPENQUERY.xml

XML 执行计划使用NULL常量显示它,与测试 #1 中的相同。

  • 这是一个多么晦涩难懂的问题。非常感谢@srutzky 的洞察力和研究,非常感谢。我会在未来的工作中牢记 SNAC 的折旧,并回退到上述解决方法。做得好! (2认同)