在接手某个项目的时候, 该项目数据库使用了GUID(UniqueIdentifier)作为大部分表的主键,同时也默认使用了聚集索引。 当数据量增加的时候, 发现会产生大量的磁盘碎片。 因此运维还专门写了个job任务来整理碎片。

这主要是由于GUID的不连续造成的。 当新的数据行插入时,GUID可能会造成大量数据的移动, 因此影响了性能。

为什么使用GUID作为主键?主要是从后台来讲, 在插入数据前,可以生成一个唯一的Id,这个还是有很多实用场景的。 

所以需要对该问题进行改进。 

 

我们在项目中使用的是Timestamp Based Guid, 或者简称为TBGuid。其实是一个字符串,长度40: 前8位是基于Unix Time的相对时间秒,转16进制后的文本;后32位是GUID转文本后去除“-”连接号。也就是时间+Guid的格式。

利用时间的连续性加Guid的唯一性, 使其即具有连续性,不至于在新增数据的时候对数据连续存储产生影响;又具有唯一性, 满足主键要求。 

下面是C#中的实现方法:

 1         /// <summary>
 2         /// 返回唯一的40位长度的Id (Hex(UnixTime)+Guid)
 3         /// </summary>
 4         /// <returns></returns>
 5         public static string NewId()
 6         {
 7             string result = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString("x", CultureInfo.CurrentUICulture.NumberFormat)
 8                         + Guid.NewGuid().ToString("N", CultureInfo.CurrentUICulture.NumberFormat);
 9             return result;
10         }

 

 

下面是SQL中的实现方法:

 1  
 2 /*
 3  * Author        : Harvey Hu
 4  * Created On    : 2021-04-02
 5  * Description   :  
 6         返回一个40位长度的字符串Id: 前8位数为UTC时间相对值, 后32位为GUID
 7  * Sample 
 8         call        [dbo].[fn_NewID40](NEWID()) ,  
 9         return      '6066AFB6020C76DC0AC0445F8E882E3801D0F239'
10 */
11 CREATE FUNCTION [dbo].[fn_NewID40] (@uid uniqueidentifier)
12 RETURNS nvarchar(40)
13 AS
14   BEGIN
15       -- 注意UTC时区, 目前是东8, 所以
16       RETURN FORMAT(Datediff(s, '1970-1-1', Dateadd(hour, -8, Getdate())), 'X')
17              + Replace( Cast(@uid AS nvarchar(36)), '-', '')
18   END 

 

补充一点,要保持40位长度的话, 前面8位的UnixTime大概还能用70年(粗估)。如果在意的话, 可以考虑用42位或更长,应该就没有时间的顾虑了。

posted on 2021-07-07 16:27  Weizheng  阅读(267)  评论(0编辑  收藏  举报