为什么SQL Server是大端字节序?

12

根据我所了解的,所有 Windows 版本和 .NET 都是小端字节序。那么为什么 SQL Server 要偏离 Microsoft 的规范呢?

我所指的“SQL Server 是大端字节序”是这样的:

SELECT CONVERT(VARBINARY, 255);

提供:

0x000000FF

而不是

0xFF000000

就像.NET的BitConverter.GetBytes()这样做。我猜SQL Server可能是将数字内部存储为小端字节序,然后CONVERT只是因为某种原因转换它。但无论如何,为什么呢?

编辑:

刚刚注意到这个...

DECLARE @q UNIQUEIDENTIFIER = '01234567-89ab-cdef-0123-456789abcdef';
SELECT @q;
SELECT CONVERT(VARBINARY, @q);

给了我:

01234567-89AB-CDEF-0123-456789ABCDEF

0x67452301AB89EFCD0123456789ABCDEF

什么鬼?


7
可能是为了兼容性。在古老的时代,微软并没有编写原始的 SQL Server,而是由 Sybase 编写的。他们在各种操作系统和硬件上支持了它。因此,很可能出于兼容性考虑,他们选择了一个字节顺序并坚持使用它。 - Damien_The_Unbeliever
那听起来…其实相当有道理。但是我添加的GUID是怎么回事呢?我相信在他们购买之后很长一段时间内都没有GUID… - Atario
2
请查看DBA.SE上的这个答案以获取详细解释。 - Phrancis
@Atario,我想知道您是否有机会审查我的答案。有什么想法吗?谢谢 :)。 - Solomon Rutzky
@srutzky 我以为你没有回答“为什么”的问题。微软的其他东西都像是单向的,而这个则像是双向的;你的回答并没有解决这个问题。 - Atario
@Atario 感谢您的回复。我重新阅读了我的答案,实际上已经回答了“为什么”的问题。由于您没有看到您认为自己看到的测试结果,因此没有直接的答案。我已更新我的答案,特别解释了 BitConverter.GetBytes() 的输出为何具有误导性,而且不能与 CONVERT(VARBINARY, 相类比。请再次阅读我的答案,如果您仍有疑问,请告诉我。谢谢。 - Solomon Rutzky
2个回答

18

是的:Windows和.NET采用小端格式。

那么为什么SQL Server采用大端格式呢?很简单:它不是;-)。甚至SQL Server的排序规则和 Unicode 支持页面都指出:

因为 Intel 平台是小端架构,Unicode 代码字符始终以字节交换的方式存储。

那么,当将 Int 值 255 转换时,为什么会得到一个大端二进制值呢?这里存在混淆。这个问题是有缺陷的,因为它基于错误的前提:你应该看到硬件和/或软件的字节序与转换后的值反映相同。但是,为什么你要这么做呢?字节序影响一个值的内部表示方式,即如何存储它。但它并不改变它本身。你可以将 DATETIME 转换为 INT,然后看到一个整数。但是,如果你将该整数保存在 INT 字段中,它将以反向顺序的 4 个字节形式存储,因为这是一个小端系统。但这与你从系统请求该值并显示给你所看到的内容无关。

例如,运行以下命令,以查看将 INT 值 301 转换为 BINARY(2) 的结果为 0x012D,因为 0x012D = 301,即十六进制。因此,将 0x012D 转换回 INT 将返回 301,正如预期的那样。如果原始的 Int 转 Binary 转换给你的是 0x2D01,那么它就不等于 301。

SELECT CONVERT(BINARY(2), 301), CONVERT(INT, 0x012D)
-- 0x012D,  301

然而,如果您创建了一个包含INT列的表,并将值"301"插入到该列中,并使用DBCC PAGE查看数据页在磁盘上的存在方式,则会按照所示的顺序看到以下十六进制数字:

2D 01 00 00

为了回答问题前提的一些证据:

是的,在.NET中执行BitConverter.ToString(BitConverter.GetBytes(255))会返回:

FF-00-00-00

但这并不是一个转换,因为GetBytes()没有将“值”进行转换,而是旨在显示内部系统表示形式,这取决于系统是小端还是大端。如果您查看BitConverter.GetBytes的MSDN页面,可能会更清楚它实际上正在做什么。

当转换实际值时,结果在不同系统之间不会(也不能)不同。所有系统(包括计算器)中的整数值256都将始终为0x0100,因为字节序与您在10进制、2进制、16进制等之间转换值无关。

在.NET中,如果要执行此转换,可以使用String.Format("{0:X8}", 255),它会返回:

