在现代软件开发中,唯一标识符(如GUID/UUID)的使用非常普遍。开发者经常需要在数据库中显式存储这些标识符,而不是依赖数据库系统生成的标识符。MongoDB及其驱动程序为GUID/UUID数据类型提供了内置支持,使得开发者可以立即开始使用GUID/UUID,而无需审查数据库规范。在大多数情况下,这种便利性是足够的。然而,在某些情况下,可能会遇到一些难以调试的意外问题。
当部署涉及多语言时,可能会发现应用程序不再按预期工作。例如,可能无法将订单与客户匹配。此外,数据可能会随着时间的推移而损坏,如果没有定期备份,将很难从这种灾难中恢复。
在本文中,将更深入地探讨使用GUID/UUID可能变得更加复杂的场景。将使意识到这些配置,并为提供一套最佳实践。
通用唯一标识符(UUID)是计算机软件中用作标识符的唯一数字。有时,术语全局唯一标识符(GUID)用来描述它。GUID是UUID标准的多种实现之一。UUID是128位值,通常显示为32个十六进制数字,用连字符分隔,例如:
176BF052-4ECB-4E1D-B48F-F3523F7F277F
(上面的示例是一个随机的版本4 UUID)
MongoDB内置了对GUID/UUID数据类型的支持,大多数MongoDB驱动程序也原生支持GUID/UUID。MongoDB本身将它们存储为二进制字段,当从软件访问这些二进制字段时,MongoDB驱动程序通常会将数据库中找到的值转换为特定于语言的GUID或UUID对象。例如,当使用C#驱动程序从MongoDB数据库读取UUID时,将返回一个类型为System.GUID的对象。另一方面,Java驱动程序将返回一个类型为java.util.UUID的对象。
这使得从应用程序代码中使用GUID/UUID数据类型变得非常方便和容易。
然而,当在多平台/多语言环境中使用UUID时,可能会遇到问题,因为使用不同的MongoDB驱动程序的不同编程语言访问数据库可能并不总是安全的,正如将在下面展示的。此外,当在第三方MongoDB管理软件中使用UUID数据类型时,应该小心,因为只有少数MongoDB工具在处理UUID数据类型时会给予适当的关注。
MongoChef是一个MongoDBGUI的例子,它提供了不同的选项来确保UUID被正确地解释、写入和更新。
MongoDB驱动程序通常在其二进制数据库表示和特定于语言的UUID数据类型之间转换UUID。最初,编码方法是平台特定的,并没有在不同的MongoDB驱动程序中一致实现。
在审查BSON规范时,会注意到两个二进制子类型被分配给UUID数据类型:最初,只有0x03子类型被用来指定UUID。为了解决由于特定于语言的UUID实现而产生的多平台环境中的可移植性问题,MongoDB设计者后来引入了新的0x04子类型。
使用0x04子类型存储UUID为二进制字段的字节顺序现在在所有MongoDB驱动程序中一致实现,而遗留的0x03子类型仅用于遗留数据。
使用二进制子类型0x04处理UUID时,可以保证能够从任何平台和任何编程语言访问数据,而不会出现这些兼容性问题。
1. 确保总是知道应用程序使用的UUID子类型是什么。可以通过查看现有数据的JSON表示(例如,在mongo shell中)来找出使用的子类型:
BinData(3,"B0AFBAMCAQAPDg0MCwoJgA==")
代表以二进制字段形式存储的UUID,使用遗留的0x03子类型
BinData(4,"SDcTgfx0SOq0Cl7DMRAzDQ==")
代表以二进制字段形式存储的UUID,使用0x04子类型
也可以使用支持遗留UUID的MongoDB GUI来访问这些信息。MongoChef将常规UUID注释为Binary - UUID,对于遗留UUID,它让知道当前使用的编码(例如Java、C#或无):
2. 配置驱动程序,以便在新部署中使用子类型0x04。MongoDB驱动程序通常将UUID存储为分配了遗留0x03子类型的二进制字段。这个配置可以更改:
C#:
BsonDefaults.GuidRepresentation = GuidRepresentation.Standard;
也可以在服务器、数据库和集合级别修改GuidRepresentation。
Python:
# 配置Python驱动程序的行为。阅读更多关于uuid_subtype属性的信息。
Java:
/*
* 将UUID对象转换为带有子类型0x04的二进制
*/
public static Binary toStandardBinaryUUID(java.util.UUID uuid) {
long msb = uuid.getMostSignificantBits();
long lsb = uuid.getLeastSignificantBits();
byte[] uuidBytes = new byte[16];
for (int i = 15; i >= 8; i--) {
uuidBytes[i] = (byte) (lsb & 0xFFL);
lsb >>= 8;
}
for (int i = 7; i >= 0; i--) {
uuidBytes[i] = (byte) (msb & 0xFFL);
msb >>= 8;
}
return new Binary((byte) 0x04, uuidBytes);
}
/*
* 将带有子类型0x04的二进制转换为UUID对象
* 请注意:没有检查子类型。
*/
public static UUID fromStandardBinaryUUID(Binary binary) {
long msb = 0;
long lsb = 0;
byte[] uuidBytes = binary.getData();
for (int i = 8; i < 16; i++) {
lsb <<= 8;
lsb |= uuidBytes[i] & 0xFFL;
}
for (int i = 0; i < 8; i++) {
msb <<= 8;
msb |= uuidBytes[i] & 0xFFL;
}
return new UUID(msb, lsb);
}
3. 配置MongoDB GUI以处理带有子类型0x03的遗留UUID。只有少数MongoDB GUI工具允许指定如何处理遗留UUID字段。
MongoChef内置了对0x03和0x04子类型的支持。可以在属性对话框中配置行为,并从以下选项中选择: