NVARCHAR 存储 SQL Server 上 UCS-2 编码不支持的字符

Posted

技术标签:

【中文标题】NVARCHAR 存储 SQL Server 上 UCS-2 编码不支持的字符【英文标题】:NVARCHAR storing characters not supported by UCS-2 encoding on SQL Server 【发布时间】:2020-12-22 05:26:24 【问题描述】:

通过 SQL Server 的documentation(和旧版documentation),没有_SC 排序规则的nvarchar 字段应使用UCS-2 ENCODING

从 SQL Server 2012 (11.x) 开始,当补充字符 (SC) 启用排序规则,这些数据类型存储全范围 Unicode 字符数据并使用 UTF-16 字符编码。如果一个 指定了非 SC 排序规则,则这些数据类型仅存储 UCS-2 字符编码支持的字符数据子集。

它还指出UCS-2 ENCODING 仅存储UCS-2 支持的子集字符。来自***UCS-2specification:

UCS-2,每个代码使用 0 到 65,535 之间的单个代码值 [...] 字符,并且只允许两个字节(一个 16 位字)来表示 那个值。因此,UCS-2 允许对每个 BMP 中表示字符的代码点。 UCS-2 不能 表示 BMP 之外的代码点。

因此,按照上述规范,我似乎无法存储如下表情符号:????其值为0x1F60D(或十进制的 128525,远高于 UCS-2 的 65535 限制)。但在 SQL Server 2008 R2 或 SQL Server 2019(均具有默认值 SQL_Latin1_General_CP1_CI_AS COLLATION)上,在 nvarchar 字段上,它被完美地存储和返回(尽管不支持与 LIKE= 的比较) :

SMSS 无法正确呈现表情符号,但这是从查询结果中复制和粘贴的值: ????

所以我的问题是:

    nvarchar 字段是否真的在 SQL Server 2008 R2 上使用 USC-2(我也在 SQL Server 2019 上进行了测试,具有相同的非 _SC 排序规则并得到相同的结果)?

    Microsoft 的 nchar/nvarchar 文档是否误导了“那么这些数据类型仅存储 UCS-2 字符编码支持的字符数据的子集”?

    UCS-2ENCODING 是否支持超过 65535 的代码点?

    当该字段的数据不受UCS-2ENCODING 的支持时,SQL Server 如何能够正确存储和检索该字段的数据?

注意:服务器的排序规则是 SQL_Latin1_General_CP1_CI_AS,字段的排序规则是 Latin1_General_CS_AS注意 2:原始问题说明了有关 SQL Server 2008 的测试。我测试并得到了相同的结果在 SQL Server 2019 上的结果,具有相同的 COLLATIONs注意 3:我测试的每个其他字符,在 UCS-2 支持的范围之外,都以相同的方式运行。有些是: ??????, ??????, ??????, ????, ????

【问题讨论】:

评论不用于扩展讨论;这个对话是moved to chat。 【参考方案1】:

这里需要对问题中发布的 MS 文档 sn-ps、示例代码、问题本身以及 cmets 中关于该问题的陈述进行一些澄清。我相信,我的以下帖子中提供的信息可以消除大部分困惑:

How Many Bytes Per Character in SQL Server: a Completely Complete Guide

