概述

.NET生成的GUID唯一性很好,用之方便,但是,缺少像雪花算法那样的有序性。虽然分布式系统中做不到绝对的有序,但是,相对的有序对于目前数据库而言,索引效率等方面的提升还是有明显效果的(当然,我认为,这是数据库的问题,而非编程的问题,数据库应该处理好任何类型数据作为主键索引时的性能,除非在SQL标准中明确写不支持哪些数据类型)。暂时数据库无法解决这些问题的时候,除了使用雪花算法,是否能够改造GUID,利用微软已经相当成熟的GUID的同性能与效率的同时,加上序列的特性呢。本文就是做此尝试。

我们需要用到时间值(戳?还是不蹭UNIX的概念吧)

1 毫秒=1000000 纳秒


var dt = DateTime.Now;// 当前时间
Console.WriteLine(dt.Ticks);// 638322150575422659,这是.net自带的运算,其它语言可以使用下面的方式生成。
Console.WriteLine((dt - new DateTime(1, 1, 1)).TotalMilliseconds*10000); // 638322150575422700
// 通过Ticks,可以取得100ns,即万分之一毫秒的精度。

到3023年(1千年以后),Ticks的值也不会进位,其值为953650368000000000。

了解一下GUID

GUID 的格式为“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”。

其含义为:【时间值低位 32bit】-【时间值中位 16bit】-【版本号 4bit】【时间值高位 4bit】【时间值高位 8bit】-【变体值 2bit】【时间序列高位 6bit】-【节点值 48bit】

位数为:8hex-4hex-4hex-4hex-12hex。

var uuidN = Guid.NewGuid().ToString("N"); // e0a953c3ee6040eaa9fae2b667060e09

时间值+GUID

时间值本身是一个long类型的数字,其大小为Int64,即8byte。

guid本身就是一个byte[],其长度为16位。

所以,我们生成一个byte[],前8位放时间值,后面放GUID,在比较大小的时候,前端的位置优先级更高,所以,后面的GUID的无序特性会被覆盖。

8 位 Byte

8 位 Byte

8 位 Byte

时间值

GUID值

加在一起,一共24个字节。

Base64字符串化

因为数据库、前端、CSV等环境下,无法描述所有的Byte(原因是部分AscII是非可见字符),故而,需要将其进行类似Base64的转换。

转换后,我们会得到一个长度大概24/3*4=32位长度的字符串。这个字符串的字节数至少是32,但是,其具体更好的可读性。

但是,Base64有一个缺陷,我们来看一下它的码表:

可以看到,【大写字母】<【小写字母】<【数字】<【+】<【/】。

但是,我们的程序进行比较时,并非如此,而是遵循了AscII码表的次序。我们来看一下AscII码表的次序:

在AscII码表中,【/】<【数字】<【=】<【大写字母】<【小写字母】。

与上面的base64对比可以发现,如果我们将两串二进制用base64表示,则他们将无法使用base64字面的字符进行大小比较。所以,我们需要对base64进行一次转换,转换的结果要与ascii对应,起到和ascii大小次序一次的效果。

BaseSortValue-BSV

为了解决这个问题,我们需要将base64的二进制拿出来,然后, 给予他们有次序的新的码表即可。

但是,我们要做更长远的考虑,我们的BSV大概率会被用作主键,会用来查询,用出现在URL中,所以,我们应该避开URL的字义字符。URL的转义表如下:

除了大小写字母、数字以外,我们还需要3个字符。除去URL转义字符,ASCII中可用的可视字符只剩下 !"$'()*,-.;<>[\]^_{|}~。其中"'出现在代码中容易影响代码本身的转义,故而不可。_符号在查询时,经常因为疏忽看不见。所以,最好的应该是!$-。因为这三者的中英文区别较大,具有较高的可识别度。同时,!小于数字及字母,作为补位可以不影响大小。最终形成的码表如下:

生成的结果如下:

0Bj4hRXIFkDoc$DXPivPF7nPBmO-smcF
0Bj4hU4f674ny-f0keZnG6VpDZm1b75r
0Bj4hU4h3WXnCsIKnnrrG7LHBzpH4yMF
0Bj4hU4h4FhniG-wH57BF6pSCVD$5sGp
0Bj4hU4h4Za$B5tkH2sIEtjA39M-nGuQ
0Bj4hU4h4qL0M-4nKRLZFcrU8qF$yezv
0Bj4hU4h548bDgf6ypAiFvI-HQSzZFeH
0Bj4hU4h5I47IsKrnkfdF7bfvOjMXWXm
0Bj4hU4h5V3P7fTSP0lBEcbZbF5h2CXV
0Bj4hU4h5iCiT-m$R7PfEeko7oaFcIPO
0Bj4hU4h5vDF6VNYTDSSFsHi1FUQt93p

