多数据库有序GUID

背景

普遍的一致种数据库设计是动连续的平头为开主键,当新的数目插入到数据库时,由数据库自动生成。但那种设计不肯定符合有场景。

乘愈来愈多之利用Nhibernate、EntityFramework等ORM(对象关系映射)框架,应用程序被设计成工作单元(Unit
Of
Work)格局,需要以数据持久化以前生成主键,为了确保在多线程并作与站点集群环境受到主键的唯一性,最简易但是广大的章程是用主键设计成GUID类型。

干活单元是数据库应用程序通常应用的同样种设计情势,简单一点的话,就是指向几近只数据库操作举行包装,记录对象上之享有变更,并以最后交时两遍性将有所变更通过网业务写副数据库。目的是为了抽数据库调用次数以及避数据库长事务。关于工作单元的知识可以在园子里找到非凡多,在这里就是无举办详细的牵线了。

GUID(全球唯一标识符)也称为UUID,是平栽由算法生成的二进制长度也128号之数字标识符。在雅观状态下,任何总括机及处理器集群都非会面变动两独一样的GUID。GUID
的总额高达了2^128(3.4×10^38)个,所以随便大成稀只同GUID的可能性至极小,但并无为0。GUID一词有常为专指微软针对UUID标准的兑现。

RFC
41222
讲述了创造标准GUID,目前大部分GUID生成算法通常是一个良丰裕的擅自数,再做一些如网络MAC地址这种随意的当地组件信息。

GUID的优点允许开发人士随时创立新价值,而不论是需由数据库服务器检查值的唯一性,这不啻是一个到的解决方案。