首先要做的事情(这是唯一的方法,对吗?):我并不是在侮辱编写 MS 文档的人,因为单独的 SQL Server 是一个巨大产品,并且有很多东西要讲,等等,但目前(直到我有机会更新它),请谨慎阅读“官方”文档。关于排序规则 / Unicode 存在一些错误陈述。

    UCS-2 是一种处理 Unicode 字符集子集的编码。它以 2 字节为单位工作。使用 2 个字节,您可以对值 0 - 65535 进行编码。此代码点范围称为 BMP(基本多语言平面)。 BMP 是所有补充字符的字符(因为它们是 BMP 的补充),它确实包含一组专门用于以 UTF-16 编码补充字符(即 2048 个代理代码点)。这是 UTF-16 的完整子集。

    UTF-16 是一种处理所有 Unicode 字符集的编码。它也以 2 字节为单位工作。事实上,UCS-2 和 UTF-16 在 BMP 代码点和字符方面没有区别。不同之处在于 UTF-16 利用 BMP 中的 2048 个代理代码点来创建代理对,这些代理对是所有补充字符的编码。虽然补充字符是 4 字节(在 UTF-8、UTF-16 和 UTF-32 中),但它们实际上是两个 2 字节代码单元在以 UTF-16 编码时(同样,它们是UTF-8 中 4 个 1 字节单元,UTF-32 中 1 个 4 字节)。

    由于 UTF-16 只是扩展了 UCS-2 可以做的事情(通过实际定义代理代码点的用法),所以在字节序列中绝对 没有 的区别可以是 存储在任何一种情况下。用于在 UTF-16 中创建补充字符的所有 2048 个代理代码点在 UCS-2 中都是有效的代码点,它们只是在 UCS-2 中没有任何定义的用法(即解释)。

    NVARCHARNCHAR 和已弃用的-so-do-NOT-use-it-NTEXT 数据类型都存储以 UCS-2 / UTF-16 编码的 Unicode 字符。从存储的角度来看,绝对没有区别。因此,是否有东西(甚至在 SQL Server 之外)说它可以存储 UCS-2 并不重要。如果它可以做到这一点,那么它可以固有地存储 UTF-16。事实上,虽然我没有机会更新上面链接的帖子,但正如预期的那样,我已经能够在 Windows XP 上运行的 SQL Server 2000 中存储和检索表情符号(其中大部分是补充字符)。我认为直到 2003 年才定义了补充字符,当然在 1999 年开发 SQL Server 2000 时也没有定义。事实上(再次),UCS-2 仅在 Windows / SQL Server 中使用,因为微软在 UTF-16 最终确定和发布之前推进了开发(一旦它完成,UCS-2 就已经过时了)。

    UCS-2 和 UTF-16 之间的唯一区别是 UTF-16 知道如何解释代理项对(由一对代理代码点组成,因此至少它们被适当命名)。这就是 _SC 排序规则(并且,从 SQL Server 2017 开始,还有版本 _140_ 排序规则,其中包括对补充字符的支持,因此它们的名称中都没有 _SC)进来:它们允许内置在 SQL Server 函数中正确解释补充字符。而已!这些排序规则与存储和检索补充字符无关,它们甚至与排序或比较它们也没有任何关系(即使“排序规则和 Unicode 支持”文档特别说明这就是这些排序规则所做的——我的“待办事项”列表中要修复的另一个项目)。对于名称中既没有 _SC 也没有 _140_ 的排序规则(尽管新的 SQL Server 2019 Latin1_General_100_BIN2_UTF8 可能是灰色区域,至少,我记得有那里或与 Japanese_*_140_BIN2 排序规则存在一些不一致),内置函数仅处理 BMP 代码点(即 UCS-2)。

    不“处理”补充字符意味着不将两个代理代码点的有效序列解释为实际上是单个补充代码点。因此,对于非“SC”排序规则,BMP 代理代码点 1 (B1) 和 BMP 代理代码点 2 (B2) 只是这两个代码点,没有一个定义,因此它们显示为两个“无” (即 B1 后跟 B2)。这就是为什么可以使用 SUBSTRING / LEFT / RIGHT 将补充字符一分为二的原因,因为他们不知道将这两个 BMP 代码点保持在一起。但是“SC”排序规则会从磁盘或内存中读取这些代码点 B1 和 B2,并看到单个补充代码点 S。现在可以通过 SUBSTRING / CHARINDEX / 等正确处理。

    NCHAR() 函数(不是数据类型;是的,函数名称不佳;)也对 当前数据库 的默认排序规则是否支持补充字符很敏感。如果是,则传入 65536 和 1114111(补充字符范围)之间的值将返回非NULL 值。如果不是,则传入任何高于 65535 的值都将返回 NULL。 (当然,如果NCHAR() 总是有效,那会好得多,因为存储/检索总是有效,所以请投票支持这个建议:NCHAR() function should always return Supplementary Character for values 0x10000 - 0x10FFFF regardless of active database's default collation。

    幸运的是,您不需要“SC”归类来输出补充字符。您可以粘贴文字字符,或转换 UTF-16 Little Endian 编码的代理对,或使用 NCHAR() 函数输出代理对。以下适用于在 Windows XP 上运行的 SQL Server 2000(使用 SSMS 2005):

    SELECT N'?', -- ?
    CONVERT(VARBINARY(4), N'?'), -- 0x3DD8A9DC
    CONVERT(NVARCHAR(10), 0x3DD8A9DC), -- ? (regardless of DB Collation)
    NCHAR(0xD83D) + NCHAR(0xDCA9) -- ? (regardless of DB Collation)
    

    有关在使用非“SC”归类时创建补充字符的更多详细信息,请参阅我对以下 DBA.SE 问题的回答: How do I set a SQL Server Unicode / NVARCHAR string to an emoji or Supplementary Character?

    这些都不会影响您看到的内容。如果您存储一个代码点,那么它就在那里。它的行为方式——排序、比较等——由排序规则控制。但是,它的外观是由字体和操作系统控制的。没有字体可以包含所有字符,因此不同的字体包含不同的字符集,在更广泛使用的字符上有很多重叠。但是,如果字体具有映射的特定字节序列,则它可以显示该字符。这就是为什么要在 Windows XP 上运行的 SQL Server 2000(使用 SSMS 2005)中正确显示补充字符所需的唯一工作是添加包含字符的字体并进行一两次较小的注册表编辑(不更改 SQL Server)。

    SQL_* 归类和名称中没有版本号的归类中的补充字符没有排序权重。因此,它们都等同于彼此以及任何其他没有排序权重的 BMP 代码点(包括“空格”(U+0020)和“空”(U+0000))。他们开始在 _90_ 排序规则版本中修复此问题。

    SSMS 与此无关,除了可能需要用于查询编辑器的字体和/或网格结果和/或错误 + 消息更改为具有所需字符的字体。 (SSMS 不会渲染空间数据之外的任何内容;字符由显示驱动程序 + 字体定义 + 可能还有其他东西渲染)。

因此,文档中的以下声明(来自问题):

如果指定了非 SC 排序规则,则这些数据类型仅存储 UCS-2 字符编码支持的字符数据子集。

既荒谬又不正确。他们可能打算说数据类型只会存储 UTF-16 编码的子集(因为 UCS-2 子集)。此外,即使它说“UTF-16 字符编码”它仍然是错误的,因为您传入的字节将被存储(假设列或变量中有足够的可用空间)。

【讨论】:

惊人的答案!我真的很感激花在它上面的所有努力和时间。非常感谢您分享您的知识!但是…… 不过,关于低级别的东西,我没有得到最后一件事:UTF-16 使用代理对将可能的代码点扩展到超过 2 字节 (BMP) 到 4 字节。好的,这就是它可以存储0x1F60D 的大代码点的方式。只有 2 个字节的 UCS-2 怎么能存储一个比 2 个字节高的值?引擎拆分0x1F60D的大码点,比使用2组2bytes? 我的意思是,当我将 4 字节长的 0x1F60D 值传递给仅映射到 2 字节的编码时,它如何正确拆分它!?它怎么能允许接收这么大的数字(即代码点)? 我了解到(并且到处都这么说)编码负责将 de code point 映射到字节序列,反之亦然。那么像UCS-2 这样被限制为 2 字节的编码如何处理(即使它不解释)一个 4 字节的值? 如果我使用更有限的encoding,例如 1byte Windows-1252,就会发生预期的行为。不管我有多少可用空间,它根本无法存储0x1F60D 的大值。它不会将0x1F60D 映射到任何有效的字节序列...

以上是关于NVARCHAR 存储 SQL Server 上 UCS-2 编码不支持的字符的主要内容,如果未能解决你的问题,请参考以下文章

数据库存储过程实现增删改差(SQL SERVER 2008)

SQL Server存储过程

SQL Server:将数据类型 nvarchar 转换为数字时出错

使用 SQL Server 中的存储过程动态更新

Sql Server charvarcharncharnvarchar的区别

在现有 SQL Server 数据库上使用实体框架时禁用 VARCHAR 和 NVARCHAR 之间的转换