在接手某个项目的时候, 该项目数据库使用了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位或更长,应该就没有时间的顾虑了。