2

我正在将数据库从 Microsoft SQL Server 迁移到 MySQL/MariaDB。在 MSSQL 上,数据库uniqueidentifier对所有主键使用 (GUID) 数据类型。NHibernate 用于在数据库和应用程序之间映射数据,并guid.comb采用该策略生成 GUID,以避免聚集索引的碎片化。

MySQL 没有专用的 GUID 数据类型,新的数据库模式BINARY(16)用于所有标识符。无需对 NHibernate 映射进行任何更改,我就可以启动我们的应用程序,持久保存新实体并从 MySQL 数据库加载它们。伟大的!然而,事实证明,按顺序生成的 GUID 在BINARY(16)列中的排序非常不按顺序排列,从而产生不可接受的索引碎片。

阅读这个问题后,发现MSSQL 有一种非常特殊的 GUID 排序方法。这 16 个字节首先按最后 6 个字节排序,然后按倒序排列,而我的幼稚 MySQL 实现首先按第一个字节排序,然后是下一个字节,依此类推。

这引出了我的问题:如何避免 MySQL 数据库中的这种碎片,同时保留现有的 GUID 和guid.comb策略?我自己有一个解决方案的想法(发布在下面),但我不禁觉得我可能错过了一些东西。当然,其他人之前一定已经处理过这个问题,也许有一种简单的方法可以解决这个问题。

4

1 回答 1

3

正如Alberto Ferrari 观察到的在 StackOverflow 上讨论的,Microsoft SQL Server 通过按特定顺序比较字节来对 GUID 进行排序。由于 MySQL 将BINARY(16)“直接”排序,我们需要做的就是在读取/写入数据库时​​重新排序字节。

NHibernate 允许我们定义自定义数据类型,可用于数据库和对象之间的映射。我已经实现了一个BinaryGuidType, 能够根据 MSSQL 对 GUID 排序的方式对产生的字节进行重新Guid.ToByteArray()排序,并将它们重新排序为构造函数接受的格式Guid(byte[])

字节顺序如下所示:

int[] ByteOrder = new[] { 10,11,12,13,14,15,8,9,6,7,4,5,0,1,2,3 };

将 a保存System.Guid到 aBINARY(16)是这样的:

var bytes = ((Guid) value).ToByteArray();
var reorderedBytes = new byte[16];

for (var i = 0; i < 16; i++)
{
    reorderedBytes[i] = bytes[ByteOrder[i]];
}

NHibernateUtil.Binary.NullSafeSet(cmd, reorderedBytes, index);

将字节读回 aSystem.Guid如下所示:

var bytes = (byte[]) NHibernateUtil.Binary.NullSafeGet(rs, names[0]);
if (bytes == null || bytes.Length == 0) return null;

var reorderedBytes = new byte[16];

for (var i = 0 ; i < 16; i++)
{
    reorderedBytes[ByteOrder[i]] = bytes[i];
}

这里的完整源代码BinaryGuidType

这似乎运作良好。在一个表中创建和持久化 10.000 个新对象,它们完全按顺序存储,没有索引碎片的迹象。

于 2012-07-09T11:39:33.583 回答