实现 - .Net


public static class SeqGuid
{
/// <summary>
/// 生成BSV的GUID。
/// </summary>
/// <returns></returns>
public static string NewGuid()
{ var gid = Guid.NewGuid().ToByteArray();// 获取唯一的guid,对应uuid的版本应该是v4。此处直接获取其byte数组。
var dtvalue = DateTime.Now.Ticks;//获取当前时间到1年1月1日的总ticks数,ticks单位是100ns,即万分之一毫秒。
var dtbytes = BitConverter.GetBytes(dtvalue);// 将ticks时间戳转换为字节数组,默认是小端。
var bytes = new Byte[gid.Length + dtbytes.Length];// 实例化新的数字,用以存放时间值和GUID值。 // 因为BitConverter.GetBytes获得的Byte[]是小端,不符合排序要求,所以,要逆序写入bytes数组中,形成大端的方式。
// 将时间值放入bytes数组中。
for (long i = 0; i < dtbytes.Length; i++)
{
var cvalue = dtbytes[dtbytes.Length - i - 1];
bytes[i] = cvalue;
} // 将guid的值,放入bytes数组中。
gid.CopyTo(bytes, dtbytes.Length);
// 将值转换为base64,主要原因是,前端、数据库比较容易处理字符串类型的数据。
var b64 = Convert.ToBase64String(bytes);
// 将无序的base64转换为有序的伪base64格式。 var ss = b64.ToArray();
for (var i = 0; i < ss.Length; i++)
{
ss[i] = dic[ss[i]];
}
return new string(ss);
}
/// <summary>
/// 仿base64的有序字典,其与base64相似,使用有限的字符,表示6bit的二进制,不足的地方补=。但是,与base64的区别是,字符串是按从小到大的次序表示000000到111111的数值的。
/// </summary>
public static readonly Dictionary<char, char> dic = new Dictionary<char, char>()
{
{'A','$'},{'B','-'},{'C','0'},{'D','1'},{'E','2'},{'F','3'},{'G','4'},{'H','5'},{'I','6'},{'J','7'},{'K','8'},
{'L','9'},{'M','A'},{'N','B'},{'O','C'},{'P','D'},{'Q','E'},{'R','F'},{'S','G'},{'T','H'},{'U','I'},{'V','J'},
{'W','K'},{'X','L'},{'Y','M'},{'Z','N'},{'a','O'},{'b','P'},{'c','Q'},{'d','R'},{'e','S'},{'f','T'},{'g','U'},
{'h','V'},{'i','W'},{'j','X'},{'k','Y'},{'l','Z'},{'m','a'},{'n','b'},{'o','c'},{'p','d'},{'q','e'},{'r','f'},
{'s','g'},{'t','h'},{'u','i'},{'v','j'},{'w','k'},{'x','l'},{'y','m'},{'z','n'},{'0','o'},{'1','p'},{'2','q'},
{'3','r'},{'4','s'},{'5','t'},{'6','u'},{'7','v'},{'8','w'},{'9','x'},{'+','y'},{'/','z'},{'=','!'}
};
}
    internal class Program
{
static void Main(string[] args)
{
var preone = SeqGuid.NewGuid();
for(int i = 0; i < 9999999; i++)
{
var newone = SeqGuid.NewGuid();
if (String.CompareOrdinal(newone, preone)<0)//必须使用CompareOrdinal,因为Compare和CompareTo等都受本地的CultureInfo影响,可能会忽略大小写。
{
Console.WriteLine($"error ,{newone} < {preone}"); }
preone = newone; }
Console.WriteLine("done...");
Console.ReadLine();
}
}

运行结果:

执行1000万次,没有大小次序错误。单线程 的情况下,每秒生成143万个。

