表格我做视频网站,网站开发checklist,免费服务器建站,大连大久建设集团有限公司源宝导读#xff1a;数据库设计时#xff0c;经常会使用GUID作为表的主键#xff0c;但由于GUID的随机性会导致数据库在读写数据时效率严重下降#xff0c;影响应用程序整体性能。本文将深入探讨如何通过使用有序GUID提升数据读写的性能。一、背景常见的数据库设计是使用连… 源宝导读数据库设计时经常会使用GUID作为表的主键但由于GUID的随机性会导致数据库在读写数据时效率严重下降影响应用程序整体性能。本文将深入探讨如何通过使用有序GUID提升数据读写的性能。一、背景 常见的数据库设计是使用连续的整数为做主键当新的数据插入到数据库时由数据库自动生成但这种设计不一定适合所有场景。 随着越来越多的应用程序使用Nhibernate、Entity Framework Core等ORM对象关系映射框架应用被设计成为工作单元Unit Of Work模式需要在数据持久化之前生成主键解决主实体与子系统的依赖关系为了保证在多线程并发以及站点集群环境中主键的唯一性最简单最常见的方式是将主键设计成为GUID类型。 工作单元是数据库应用程序经常使用的一种设计模式简单一点来说就是对多个数据库操作进行打包记录对象上的所有变化并在最后提交时一次性将所有变化通过系统事务写入数据库。目的是为了减少数据库调用次数以及避免数据库长事务。关于工作单元的知识可以在各类博客网站中都有说明在这里就不做详细的介绍了。 GUID全球唯一标识符也称为UUID是一种由算法生成的二进制长度为128位的数字标识符。在理想情况下任何计算机之间都不会生成两个相同的GUID。GUID 的总数达到了2^1283.4×10^38个所以随机生成两个相同GUID的可能性非常小但并不为0。GUID一词有时也专指微软对UUID标准的实现。 RFC 41222描述了创建标准GUID如今大多数GUID生成算法通常是一个很长的随机数再结合一些像网络MAC地址这种随机的本地组件信息。 GUID的优点允许开发人员随时创建新值而无需从数据库服务器检查值的唯一性这似乎是一个完美的解决方案。 很多数据库在创建主键时为了充分发挥数据库的性能会自动在该列上创建聚集索引。我们先来说一说什么是聚集索引。集索引确定表中数据的物理顺序类似于电话簿按姓氏排列数据。由于聚集索引规定数据在表中的物理存储顺序因此一个表也只能包含一个聚集索引。它能够快速查找到数据但是如果插入数据库的主键不在列表的末尾向表中添加新行时就非常缓慢。例如看下面这个例子在表中已经存在三行数据例子来自Jeremy Todd的博客《GUIDs as fast primary keys under multiple databases》 此时非常简单数据行按对应ID列的顺序储存。如果我们新添加一行ID为8的数据不会产生任何问题新行会追加的末尾。 但如果我们想插入一行的ID为5的数据。 ID为78的数据行必须向下移动。虽然在这算什么事儿但当您的数据量达到数百万行的级别之后这就是个问题了。如果您还想要每秒处理上百次这种请求那可真是难上加难了。 这就是GUID主键引发的问题它是随机产生的所以在数据插入时随时都会涉及到数据的移动导致插入会很缓慢还会涉及大量不必要的磁盘活动。根据数据库的存储的相关知识会带如下两点问题空间的浪费以及由此带来的读写效率的下降更主要的存储的碎片化以及由此带来的读写效率严重下降。 GUID最关键的问题就是它是随机的。我们需要设计一种有规则的GUID生成方式在之后生成的GUID类型总是比之前的要大保证插入数据库的主键是在表数据的末尾追加的这种我们称之为有序GUID。二、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个字节。我们分别的对各字节编上序号GUID在.Net中的排序规则 在.Net中GUID默认的排序规则是按左到右的看下面这个示例。 输出结果 通过上面的输出结果我们可以得到排序的权重如下 这与数字排序规则一致从右到左进行依次进行排序数字越小权重越高排序的优先级越高。GUID在各个数据库中的排序规则 在SQL Server数据库中我们有一种非常简单的方式来比较两个GUID类型的大小值其实在SQL Server数据库中称为UniqueIdentifier类型 上面的例子来自Ferrari的博客《How are GUIDs sorted by SQL Server?》。 查询结果通过上面可以得到如下结果先按每1-8从左到右进行排序接着按第9-10位从右到左进行排序最后按后11-16位从右到左进行排序通过分析我们可得到如下权重列表 在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中排序规则不过篇章的限制这里不再做具体的演示您可以自己进行测试。我们在这里只给出最终的结论.Net中GUID的排序规则是从左到右依次进行排序与数字排序规则一致Sql Server数据库提供对GUID类型的支持在数据库中称为UniqueIdentifier类型但是排序规则比较复杂先按每1-8从左到右进行排序接着按第9-10位从右到左进行排序最后按后11-16位从右到左进行排序Oracle数据库未提供对GUID类型的支持使用的是raw bytes类型保存数据真实类型为raw(16)排序规则是按Oracle二进制进行排序的MySql数据库未提供对GUID类型的支持使用的是字符串的类型保存数据使用是的char(36)类型由于使用的是字符串类型排序规则与GUID在.Net中的规则一致。三、有序GUID 有序GUID是有规则的生成GUID保证在之后生成的GUID的值总是比之前的要大。不过在上一节中已经提到过各个数据库对GUID支持不一样而且排序的规则也不一样所以我们需要为每一个数据库提供不一致的有序GUID生成规则。UuidCreateSequential函数 我们都知道SQL Server数据库有一个NewSequentialId()函数用于创建有序GUID。在创建表时可以将它设置成为GUID类型字段的默认值在插入新增数据时自动创建主键的值该函数只能做为字段的默认值不能直接在SQL中调用。示例如下 NewSequentialId()函数只能在数据库使用不过在 Microsoft 的 MSDN 文档中有说明NEWSEQUENTIALID 是对 Windows UuidCreateSequential 函数的包装https://msdn.microsoft.com/zh-cn/library/ms189786(vsql.120).aspx。这样我们可以在C#通过非托管方法调用 但是上面的方法也存在三个问题1、这个方法涉及到安全问题UuidCreateSequential函数依赖的计算硬件该方法的后12位其实是网卡的MAC地址。这是我电脑生成的一组有序GUID。 这是我本地电脑的网卡的MAC地址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针对其它数据库支持您可以按排序规则自行修改小结 UuidCreateSequential函数存在隐私的问题不适合集群环境并且需要重新排序后再提交到数据库COMB解决方案 COMB 类型的GUID 是由Jimmy Nilsson在他的“The Cost of GUIDs as Primary Keys”一文中设计出来的。 基本设计思路是这样的既然GUID数据生成是随机的会造成索引效率低下影响了系统的性能那么能不能通过组合的方式保留GUID的前10个字节用后6个字节表示GUID生成的时间DateTime这样我们将时间信息与GUID组合起来在保留GUID的唯一性的同时增加了有序性以此来提高索引效率这是针对Sql Server数据库来设计的。 在NHibernate框架中已经实现该功能可以在github上看到实现方式https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Id/ GuidCombGenerator.cs#L45-L69。 在EF以及EF Core也同样实现了类似的解决方案EF Core的实现方式https://github.com/aspnet/EntityFrameworkCore/blob/f7f6d6e23c8e47e44a61983827d9e41f2afe5cc7/src/EFCore/ValueGeneration/SequentialGuidValueGenerator.cs#L25-L44。 在这里介绍一下使用的方式由EF Core框架自动生成有序GUID的方式 但是请注意这两个ORM的解决方案只针对Sql Server数据库因为只保证了最后几位字节是按顺序来生成的。SequentialGuid框架 SequentialGuid框架也是我要推荐给您因为它提供了常见数据库生成有序Guid的解决方案。 基本原理与COMB方案一样使用时间来保证有序GUID的顺序使用System.Security.Cryptography. RNGCryptoServiceProvider保证生成的数据的唯一性关于该框架的设计思路以及针对各个数据库的性能测试见链接https://www.codeproject.com/Articles/388157/GUIDs-as-fast-primary-keys-undermultiple-database。 使用方式建议您参考ABP框架在ABP中使用SequentialGuid框架来生成有序GUID关键代码链接https://github.com/aspnetboilerplate/aspnetboilerplate/ blob/b36855f0c238c3592203f058c641862844a0614e/src/Abp/SequentialGuidGenerator.cs#L36-L51。四、总结 我们来总结一下在数据库中最好不要使用随机的GUID它会影响性能在SQL Server中提供了NewSequentialId函数来生成有序GUID各个数据库对GUID支持的不一样而且排序的规则也不一样UuidCreateSequential函数存在隐私的问题不适合集群环境并且需要重新排序后再提交到数据库各ORM框架提供了有序GUID的支持但是其实只是针对Sql Server数据库设计的推荐您使用SequentialGuid框架它解决了多数据库以及集群环境的问题。------ END ------作者简介唐同学 架构师目前负责ERP运行平台整体架构设计和开发。也许您还想看ERP缓存实践经验分享大数据列表页面前端性能优化方案与实践.Net最小工作线程对应用程序性能的影响成本计算引擎动态规则解析技术详解