首页 > 数据库 >一种对数据库友好的GUID的变种使用方法

一种对数据库友好的GUID的变种使用方法

时间:2023-10-06 22:12:31浏览次数:33  
标签:dtbytes 变种 数据库 base64 bytes var Byte GUID

概述

.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万个。

标签:dtbytes,变种,数据库,base64,bytes,var,Byte,GUID
From: https://www.cnblogs.com/ensleep/p/17745166.html

相关文章

  • MySQL数据库
    1.事物的四大特性和隔离级别原子性:不可分割的操作单元,要么全部成功,要么回滚。一致性:如果执行事物之前数据库是一致的,那么执行后还是一致的。隔离性:事物操作之间彼此独立和透明,互不影响。持久性:事物一旦提交,其结果就是永久的。未提交读:允许脏读,其他事物只要修改了数据,即使未提交,......
  • SQLServer数据库三种恢复模式:简单恢复模式、完整恢复模式和大容量日志恢复模式介绍
    SQLServer数据库三种恢复模式:简单恢复模式、完整恢复模式和大容量日志恢复模式介绍  1.Simple简单恢复模式,Simple模式的旧称叫”Checkpointwithtruncatelog“,其实这个名字更形象,在Simple模式下,SQLServer会在每次checkpoint或backup之后自动截断log,也就是丢弃所有的in......
  • openGauss学习笔记-91 openGauss 数据库管理-内存优化表MOT管理-内存表特性-使用MOT-M
    openGauss学习笔记-91openGauss数据库管理-内存优化表MOT管理-内存表特性-使用MOT-MOT使用MOT外部支持工具为了支持MOT,修改了以下外部openGauss工具。请确保使用的工具是最新版本。下面将介绍与MOT相关的用法。有关这些工具及其使用方法的完整说明,请参阅《工具与命令参考》。91......
  • 数据库的隔离级别
    数据库的隔离级别是指在多个并发事务同时对数据库进行读写操作时,各个事务之间的隔离程度。常见的数据库隔离级别包括:读未提交(ReadUncommitted):最低的隔离级别,允许一个事务读取另一个事务尚未提交的数据。可能会出现脏读(DirtyRead)问题,即读取到未提交的数据。读已提交(ReadCommitted......
  • 缓存(Redis)与数据库(MySQL)一致性如何解决?
    【零】场景预设我们以12306购票系统为例,结合购票场景完成缓存与数据库双写一致性的相关问题解决【一】业务背景为了满足用户对一趟列车不同站点不同座位类型的余量查询需求,我们采取了一种优化方案。我们将这些余量信息存储在缓存中,以便用户可以快速查询。然而,在用户创建......
  • 数据库事务和隔离级别的解析
    什么是数据库中的事务,可以说事务就是一组原子性的SQL查询,独立的工作单元。我们的事务内的语句,要么全部执行成功,要么全部执行失败!事务要满足ACID特性,可以通过Commit提交一个事务,也可以使用Rollback进行回滚!下面我们就介绍一下事务的ACID特性。ACID特性原子性(actomicity)一个事......
  • 如何远程修复损坏的mysql数据库
    当mysql数据库出现错误需要修复时,可以用mysqlcheck这个命令修复点击开始-运行,输入c:\mysqlcheck -r -u用户名 -p数据库密码 -h服务器地址 数据库名回车即可,如:c:\mysqlcheck -r -udemo -pdemo -h218.89.170.62 demo......
  • 如何连接mysql数据库?
    下面的例子是用PHP4连接一个mysql数据库操作的演示<?    /*本例是用PHP4连接一个mysql数据库操作的演示,    实现连接打开一个库,并读取数据的基本功能。    */?><HTML><HEAD><META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0"><style type="text/c......
  • 清空MYSQL数据库的简单办法
    直接在控制面板中点“清空mysql数据库”即可。 也可以用如下办法:保存为***.php运行就OK了<?php$user="";//数据库用户名$password="";//数据库密码$db_name="";//数据库名$link=mysql_connect("localhost:3306",$user,$password);mysql_select_db("$db_name......
  • 如何避免数据库被别人下载?
    有许多方法可以避免access数据库被下载,下面介绍几种1、将数据库改后缀名,改成asp文件形式,或者改为global.asa2、将数据库名字复杂命名,比如命名为kjfkefijkje3.mdb3、最放心的方法是将数据库放置在FTP中的database目录下,这样别人就无法下载了......