一种对数据库友好的GUID的变种使用方法的更多相关文章

  1. 数据库 --> 8种NoSQL数据库对比

    8 种 NoSQL 数据库对比 NoSQL是一项全新的数据库革命性运动,NoSQL的拥护者们提倡运用非关系型的数据存储.现今的计算机体系结构在数据存储方面要求具备庞大的水平扩展性,而NoSQL致力于改 ...

  2. 针对多类型数据库,集群数据库的有序GUID

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

  3. Microsoft SQLServer有四种系统数据库

    Microsoft SQLServer有四种系统数据库: 1.master数据库 master数据库记录SQLServer系统的所有系统级别信息.它记录所有的登录帐户和系统配置设置.master数据库 ...

  4. DBA 需要知道N种对数据库性能的监控SQL语句

    --DBA 需要知道N种对数据库性能的监控SQL语句 -- IO问题的SQL内部分析 下面的DMV查询可以来检查当前所有的等待累积值. Select wait_type, waiting_tasks_ ...

  5. 生成GUID唯一值的方法汇总(dotnet/javascript/sqlserver)

    一.在 .NET 中生成1.直接用.NET Framework 提供的 Guid() 函数,此种方法使用非常广泛.GUID(全局统一标识符)是指在一台机器上生成的数字,它保证对在同一时空中的任何两台计 ...

  6. 26种提高ASP.NET网站访问性能的优化方法 .

    1. 数据库访问性能优化 数据库的连接和关闭 访问数据库资源需要创建连接.打开连接和关闭连接几个操作.这些过程需要多次与数据库交换信息以通过身份验证,比较耗费服务器资源. ASP.NET中提供了连接池 ...

  7. 主流数据库字段类型转.Net类型的方法

    最近在阅读一些开源的代码,发现其中有些方法总结的很全面,至少在我做同样的事情时候,需要抓破脑袋想活着google,现在看到了这个关于主流数据库字段类型转.Net类型的方法,故收藏之,也顺便分享给那些能 ...

  8. 设置SQLServer数据库中某些表为只读的多种方法

    原文:设置SQLServer数据库中某些表为只读的多种方法 翻译自:http://www.mssqltips.com/sqlservertip/2711/different-ways-to-make- ...

  9. JS生成全局唯一标识符(GUID,UUID)的方法

    全局唯一标识符(GUID,Globally Unique Identifier)也称作 UUID(Universally Unique IDentifier) . GUID是一种由算法生成的二进制长度 ...

  10. 一种读取Exchange的用户未读邮件数方法!

    已好几个月没写博客了,由于之前忙于开发基于Sharepoint上的移动OA(AgilePoint)和采用混合移动开发技术开发一个安卓版的企业通讯录APP(数据与lync一致),并于1月初正式上线.马年 ...

随机推荐

  1. CMU15445 (Fall 2020) 数据库系统 Project#3 - Query Execution 详解

    前言 经过前两个实验的铺垫,终于到了执行 SQL 语句的时候了.这篇博客将会介绍 SQL 执行计划实验的实现过程,下面进入正题. 总体架构 一条 SQL 语句的处理流程可以归纳为: SQL 被 Par ...

  2. 【Netty】一个RPC实例

    Netty实现简易RPC调用 总体流程: 客户端发起rpc调用请求,封装好调用的接口名,函数名,返回类型,函数参数类型,函数参数值等属性,将消息发送给服务器. 服务器的handler解析rpc请求,调 ...

  3. 【技术积累】Vue.js中的基础概念与语法【一】

    写在前面 学习Vue之前最好有前端三驾马车的基础[HTML+CSS+JavaScript] 笔者接了一个从头开发的Vue项目,由于公司急着要,没有时间慢慢像在学校里学了,只能边学边做,现在项目雏形已经 ...

  4. 3 分钟为英语学习神器 Anki 部署一个专属同步服务器

    原文链接:https://icloudnative.io/posts/anki-sync-server/ Anki 介绍 Anki 是一个辅助记忆软件,其本质是一个卡片排序工具--即依据使用者对卡片上 ...

  5. 统信UOS系统开发笔记(七):在统信UOS系统上使用linuxdeployqt发布qt程序

    前言   在ubuntu上发布qt程序相对还好,使用脚本,但是在统信UOS麒麟上发布的时候,因为银河麒麟等不同版本,使用脚本就不太兼容,同时为了实现直接点击应用可以启动应用的效果,使用linuxdep ...

  6. 如何在.net6webapi中记录每次接口请求的日志

    为什么在软件设计中一定要有日志系统? 在软件设计中日志模块是必不可少的一部分,可以帮助开发人员更好的了解程序的运行情况,提高软件的可靠性,安全性和性能,日志通常能帮我们解决如下问题: 调试和故障排查: ...

  7. Linux 图形栈从入门到放弃 --- Linux 图形相关概念简介

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 环境说明   无 前言   在日常生活中,像我们常用的ubunt ...

  8. python学习--解析网页

    # -*- coding: utf-8 -*-"""Created on Thu Oct 17 14:04:21 2019 @author: DELL"&quo ...

  9. 🎉Avalonia 11.0.0 正式版发布

    Avalonia 11.0.0 正式版发布! AvaloniaUI 发布11.0.0正式版 终于avalonia发布了正式版. 更新内容 A11y(辅助功能) 这个版本的Avalonia在使应用程序更 ...

  10. Python爬虫突破验证码技巧 - 2Captcha

    在互联网世界中,验证码作为一种防止机器人访问的工具,是爬虫最常遇到的阻碍.验证码的类型众多,从简单的数字.字母验证码,到复杂的图像识别验证码,再到更为高级的交互式验证码,每一种都有其独特的识别方法和应 ...