如何使用有序GUID提升数据库读写性能

源宝导读:数据库设计时,经常会使用GUID作为表的主键,但由于GUID的随机性会导致数据库在读写数据时效率严重下降,影响应用程序整体性能。本文将深入探讨如何通过使用有序GUID提升数据读写的性能。

一、背景

    常见的数据库设计是使用连续的整数为做主键,当新的数据插入到数据库时,由数据库自动生成,但这种设计不一定适合所有场景。

  随着越来越多的应用程序使用Nhibernate、Entity Framework Core等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的优点允许开发人员随时创建新值,而无需从数据库服务器检查值的唯一性,这似乎是一个完美的解决方案。

    很多数据库在创建主键时,为了充分发挥数据库的性能,会自动在该列上创建聚集索引。我们先来说一说什么是聚集索引。集索引确定表中数据的物理顺序,类似于电话簿,按姓氏排列数据。由于聚集索引规定数据在表中的物理存储顺序,因此一个表也只能包含一个聚集索引。它能够快速查找到数据,但是如果插入数据库的主键不在列表的末尾,向表中添加新行时就非常缓慢。例如,看下面这个例子,在表中已经存在三行数据(例子来自Jeremy Todd的博客《GUIDs as fast primary keys under multiple databases》):

    此时非常简单:数据行按对应ID列的顺序储存。如果我们新添加一行ID为8的数据,不会产生任何问题,新行会追加的末尾。

    但如果我们想插入一行的ID为5的数据。

    ID为7,8的数据行必须向下移动。虽然在这算什么事儿,但当您的数据量达到数百万行的级别之后,这就是个问题了。如果您还想要每秒处理上百次这种请求,那可真是难上加难了。

    这就是GUID主键引发的问题:它是随机产生的,所以在数据插入时,随时都会涉及到数据的移动,导致插入会很缓慢,还会涉及大量不必要的磁盘活动。根据数据库的存储的相关知识,会带如下两点问题:

  1. 空间的浪费以及由此带来的读写效率的下降;

  2. 更主要的,存储的碎片化以及由此带来的读写效率严重下降。

    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(v=sql.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最小工作线程对应用程序性能的影响

成本计算引擎动态规则解析技术详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311365.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《商业洞察力30讲》学习笔记(上)

【洞察力】| 作者 / Edison Zhou这是恰童鞋骚年的第197篇原创文章学习洞察力,也是新时代IT人员的一门进阶必修课...1学习背景2019年下半年至今,在领导的推荐下学习了刘润老师的《商业洞察力30讲》,刷新了我对于事物的认知,也为我提…

[蓝桥杯][历届试题]九宫重排-双向bfs和map标记

题目描述 如下面第一个图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动,可以形成第二个图所示的局面。 我们把第一个图的局面记为:12345678. 把第二个图的局面记…

3月数据库排行:前10整体下行,出新技术了?

DB-Engines 数据库流行度排行榜 3 月更新已发布,排名前二十如下:排名方面没有任何变动(仅针对前十),相信很长一段时间内也都不会变动,毕竟巨头的位置不是一时半刻就能动摇的。不过这个月的排行榜还是有值得…

每日一题——LeetCode160.相交链表

个人主页:白日依山璟 专栏:Java|数据结构与算法|每日一题 文章目录 1. 题目描述示例1:示例2:提示: 2. 思路3. 代码 1. 题目描述 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的…

.NET Core的HttpClient连接池管理

译者荐语:使用.NET Core的HttpClient连接池管理有哪些注意事项?本文给出了非常中肯的建议。原文来自互联网,由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权,请联系小编,小编将在24小时内删除。本文来源[1]史蒂…

[蓝桥杯][2014年第五届真题]兰顿蚂蚁-模拟

题目描述 兰顿蚂蚁,是于1986年,由克里斯兰顿提出来的,属于细胞自动机的一种。 平面上的正方形格子被填上黑色或白色。在其中一格正方形内有一只“蚂蚁”。 蚂蚁的头部朝向为:上下左右其中一方。 蚂蚁的移动规则十分简单&#…

.NET Core开发实战(第17课:为选项数据添加验证:避免错误配置的应用接收用户流量)--学习笔记...

17 | 为选项数据添加验证:避免错误配置的应用接收用户流量三种验证方法1、直接注册验证函数2、实现 IValidateOptions3、使用 Microsoft.Extensions.Options.DataAnnotations延用上一节代码需要添加验证的时候不能用 Configure,而用 AddOptions 方法//se…

分支程序与循环程序设计-汇编实验二

DATA SEGMENT ;定义数据段BUF DB -1, 20, 3, 30, -5, 15, 100, -54, 0, 4, 78, 99DB -12, 32, 3, 23, -7, 24, 60,-51 ;定义比较大小的数据DATA ENDS ;数据段结束ESEG SEGMENT ;定义附加段RES1 DB 0;定义结果存放区RES2 DB 0RES3 DB 0 ESEG ENDS ;附加段结束CODE SEGMENT ;定义…

[蓝桥杯][2013年第四届真题]剪格子-dfs

题目描述 历届试题 剪格子 时间限制:1.0s 内存限制:256.0MB 问题描述 如下图所示,3 x 3 的格子中填写了一些整数。 我们沿着图中的星号线剪开,得到两个部分,每个部分的数字和都是60。 本题的要求就是请你编程判定&a…

使用有序GUID:提升其在各数据库中作为主键时的性能

原文出处:https://www.codeproject.com/articles/388157/guids-as-fast-primary-keys-under-multiple-database ,避免今后忘记了再去阅读原英文。【】是感觉理解有问题的地方正确的使用有序GUID在大部分数据库中可以获得和 整型作为主键 时相媲美的性能。…

串操作指令及其应用程序的设计与调试运行——汇编实验四 用8086汇编完成下题 编制一程序,从键盘输入两个长度不同的字符串,设字符串长度小于25个字符。要求在屏幕上以右边对齐的形式显示出

文章目录实验目的和内容实验要求和步骤实现右对齐以下为源码:方法一方法二方法三测试一——字符串的输入输出测试二——改进测试三——改进测试四——改进实验目的和内容 实验要求和步骤 实现右对齐 以下为源码: 方法一 DATA SEGMENTNUM EQU 25BUF1 D…

selenium.common.exceptions.WebDriverException: Message: ‘chromedriver‘ executable needs to bein PATH

使用Selenium模拟浏览器访问淘宝首页,出现报警 from selenium import webdriver import timebrowser webdriver.Chrome() browser.get(https://www.taobao.com) time.sleep(2) print(browser.page_source)selenium.common.exceptions.WebDriverException: Message…

【朝夕技术专刊】Core3.1WebApi_Filter详解

欢迎大家阅读《朝夕Net社区技术专刊》第4期我们致力于.NetCore的推广和落地,为更好的帮助大家学习,方便分享干货,特创此刊!很高兴你能成为忠实读者,文末福利不要错过哦!01PARTCoreWebApi五大Filter1. Autho…

统计学习方法 pdf_机器学习基础教材-《统计学习与数据分析介绍》免费pdf分享...

本书介绍本入门级统计教科书主要讲解发展和培养统计思维所需的基本概念和工具。它提供了描述性,归纳性和探索性的统计方法,并指导读者完成定量数据分析的过程。在实验科学和跨学科研究中,数据分析已成为任何科学研究的组成部分。诸如判断数据…

selenium.common.exceptions.SessionNotCreatedException: Message: session not created全套解决方案

chromedriver版本不匹配 报错: selenium.common.exceptions.SessionNotCreatedException: Message: session not created selenium.common.exceptions.WebDriverException: Message: session not created: 原因: Chrome版本和ChromeDriver版本不一致…

.NET Core开发实战(第18课:日志框架:聊聊记日志的最佳姿势)--学习笔记(上)...

18 | 日志框架:聊聊记日志的最佳姿势源码链接:https://github.com/witskeeper/geektime/tree/master/samples/LoggingSimpleDemo日志框架必要的包:1、Microsoft.Extensions.Logging2、Microsoft.Extensions.Logging.Console3、Microsoft.Exte…

Istio 1.5 发布——拥抱变化,爱上单体

北京时间 2020 年 3 月 6 日凌晨,我们期待已久的 Istio 1.5 发布了,发布公告见 https://istio.io/news/releases/1.5.x/announcing-1.5/。由 ServiceMesher 社区组织翻译的 Istio 官方文档同时发布,见 https://istio.io/zh。Istio 1.5 是一个…

hbuilderx怎么添加断点_【高考语文题库】高考一直提分提不上去该怎么办?同一卷高考押题语文答案,助你再提30分...

大树从来不是在温室里长成的,而是在风霜雪雨的洗礼中参天的……古语云:“工欲善其事,必先利其器。”俗话说:“磨刀不误砍材工。”用到学习上就是学习必须讲究学习方法,有了适合自己的有效学习方法必定会事半功倍。一个…

业务模块化打造单体和分布式部署同步支持方案

我在2019年中国.NET开发者峰会上为大家分享了我们的微服务电商安全工程实践,那次会议分享的高清录播已经上传到我的腾讯课堂,大家可以通过底部的小程序打开直接观看(复习)。在大会上跟大家提到,我们当时只有4个人的创业…

Asp.Net Core EndPoint 终结点路由工作原理解读

Asp.Net Core EndPoint 终点路由工作原理解读一、背景在本打算写一篇关于Identityserver4 的文章时候,却发现自己对EndPoint -终结点路由还不是很了解,故暂时先放弃了IdentityServer4 的研究和编写;所以才产生了今天这篇关于EndPoint (终结点…