Jon*_*len 19 sql-server migration uuid
我最近继承了一个使用BINARY(16)
而不是UNIQUEIDENTIFIER
存储 Guid的 SQL Server 数据库。它对包括主键在内的所有内容都执行此操作。
我应该担心吗?
Sol*_*zky 23
我应该担心吗?
嗯,这里有几件事有点令人担忧。
第一:虽然 a UNIQUEIDENTIFIER
(ie Guid
) 是一个 16 字节的二进制值是正确的,但它也是正确的:
INT
可以存储在 中BINARY(4)
,DATETIME
可以存储在BINARY(8)
中等),因此 #2 ?sysname
作为 的别名NVARCHAR(128)
),可能有一个单独的 GUID 数据类型的原因。我能找到的三个行为差异是:
比较UNIQUEIDENTIFIER
SQL Server 中的值,无论好坏,实际上与比较BINARY(16)
值的方式不同。根据比较 GUID 和 uniqueidentifier 值的 MSDN 页面,在比较UNIQUEIDENTIFIER
SQL Server 中的值时:
值的最后六个字节是最重要的
虽然这些值不经常排序,但这两种类型之间存在细微差别。根据uniqueidentifier的 MSDN 页面:
排序不是通过比较两个值的位模式来实现的。
鉴于在 SQL Server 和 .NET 之间处理 GUID 值的方式存在差异(在上面链接的“比较 GUID 和 uniqueidentifier 值”页面中注明),将这些数据从 SQL Server 中提取到应用程序代码中可能无法正确处理如果需要模拟 SQL Server 比较行为,请使用应用程序代码。这种行为可以通过转换为 a 来模拟SqlGuid
,但开发人员知道这样做吗?
第二:基于以下陈述
它对包括主键在内的所有内容都执行此操作。
我通常会通过使用 GUID 作为 PK 而不是替代键以及使用PKINT
甚至BIGINT
作为 PK来关注系统性能。如果这些 GUID PK 是聚集索引,则更令人担忧。
OP 对@Rob 的回答发表的以下评论引起了额外的关注:
它是从我认为 MySQL 迁移的
GUID 可以以2 种不同的二进制格式存储。因此,可能会引起关注,具体取决于:
生成二进制表示的问题与 4 个“字段”中的前 3 个的字节顺序有关。如果您按照上面的链接访问 Wikipedia 文章,您将看到 RFC 4122 指定对所有 4 个字段使用“Big Endian”编码,但 Microsoft GUID 指定使用“Native”Endianness。嗯,Intel 架构是 Little Endian,因此前 3 个字段的字节顺序与遵循 RFC 的系统相反(以及在 Big Endian 系统上生成的 Microsoft 风格的 GUID)。第一个字段“数据 1”是 4 个字节。在一种字节顺序中,它将表示为 (假设) 0x01020304
。但在另一个字节序中它会是0x04030201
。所以如果当前数据库'BINARY(16)
该二进制表示是在遵循 RFC 的系统上生成的,然后将当前在该BINARY(16)
字段中的数据转换为 aUNIQUEIDENTIFIER
将导致与最初创建的 GUID 不同。如果值从未离开数据库,这并不会真正造成问题,并且这些值只是为了相等而不是为了排序而进行比较。
排序的问题很简单,它们在转换为UNIQUEIDENTIFIER
. 幸运的是,如果原始系统确实是 MySQL,那么首先从未对二进制表示进行排序,因为 MySQL 只有UUID的字符串表示。
如果二进制表示是在 Windows / SQL Server 之外生成的,那么对在数据库外部使用的字符串值的担忧会更加严重。由于字节顺序可能不同,因此字符串形式的相同 GUID 将导致 2 种不同的二进制表示,具体取决于转换发生的位置。如果应用程序的代码或客户给予在串形式的GUID作为ABC
从的二进制形式来123
和二进制表示的系统上产生以下的RFC,那么同一二进制表示(即123
)将转化成的字符串形式DEF
,当转换成一个UNIQUEIDENTIFIER
。同样地,原始字符串形式ABC
将转换成的二进制形式456
,当转换成一个UNIQUEIDENTIFIER
。
因此,如果 GUID 从未离开过数据库,那么除了排序之外就没有什么可担心的了。或者,如果从 MySQL 导入是通过转换字符串形式(即FCCEC3D8-22A0-4C8A-BF35-EC18227C9F40
)完成的,那么它可能没问题。否则,如果这些 GUID 是提供给客户或在应用程序代码中提供的,您可以通过获取一个并转换通过SELECT CONVERT(UNIQUEIDENTIFIER, 'value found outside of the database');
并查看是否找到预期记录来测试它们是如何转换的。如果您无法匹配记录,那么您可能必须将字段保留为BINARY(16)
.
很可能不会有问题,但我之所以提到这一点,是因为在正确的条件下可能会出现问题。
无论如何如何插入新的 GUID?在应用程序代码中生成?
如果之前对与导入在另一个系统上生成的 GUID 的二进制表示相关的潜在问题的解释有点(或很多)令人困惑,希望以下内容会更清楚一些:
DECLARE @GUID UNIQUEIDENTIFIER = NEWID();
SELECT @GUID AS [String], CONVERT(BINARY(16), @GUID) AS [Binary];
-- String = 5FED23BE-E52C-40EE-8F45-49664C9472FD
-- Binary = 0xBE23ED5F2CE5EE408F4549664C9472FD
-- BE23ED5F-2CE5-EE40-8F45-49664C9472FD
Run Code Online (Sandbox Code Playgroud)
在上面显示的输出中,“String”和“Binary”值来自相同的 GUID。“二进制”行下方的值与“二进制”行的值相同,但格式与“字符串”行的样式相同(即删除了“0x”并添加了四个破折号)。比较第一个和第三个值,它们并不完全相同,但它们非常接近:最右侧的两个部分相同,但最左侧的三个部分不同。但是如果仔细观察,您会发现三个部分中的每个部分都是相同的字节,只是顺序不同。如果我只显示前三个部分,并给字节编号可能更容易看出它们的顺序在两种表示之间有何不同:
字符串 = 1 5F 2 ED 3 23 4 BE – 5 E5 6 2C – 7 40 8 EE
二进制 = 4 BE 3 23 2 ED 1 5F – 6 2C 5 E5 – 8 EE 7 40(在 Windows / SQL Server 中)
因此,在每个分组中,字节的顺序是相反的,但仅限于 Windows 和 SQL Server。但是,在遵循 RFC 的系统上,二进制表示将反映 sting 表示,因为字节顺序不会发生任何逆转。
数据是如何从 MySQL 带入 SQL Server 的?这里有几个选择:
SELECT CONVERT(BINARY(16), '5FED23BE-E52C-40EE-8F45-49664C9472FD'),
CONVERT(BINARY(16), 0x5FED23BEE52C40EE8F4549664C9472FD),
CONVERT(BINARY(16), CONVERT(UNIQUEIDENTIFIER, '5FED23BE-E52C-40EE-8F45-49664C9472FD'));
Run Code Online (Sandbox Code Playgroud)
返回:
0x35464544323342452D453532432D3430
0x5FED23BEE52C40EE8F4549664C9472FD
0xBE23ED5F2CE5EE408F4549664C9472FD
Run Code Online (Sandbox Code Playgroud)
假设它是直接的二进制到二进制(即上面的 Convert #2),那么生成的 GUID,如果转换为实际的UNIQUEIDENTIFIER
,将是:
SELECT CONVERT(UNIQUEIDENTIFIER, 0x5FED23BEE52C40EE8F4549664C9472FD);
Run Code Online (Sandbox Code Playgroud)
返回:
BE23ED5F-2CE5-EE40-8F45-49664C9472FD
Run Code Online (Sandbox Code Playgroud)
这是错误的。这给我们留下了三个问题:
你总是可以担心。;)
该系统可能是从不支持唯一标识符的其他系统迁移而来的。还有其他你不知道的妥协吗?
设计者可能不知道 uniqueidentifier 类型。还有哪些事情是他们不知道的?
虽然从技术上讲 - 这不应该是一个主要问题。