000000FF

这与SELECT CONVERT(BINARY(4), 255);返回的结果相同,因为它们都将值进行了转换。这个结果没有被显示为Big Endian,而是被显示为它真实的值,这恰好与Big Endian的字节顺序匹配。

换句话说,当以100000000的位序列开始时,它可以用十进制形式表示为256,或者用十六进制形式表示(在SQL Server中称为BINARY / VARBINARY)为0x0100。字节序与此无关,这只是表示相同基础值的不同方式。

当在VARBINARYNVARCHAR之间转换时,可以看到SQL Server是Little Endian的更多证据。由于NVARCHAR是16位(即2字节)编码,因此我们可以看到字节顺序,因为字符没有数字等效项(不像256 -> 0x0100的示例),所以没有其他要展示的内容(显示Code Point值不可行,因为补充字符问题)。

如下所示,Latin大写字母A具有U+0041的Code Point(数值上等于65),将转换为VARBINARY0x4100,因为那就是该字符的UTF-16 Little Endian编码值:

SELECT CONVERT(VARBINARY(10), N'A'), -- 0x4100
       CONVERT(NVARCHAR(5), 0x4100), -- A
       CONVERT(INT, 0x4100),         -- 16640
       UNICODE(N'A'),                -- 65
       CONVERT(VARBINARY(8), 65);    -- 0x00000041

SELECT CONVERT(VARBINARY(10), N'ᄀ'), -- 0x0011
       CONVERT(NVARCHAR(5), 0x0011),  -- ᄀ
       CONVERT(INT, 0x0011),          -- 17
       UNICODE(N'ᄀ'),                -- 4352
       CONVERT(VARBINARY(8), 4352);   -- 0x00001100

此外,“Pile of Poo”表情符号(代码点U+01F4A9)可以使用代理对“D83D + DCA9”进行查看(NCHAR函数允许此操作),或者您可以注入UTF-16小端字节序列:

SELECT NCHAR(0xD83D) + NCHAR(0xDCA9) AS [SurrogatePair],
       CONVERT(NVARCHAR(5), 0x3DD8A9DC) AS [UTF-16LE];
--    

UNIQUEIDENTIFIER类似于“它是什么”和“它如何存储”两者不同且不需要匹配。请记住,UUID / GUID不像intchar等基本数据类型,而更像具有定义格式的实体,就像JPG或MP3文件一样。有关UNIQUEIDENTIFIER的更多讨论,请参见我在DBA.StackExcange上回答一个相关问题的答案(包括为什么它由Big Endian和Little Endian组合表示)。


1
这仍然没有意义。请参见下面的个人注释。 - Atario
首先,您似乎在说0x0100是一个值(如数字),而不是一系列字节,但是当涉及到转换为“BINARY”时,这是不正确的,因为该类型明确旨在成为一系列字节,而不是数字。 文本表示 0x0100看起来可能被解释为数字,但它只是BINARY类型正在执行的可视化。 - Atario
1
其次,我并不真正关心内部表示或底层架构;我只对现象作为一个黑盒子感兴趣。如果它将 2560x01000x0100256,那么我会称之为大端字节序;如果它将 2560x00010x0001256,那么我会称之为小端字节序,而不考虑其他任何因素。 - Atario
第三点,当我问“为什么”时,我不是指它的机制是什么。我的意思是微软在设计系统时的理由/意图/目标是什么。 - Atario
@Atario,你上面提到的第一点是不正确的。在SQL Server中将整数类型转换为[VAR]BINARY并不意味着显示底层编码字节序列。从TINYINT、SMALLINT、INT、BIGINT转换为[VAR]BINARY会显示十六进制等效值,与.NET中的String.Format("{X8}", intVal)相同。另一方面,BitConverter确实显示了底层编码。如果你问微软为什么决定在将INT->VARBINARY转换时不公开底层编码,我会说因为没有人会期望(或希望)它这样做,因为256是一个值,而不是一个字节序列。 - Solomon Rutzky
显示剩余2条评论

-1
这与字节序无关。字节序是指位/字节在物理存储中的排列方式,这只是展示了十进制数255的32位十六进制表示。
编辑: 您还可以在文档中查看: https://learn.microsoft.com/en-us/sql/t-sql/data-types/binary-and-varbinary-transact-sql?view=sql-server-ver16 "当其他数据类型转换为二进制或变长二进制时,数据会在左侧进行填充或截断。填充使用十六进制零来实现。"
所以与字节序无关,只是在左侧进行填充。

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