许多数据库在创造主键时,为了充足发挥数据库的性,会自行在该列上创制聚集索引。我们先来说一样说啊是聚集索引,。集索引确定表中多少的大体顺序。聚集索引类似于电话簿,按姓氏氏排列数据。由于聚集索引规定数额以表中的大体存储顺序,由此一个注明也只能分包一个聚集索引。它亦可快捷查看找到数据,但是若插入数据库的主键不以列表的末梢,向表中补充加新行时虽挺缓慢。例如,看下那么些事例,在表中已经在三行数据(例子来自杰里米Todd的博客GUIDs as fast primary keys under multiple
databases

ID Name
1 Holmes, S.
4 Watson, J.
7 Moriarty, J.

这儿非常简单:数据进行仍对许ID列的相继储存。假若我们新加加相同实施ID为8的多少,不碰面发生另外问题,新行会追加的末段。

ID Name
1 Holmes, S.
4 Watson, J.
7 Moriarty, J.
8 Lestrade, I.

可如果我们记念插队一行的ID为5底数目:

ID Name
1 Holmes, S.
4 Watson, J.
5 Hudson, Mrs.
7 Moriarty, J.
8 Lestrade, I.

ID为7,8的数据行必须于下活动。虽然以这到底什么事情,但当您的数据量达到数百万尽的级别下,这便是个问题了。如若你还系念倘使各秒处理过剩蹩脚这种求,那但是正是难上加难了。

眼看即便是GUID主键引发的题目:它是随机发生的,所以在数额插入时,随时都晤面涉及到数码的移位,导致插入会杀缓慢,还晤面干大气非必要之磁盘活动。总括果出星星点点触及:

GUID最关键的题目即是她是自由的。大家用规划相同种植起平整之GUID生变为道,在后转的GUID类型总是比前的若相当,保证插入数据库的主键是当列表末尾追加的,那种我们誉为有序GUID

SQL Server,GUID排序规则

在授课有序GUID以前,我们务必先行理解一下GUID在.Net中及各样数据库被之排序规则,排序规则不相同,生成有序GUID的条条框框也会就变化。

128员的GUID首要爆发4片组成:Data1, Data2, Data3, and
Data4,你可看做下面这样:

11111111-2222-3333-4444-444444444444

Data1 占4独字节, Data2 2个字节, Data3 2个字节加 Data4
8单字节。大家独家的对只字节编上序号:

序号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Value 11 11 11 11 22 22 33 33 44 44 44 44 44 44 44 44

GUID在.Net中的排序规则

当.Net中,GUID默认的排序过段规则是按照错到右的,看上边是示例。

    var list = new List<Guid> {
        new Guid("00000000-0000-0000-0000-010000000000"),
        new Guid("00000000-0000-0000-0000-000100000000"),
        new Guid("00000000-0000-0000-0000-000001000000"),
        new Guid("00000000-0000-0000-0000-000000010000"),
        new Guid("00000000-0000-0000-0000-000000000100"),
        new Guid("00000000-0000-0000-0000-000000000001"),
        new Guid("00000000-0000-0000-0100-000000000000"),
        new Guid("00000000-0000-0000-0010-000000000000"),
        new Guid("00000000-0000-0001-0000-000000000000"),
        new Guid("00000000-0000-0100-0000-000000000000"),
        new Guid("00000000-0001-0000-0000-000000000000"),
        new Guid("00000000-0100-0000-0000-000000000000"),
        new Guid("00000001-0000-0000-0000-000000000000"),
        new Guid("00000100-0000-0000-0000-000000000000"),
        new Guid("00010000-0000-0000-0000-000000000000"),
        new Guid("01000000-0000-0000-0000-000000000000")
    };
    list.Sort();

    foreach (Guid guid in list) {
        Console.WriteLine(guid.ToString());
    }

输出结果:

00000000-0000-0000-0000-000000000001
00000000-0000-0000-0000-000000000100
00000000-0000-0000-0000-000000010000
00000000-0000-0000-0000-000001000000
00000000-0000-0000-0000-000100000000
00000000-0000-0000-0000-010000000000
00000000-0000-0000-0010-000000000000
00000000-0000-0000-0100-000000000000
00000000-0000-0001-0000-000000000000
00000000-0000-0100-0000-000000000000
00000000-0001-0000-0000-000000000000
00000000-0100-0000-0000-000000000000
00000001-0000-0000-0000-000000000000
00000100-0000-0000-0000-000000000000
00010000-0000-0000-0000-000000000000
01000000-0000-0000-0000-000000000000

透过者的出口结果,我们可获排序的权重如下:

序号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
权重 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Value 11 11 11 11 22 22 33 33 44 44 44 44 44 44 44 44

立刻和数字排序规则平等,从右到左进行逐展开排序(数字越小,权重越强,排序的预级更强)。

GUID在逐个数据库被的排序规则

在SQL
Server数据库中,我们有相同种相当简单的方来比简单个GUID类型的大小值(其实在SQL
Server数据库被叫UniqueIdentifier类型):

With UIDs As (--                         0 1 2 3  4 5  6 7  8 9  A B C D E F
            Select ID =  1, UID = cast ('00000000-0000-0000-0000-010000000000' as uniqueidentifier)
    Union   Select ID =  2, UID = cast ('00000000-0000-0000-0000-000100000000' as uniqueidentifier)
    Union   Select ID =  3, UID = cast ('00000000-0000-0000-0000-000001000000' as uniqueidentifier)
    Union   Select ID =  4, UID = cast ('00000000-0000-0000-0000-000000010000' as uniqueidentifier)
    Union   Select ID =  5, UID = cast ('00000000-0000-0000-0000-000000000100' as uniqueidentifier)
    Union   Select ID =  6, UID = cast ('00000000-0000-0000-0000-000000000001' as uniqueidentifier)
    Union   Select ID =  7, UID = cast ('00000000-0000-0000-0100-000000000000' as uniqueidentifier)
    Union   Select ID =  8, UID = cast ('00000000-0000-0000-0010-000000000000' as uniqueidentifier)
    Union   Select ID =  9, UID = cast ('00000000-0000-0001-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 10, UID = cast ('00000000-0000-0100-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 11, UID = cast ('00000000-0001-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 12, UID = cast ('00000000-0100-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 13, UID = cast ('00000001-0000-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 14, UID = cast ('00000100-0000-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 15, UID = cast ('00010000-0000-0000-0000-000000000000' as uniqueidentifier)
    Union   Select ID = 16, UID = cast ('01000000-0000-0000-0000-000000000000' as uniqueidentifier)
)
Select * From UIDs Order By UID, ID

事例来自Ferrari的博客How are GUIDs sorted by SQL
Server?

询问结果:

ID UID
16 01000000-0000-0000-0000-000000000000
15 00010000-0000-0000-0000-000000000000
14 00000100-0000-0000-0000-000000000000
13 00000001-0000-0000-0000-000000000000
12 00000000-0100-0000-0000-000000000000
11 00000000-0001-0000-0000-000000000000
10 00000000-0000-0100-0000-000000000000
9 00000000-0000-0001-0000-000000000000
8 00000000-0000-0000-0010-000000000000
7 00000000-0000-0000-0100-000000000000
6 00000000-0000-0000-0000-000000000001
5 00000000-0000-0000-0000-000000000100
4 00000000-0000-0000-0000-000000010000
3 00000000-0000-0000-0000-000001000000
2 00000000-0000-0000-0000-000100000000
1 00000000-0000-0000-0000-010000000000

透过者可以得于是之类结果:

  1. 预先以每1-8从左到右举办排序;
  2. 进而以第9-10各种从右到左进行排序;
  3. 最后准后11-16各从右到左举办排序;

经分析,大家而落如下权重列表:

序号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
权重 16 15 14 13 12 11 10 9 7 8 1 2 3 4 5 6
Value 11 11 11 11 22 22 33 33 44 44 44 44 44 44 44 44

在Microsoft官方文档中,有同篇文档关于GUID与uniqueidentifier的价值相比较:
Comparing GUID and uniqueidentifier
Values

不等之数据库处理GUID的法子为是殊之。在SQL
Server存在内置GUID类型,没有原生GUID协理的数据库通过模拟来法来促成之。在Oracle保存也raw
bytes类型,具体项目为raw(16);在MySql中数见不鲜将GUID储存也char(36)的字符串模式。

关于Oracle、MySql数据库的排序规则与.Net中排序规则,不过篇章的范围,那里不再做实际的示范,然则我于github上提供了示范SQL语句:https://gist.github.com/tangdf/f0aed064ba10bfa0050e4344b9236889,您得协调开展测试。大家于这里只让闹最终之定论:

小结:

  1. .Net中GUID的排序规则是从左到右依次展开排序,与数字排序规则一样;
  2. Sql
    Server数据库提供对GUID类型的辅助,在数据库被称之为UniqueIdentifier品种,可是排序规则相比复杂:

    • 预先照各1-8从左到右举办排序;
    • 紧接着以第9-10位从右到左举办排序;
    • 末未来11-16各样从右到左举办排序;
  3. Oracle数据库未供针对性GUID类型的支撑,使用的凡raw
    bytes类型保存数据raw(16),具体项目为,排序规则及GUID在.Net中规则平等;
  4. MySql数据未提供对GUID类型的协理,使用的是字符串的种保存数据,使用是的char(36)类别,由于应用的是字符串类型,排序规则及GUID在.Net中的规则平等。

有序GUID

以不变应万变GUID是爆发平整之生成GUID,保存在之后转的GUID类型总是比前的要非凡。但是当达成同一节被,已经干了各个数据库对GUID帮助不等同,而且排序的平整为非同等,所以大家用为各样一个数据库提供不均等的有序GUID生成规则。

UuidCreateSequential函数

咱俩还了然SQL
Server数据库有一个NewSequentialId()函数,用于成立有序GUID。在创立表时,能够将它们装成GUID类型字段的默认值,在插入新增多少平常自动创立主键的价(该函数只可以开吧字段的默认值,不可能一向当SQL中调用)。

示例:

Create Table TestTable
       (
         ID UniqueIdentifier Not Null Default ( NewSequentialId() ) ,
         Number Int
       );

NewSequentialId()函数只好当数据库使用,不过当 Microsoft 的 MSDN
文档中出征,NEWSEQUENTIALID 是针对性 Windows UuidCreateSequential
函数的包装
https://msdn.microsoft.com/zh-cn/library/ms189786(v=sql.120).aspx。这样大家得在C#透过非托管方法调用:

   [System.Runtime.InteropServices.DllImport("rpcrt4.dll", SetLastError = true)]
   private static extern int UuidCreateSequential(out Guid guid);

   public static Guid NewSequentialGuid()
   {
       const int RPC_S_OK = 0;

       int result = UuidCreateSequential(out var guid);
       if (result != RPC_S_OK) {
           throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error());
       }

       return guid;
   }

莫之措施呢存三单问题:

  1. 本条方法涉及到安全问题,UuidCreateSequential函数倚重之计硬件,该法的晚12位其实是网卡的MAC地址。这是我电脑生成的一律组来序GUID。

    {A2A93393-C8DC-11E7-B133-2C56DC497A97}
    {A2A93394-C8DC-11E7-B133-2C56DC497A97}
    {A2A93395-C8DC-11E7-B133-2C56DC497A97}
    {A2A93396-C8DC-11E7-B133-2C56DC497A97}
    {A2A93397-C8DC-11E7-B133-2C56DC497A97}
    {A2A93398-C8DC-11E7-B133-2C56DC497A97}
    {A2A93399-C8DC-11E7-B133-2C56DC497A97}
    {A2A9339A-C8DC-11E7-B133-2C56DC497A97}
    {A2A9339B-C8DC-11E7-B133-2C56DC497A97}
    {A2A9339C-C8DC-11E7-B133-2C56DC497A97}

    即刻是自家电脑的网卡的MAC地址:
    SQL Server 1

  2. 由于UuidCreateSequential函数生成的平稳GUID中包括MAC地址,所以假设当服务器集群环境受到,肯定有同样玉服务器A上其它有序GUID总比其他一样华服务器B生成要再一次小,服务器A暴发的数目插入到数据库时,由于聚集索引的题目,总是会倒服务器B已经持久化到数据库中之多少。集群的服务器越多,爆发的IO问题重新要紧。在服务器群集环境遭遇,需要活动实现稳步GUID。

  3. UuidCreateSequential函数生成的GUID规则及SQL
    Server中排序的条条框框是不平等
    ,这样仍会造成惨重的IO问题,所以需要以GUID重新排序后再行持久化到数据库。例如地点列有转变的GUID列表,依次生成的数据可观望,是第4员字节在起增长,在及时同另外一个数据库的排序规则都无一致;关于该函数生成的平整,可以呈现此链接:https://stackoverflow.com/questions/5585307/sequential-guids

脚的艺术是以扭转的GUID调整成可Sql
Server使用的雷打不动GUID(针对其他数据库扶助,您可以以排序规则自行修改):

[System.Runtime.InteropServices.DllImport("rpcrt4.dll", SetLastError = true)]
static extern int UuidCreateSequential(byte[] buffer);

static Guid NewSequentialGuid() {

    byte[] raw = new byte[16];
    if (UuidCreateSequential(raw) != 0)
        throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error());

    byte[] fix = new byte[16];

    // reverse 0..3
    fix[0x0] = raw[0x3];
    fix[0x1] = raw[0x2];
    fix[0x2] = raw[0x1];
    fix[0x3] = raw[0x0];

    // reverse 4 & 5
    fix[0x4] = raw[0x5];
    fix[0x5] = raw[0x4];

    // reverse 6 & 7
    fix[0x6] = raw[0x7];
    fix[0x7] = raw[0x6];

    // all other are unchanged
    fix[0x8] = raw[0x8];
    fix[0x9] = raw[0x9];
    fix[0xA] = raw[0xA];
    fix[0xB] = raw[0xB];
    fix[0xC] = raw[0xC];
    fix[0xD] = raw[0xD];
    fix[0xE] = raw[0xE];
    fix[0xF] = raw[0xF];

    return new Guid(fix);
}

小结:
UuidCreateSequential函数存在隐私之题目,不称集群环境,并且用重新排序后再付至数据库;

COMB解决方案

COMB 类型的GUID 是由Jimmy Nilsson在他的“The Cost of GUIDs as Primary
Keys
”一文被计划出来的。
核心计划思路是这般的:既然GUID数据变化是自由的致索引效用低下,影响了系的性,那么会无法通过结合的方,保留GUID的前10独字节,用后6个字节表示GUID生成的岁月(Date提姆e),这样我们用日音信与GUID组合起来,在保留GUID的唯一性的以多了有序性,以之来增长索引效能(这是本着Sql
Server数据库来统筹的)。

每当NHibernate框架中早已实现该意义,可以以github上寓目实现模式:https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Id/GuidCombGenerator.cs#L25-L72

每当EF以及EF Core也一律实现了接近的缓解方案,EF
Core的兑现模式:https://github.com/aspnet/EntityFrameworkCore/blob/f7f6d6e23c8e47e44a61983827d9e41f2afe5cc7/src/EFCore/ValueGeneration/SequentialGuidValueGenerator.cs#L25-L44

每当此介绍一下使用的法子,由EF Core框架自动生成有序GUID的办法:

    public class SampleDbContext : DbContext
    {
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<GuidEntity>(b =>
            {
                b.Property(e => e.Id).HasValueGenerator<SequentialGuidValueGenerator>();
            });
        }
    }

只是要留心,这半独ORM的解决方案就对Sql
Server数据库,因为只有包了最终几乎位字节是据梯次来扭转的。

SequentialGuid框架

SequentialGuid框架为是我如若推荐给你,因为它们提供了广大数据库生成有序Guid的化解方案。

关于该框架的计划思路和针对性各类数据库的属性测试,见链接:https://www.codeproject.com/Articles/388157/GUIDs-as-fast-primary-keys-under-multiple-database

下方法,提议你参考ABP框架,在ABP中行使SequentialGuid框架来转有序GUID,关键代码链接:https://github.com/aspnetboilerplate/aspnetboilerplate/blob/b36855f0c238c3592203f058c641862844a0614e/src/Abp/SequentialGuidGenerator.cs#L36-L51

总结

俺们来缓解一下:

  1. 于数据库中最为好永不拔取随机的GUID,它会潜移默化性;
  2. 在SQL Server中提供了NewSequentialId函数来扭转有序GUID;
  3. 梯次数据库对GUID帮助的不相同,而且排序的条条框框也无一样;
  4. UuidCreateSequential函数存在隐私之题材,不切合集群环境,并且要再行排序后再度提交至数据库;
  5. 各ORM框架提供了平稳GUID的匡助,不过事实上就是针对性Sql
    Server数据库设计的;
  6. 推荐您运SequentialGuid框架,它解决了绝大多数据库以及集群环境之题材。

相关文章