【问题标题】:SQL Server - defining an XML type column with UTF-8 encodingSQL Server - 使用 UTF-8 编码定义 XML 类型列
【发布时间】:2017-05-20 13:49:33
【问题描述】:

在 SQL Server 中定义的 XML 类型字段的默认编码是 UTF-16。我可以毫无问题地使用 UTF-16 编码的 XML 流插入该字段。

但如果我尝试使用 UTF-8 编码的 XML 流插入字段,插入尝试会收到错误响应
unable to switch encoding

问题:有没有办法将 SQL Server 列/字段定义为具有 UTF-8 编码?

更多信息

使用 Spring JDBCTemplate 执行插入操作。

XML 流由 JAXB Marshaller 生成,设置为 UTF-8 或 UTF-16 编码。

private String marshall(myDAO myTao, JAXBEncoding jaxbEncoding)
throws JAXBException{
    JAXBContext jc = JAXBContext.newInstance(ObjectFactory.class);
    m = jc.createMarshaller();
    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    if (jaxbEncoding!=null)
        m.setProperty(Marshaller.JAXB_ENCODING, jaxbEncoding.toString());
    StringWriter strw = new StringWriter();
    m.marshal(myTao, strw);
    String strw.toString();
}

在哪里...

public enum JAXBEncoding {
    UTF8("UTF-8"),
    UTF16("UTF-16")
    ;
    
    private String value;
    private JAXBEncoding(String value){
        this.value = value;
    }
    
    public String toString(){
        return this.value;
    }
}

