Msg 9402,级别16,状态1,行9 XML解析:第1行,第38个字符,无法切换编码。

4

一段 T-SQL 代码在生产和测试环境中的表现不同。当在生产环境中执行以下代码时,将返回数据:

SELECT [col1xml]
FROM [DBName].[dbo].[Table1] (NOLOCK)
WHERE (cast([col1xml] as xml).value('(/Payment/****/trn1)[1]','nvarchar(20)') ='123456'))

然而,当在测试中运行相同的代码时,会出现以下错误。

Msg 9402,级别16,状态1,第9行 XML解析:第1行,第38个字符,无法切换编码

我已经看到了该网站提供的UTF转换修复方法,并且在生产和测试中都有效。但是,我需要向开发人员提供为什么会出现这种情况以及他们应该更改代码的理由(如果有必要)。

WHERE CAST(
REPLACE(CAST(col1xml AS VARCHAR(MAX)), 'encoding="utf-16"', 'encoding="utf-8"')
AS XML).value('(/Payment/****/trn1)[1]','NVARCHAR(max)') ='123456')

我已经比较了两个数据库,并寻找任何明显的问题,例如ANSI空值和ANSI填充。一切都相同,包括SQL Server的版本。这是SQL Server 2012 11.0.5388版本。环境之间的数据不同,但表模式相同,col1xml的数据类型为ntext


由于XML的编码(隐式或显式)完全由底层字符串类型确定,因此您的XML文档不应包含编码指令--这些指令除了占用空间外,还可能使解析器出现问题。如果可以,请在存储它们时将其完全剥离,如果无法避免获取它们(即不要用encoding="utf-8"替换它们),请将其替换为nothing。大多数XML库都可以被说服不输出XML声明,或者至少不输出带有编码的声明。 - Jeroen Mostert
坏习惯:到处加上 nolock - Dale K
1个回答

4
在SQL Server中,您应该将XML存储在类型为XML的列中。该本机类型具有许多优点。它速度更快并具有隐式有效性检查。
从您的问题中我知道,您将XML存储在NTEXT中。该类型已过时数个世纪,并且将不会被支持在未来的版本中! 您应该尽快更改此项! SQL Server知道两种字符串:
  • 1字节字符(CHARVARCHAR),这是扩展ASCII
    重要提示:这不是UTF-8!原生的UTF-8支持将成为即将发布的版本的一部分。
  • 2字节字符(NCHARNVARCHAR),这是UTF-16(UCS-2)
如果XML具有带编码的前导声明(在大多数情况下,这是utf-8utf-16),则可能会遇到麻烦。

如果XML以2字节字符串存储(至少NTEXT告诉我是这样的),则声明必须为utf-16。对于1字节字符串,应该使用utf-8

最好(也是最简单的方法)是完全省略声明。您不需要它。将XML存储在适当的类型中将自动删除此声明。

您应该做的是:创建一个新的XML类型列,并将所有XML移动到此列中。摆脱任何可能存在的TEXTNTEXTIMAGE列!

下一步是:快乐地享受本机XML类型的快速和轻松。

更新环境中的差异

环境之间的数据不同

错误发生在这里:

cast([col1xml] as xml)

如果您的列将以本机类型存储XML,则根本不需要进行转换(这非常昂贵!!)。但在您的情况下,此转换取决于实际的XML。由于它存储在NTEXT中,因此它是2字��字符串。如果您的XML以声明非受支持的编码(在大多数情况下为utf-8)开头,则会失败。

尝试使用以下代码:

DECLARE @xml2Byte_UTF16 NVARCHAR(100)='<?xml version="1.0" encoding="utf-16"?><root>test1</root>';
SELECT CAST(@xml2Byte_UTF16 AS XML);

DECLARE @xml1Byte_UTF8 VARCHAR(100)='<?xml version="1.0" encoding="utf-8"?><root>test2</root>';
SELECT CAST(@xml1Byte_UTF8 AS XML);

这个失败了

DECLARE @xml2Byte_UTF8 NVARCHAR(100)='<?xml version="1.0" encoding="utf-8"?><root>test3</root>';
SELECT CAST(@xml2Byte_UTF8 AS XML);

DECLARE @xml1Byte_UTF16 VARCHAR(100)='<?xml version="1.0" encoding="utf-16"?><root>test4</root>';
SELECT CAST(@xml1Byte_UTF16 AS XML);

尝试使用VARCHARNVARCHARutf-8utf-16进行编程实验...


CHARVARCHAR包含“非Unicode数据”。使用的排序规则不需要与ASCII兼容,称它们为“扩展ASCII”是误导性的。例如:SQL_EBCDIC037_CP1_CS_AS。(我认为所有支持的排序规则都是单字节的,至少是这样。)此外,你有没有关于UTF-8将在未来版本中支持CHAR存储的消息来源?我所知道的只有从SQL Server 2016开始支持代码页65001的批量导入/导出操作,这并不是同一件事情。 - Jeroen Mostert
谢谢。这些都是有用的信息。但我仍然无法找到环境之间的行为差异。我已经在数据库、表和列级别检查了排序规则。我一定会反馈XML数据类型和ntext被弃用的情况。 - ADTJOB
@JeroenMostert 嗯...这篇Connect文章并不支持我的说法。由于数据库的性质,固定字节宽度胜出是很明显的...我看了一下SQL Server 2016的新功能列表,看到了UTF-8支持的标题,但你是正确的:这并没有触及到普通字符串处理,只是BCP、BULK等方面。 - Shnugo
@ADTJOB 请看我的更新,解释环境的差异。 - Shnugo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接