【问题讨论】:

    标签: sql-server xml utf-8 character-encoding jaxb


    【解决方案1】:

    有没有办法将 SQL Server 列/字段定义为具有 UTF-8 编码?

    不,SQL Server 中唯一的 Unicode 编码是 UTF-16 Little Endian,这就是 NCHARNVARCHARNTEXT(自 SQL Server 2005 起已弃用,因此不要在新开发中使用它; 此外,与NVARCHAR(MAX) 相比,它很糟糕),并且处理了XML 数据类型。您无法像其他一些 RDBMS 一样选择 Unicode 编码。

    只要遵循以下三个规则,就可以将 UTF-8 编码的 XML 插入 SQL Server:

    1. 传入的字符串必须是数据类型VARCHAR,而不是NVARCHAR(因为NVARCHAR 始终是UTF-16 Little Endian,因此会出现无法切换编码的错误)。
    2. XML 有一个 XML 声明,明确指出 XML 的编码确实是 UTF-8:<?xml version="1.0" encoding="UTF-8" ?>
    3. 字节序列必须是实际的 UTF-8 字节。

    例如,我们可以导入包含screaming face emoji 的 UTF-8 编码 XML 文档(我们可以通过该链接获取该补充字符的 UTF-8 字节序列):

    SET NOCOUNT ON;
    DECLARE @XML XML = '<?xml version="1.0" encoding="utf-8"?><root><test>'
                        + CHAR(0xF0) + CHAR(0x9F) + CHAR(0x98) + CHAR(0xB1)
                        + '</test></root>';
    
    SELECT @XML;
    PRINT CONVERT(NVARCHAR(MAX), @XML);
    

    返回(在“结果”和“消息”选项卡中):

    <root><test>?</test></root>
    

    您在@Shnugo 的回答的评论中提到:

    将带有 utf-8 标头的 utf-8 编码流插入 SQL Server 2013 NVARCHAR 列没有问题。会不会有什么隐藏的问题?

    不,您没有在 NVARCHAR 列中存储 UTF-8 编码的任何内容(此外,没有 2013 版本的 SQL Server,但这可能只是一个错字)。 NVARCHAR 只是 UTF-16 Little Endian。您的 UTF-8 流很可能在传输到 SQL Server 期间被数据库驱动程序转换为 UTF-16 LE。这与 XML 列将使用的编码相同,但 XML 列会尝试将流从 UTF-8 转换为 UTF-16,但由于它已经是 UTF-16 而失败。这也意味着在离开 SQL Server 的过程中,存储在 NVARCHAR 列中的 XML 文档仍然会有 XML 声明,说明编码是 UTF-8,但绝对不是 UTF-8。

    如果您因为不想将来自 SQL Server XMLNVARCHAR 的 UTF-16 LE 转换为 UTF-8 而在输出时绝对需要数据为 UTF-8,那么您只能将数据存储为VARBINARY(MAX)

    【讨论】:

      【解决方案2】:

      如您所见,XML 将存储为unicode (utf-16, well, it's ucs-2 actually)。没有其他格式。

      在 SQL-Server 中,VARCHAR(MAX) 对应于extended ASCII (1-byte)NVARCHAR(MAX) 对应于unicode。两者都可以直接转换为 XML(只要字符串是有效的 XML)。必须知道,VARCHAR(MAX) 可能无法处理特殊字符......所以 - 如果这是一个问题 - 你应该坚持使用 unicode

      当编码声明包含在&lt;?xml ...?&gt;中时出现问题:

      这行得通:

      DECLARE @xml XML =
      '<?xml version="1.0" encoding="utf-8"?>
       <root>test</root>';
      
      SELECT @xml;
      

      这会产生错误:

      DECLARE @xml XML =
      '<?xml version="1.0" encoding="utf-16"?>
       <root>test</root>';
      
      SELECT @xml;
      

      但这又可以了(请参阅字符串文字前的前导 N):

      DECLARE @xml XML =
      N'<?xml version="1.0" encoding="utf-16"?>
       <root>test</root>';
      
      SELECT @xml;
      

      ##Fazit

      如果你传递 1 字节编码的字符串,但声明为 utf-16(或 vice-versa),你会遇到麻烦。最好的是,在没有 &lt;?xml ...?&gt;-declaration 的情况下传递您的 XML。

      ##更新

      你正在混合两种东西

      ##编码

      来自您的评论:

      UTF-8 是弹性长度的 unicode,长度从 1 字节到 4 字节不等。而 UTF-16 是固定长度的 2 字节 unicode。 UTF-8 现在似乎是事实上的 unicode std...

      是的,没错,UTF-8UTF-16unicode 的两种风格。但是将utf-8 称为新的de-facto 标准是不正确的。这在很大程度上取决于您的需求。生活在讲英语的国家,处理 纯拉丁文 文本将使用 UTF-8 节省一些字节。由于许多 3 和 4 字节代码,住在远东的某个地方会使您的文本变得非常臃肿。

      而且 - 这在数据库方面更重要 - 固定宽度非常容易处理。想象一下WHERE SUBSTRING(SomeUTF8Column,100,1)='A'。使用utf-16,引擎无需查看即可剪切字节200 和201,使用utf-8,必须分析直到字符100 的完整字符串才能找出第100 个字符的实际位置。我更喜欢 utf-8 仅在带宽或存储空间是一个重要因素的情况下... SQL Server 使用固定宽度的 1 字节编码,实际上没有 utf-8:扩展 ASCII 与排序规则相结合。

      将带有 utf-8 标头的 utf-8 编码流插入 SQL Server 2013 NVARCHAR 列没有问题

      而且 - 这对于 XML 而言更为重要 - XML 不存储为您看到的文本,而是存储为层次结构树。您几乎可以将所有内容存储在(N)VARCHAR

      DECLARE @s VARCHAR(MAX)='Don''t store me, I''m UTF-16. Your machine will explode!';
      

      这适用于任何组合。您可以声明NVARCHAR 和/或在文字前面放置N。由于隐式转换没有问题。

      但内部VARCHAR 无法处理更高的编码!。试试这个:

       DECLARE @s NVARCHAR(MAX)=N'слов в тексте';
       SELECT @s
      

      这仅适用于 NVARCHARN'Your string'

      ##XML 存储

      如前所述,XML 不是存储为您看到的文本,而是存储为一棵树。一切都针对性能进行了优化。因此 固定宽度 UTF-16。在任何情况下都省略了 xml 声明...

      当您传入一个物理编码为utf-8 但声明为其他内容(反之亦然)的字符串时,就会出现问题。你可以毫无问题地传入一个真正的UTF-16,并声明编码为utf-16(与utf-8相同)。

      ##Fazit

      如果您有一点机会包含 3 或 4 字节的 UTF-8 代码,您应该坚持使用 UTF-16。

      【讨论】:

      • UTG-8 是弹性长度的 unicode,长度从 1 字节到 4 字节不等。而 UTF-16 是固定长度的 2 字节 unicode。 UTF-8 现在似乎是事实上的 unicode 标准。 XML 流由 JAXB 编组器生成。我在 SQL Server 2013 NVARCHAR 列中插入带有 utf-8 标头的 utf-8 编码流没有问题。会不会有隐藏的问题?
      • 没有反对意见,但您的回答有点误导,因为 (1) varchar 不是“用于 utf-8”,而是用于(单字节字符) 为 SQL Server 实例定义的代码页。一些字符集(例如,“latin1”)看起来很像 UTF-8,但它们相同,并且将 UTF-8 提供给 varchar 列可能会产生一些 mojibake(例如, 'Montrél' 而不是 'Montréal')。 (2) UTF-8 和 UTF-16 不是“两种风格的 unicode”,它们是 Unicode 的两种 编码:字符(码位)相同,不同的是它们作为字节流的表示。
      • (再看第二点,很多人觉得this article很有帮助。)
      • @GordThompson,非常感谢您提供的链接!我读了这篇文章,混合了 Ahhshame :-) 真的很有启发性,写得很好。
      • @BlessedGeek,将所有 cmets 放在一起(SQL-Server 仅用作缓冲区),您可能会考虑将 UTF-8 编码的 XML 字符串填充到 VARBINARY(MAX) 类型的列中。您将无法在 SQL 级别对此进行任何操作(嗯,可能会使用一些花哨的 .Net-DLL-Assembly-methods),但您可以将其推入并调出.这对于缓冲区来说已经足够了......
      【解决方案3】:

      两步工作;首先将您的 UTF-8 编码为 textvarchar(MAX),然后再编码为 xml

      convert(xml, convert(text, '<your UTF-8 xml>'))
      

      【讨论】:

        【解决方案4】:

        MSDN 文档的“类型转换字符串和二进制实例”部分

        Create Instances of XML Data

        解释如何解释传入的 XML 数据。本质上,

        • 如果 SQL Server 以 nvarchar 形式接收 XML 数据,则它“假定使用两字节 unicode 编码,例如 UTF-16 或 UCS-2”,

        • 如果 SQL Server 以varchar 接收 XML 数据,则默认情况下它将使用为 SQL Server 实例定义的(单字节字符集)代码页,

        • 1234563解释为 UTF-8"。

        如果您的编组代码将 Java String 发送到 SQL Server,那么它很可能以 nvarchar 发送,因为 Java String 始终是 Unicode 字符串。这可以解释为什么 SQL Server 采用 UTF-16 编码。

        如果您真的需要使用 UTF-8 编码将 XML 数据发送到 SQL Server(虽然我无法想象为什么),那么您的编组代码可能需要生成一个 (UTF -8 个编码)字节,将作为varbinary 发送到 SQL Server。

        【讨论】:

        • "如果你真的需要发送带有 UTF-8 编码的 XML 数据(虽然我无法想象为什么)" - 那是因为我需要发送下游太多的消费者,前导字符串“” 因为我不能保证他们可以处理 encoding=utf-16.
        • 我使用 sql server 作为这些下游消费者的前端缓冲区。如果我必须在 xml 流的传输中包含更多的字符/字节处理,那将破坏拥有前端缓冲区的目的。
        • @BlessedGeek - 如果您只是使用 SQL Server 存储作为 XML 数据的缓存,那么您可能不需要将 XML 存储在真正的 xml 列中;您可以简单地将其“准备好服务”(即,正确编码并转义)存储在[n]varchar(max) 列中。请参阅 this MSDN document 的“XML 存储选项”部分,讨论真正的 xml 列是否会为您的特定用例提供任何真正的好处。
        猜你喜欢
        • 2018-09-20
        • 1970-01-01
        • 2020-04-07
        • 2012-11-07
        • 1970-01-01
        • 1970-01-01
        • 2012-08-02
        • 2017-02-15
        • 1970-01-01
        相关资源
        最近更新 更多