改进GUID作为主键时的问题
在接手某个项目的时候, 该项目数据库使用了GUID(UniqueIdentifier)作为大部分表的主键,同时也默认使用了聚集索引。 当数据量增加的时候, 发现会产生大量的磁盘碎片。 因此运维还专门写了个job任务来整理碎片。
这主要是由于GUID的不连续造成的。 当新的数据行插入时,GUID可能会造成大量数据的移动, 因此影响了性能。
为什么使用GUID作为主键?主要是从后台来讲, 在插入数据前,可以生成一个唯一的Id,这个还是有很多实用场景的。
所以需要对该问题进行改进。
我们在项目中使用的是Timestamp Based Guid, 或者简称为TBGuid。其实是一个字符串,长度40: 前8位是基于Unix Time的相对时间秒,转16进制后的文本;后32位是GUID转文本后去除“-”连接号。也就是时间+Guid的格式。
利用时间的连续性加Guid的唯一性, 使其即具有连续性,不至于在新增数据的时候对数据连续存储产生影响;又具有唯一性, 满足主键要求。
下面是C#中的实现方法:
/// <summary> /// 返回唯一的40位长度的Id (Hex(UnixTime)+Guid) /// </summary> /// <returns></returns> public static string NewId() { string result = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString("x", CultureInfo.CurrentUICulture.NumberFormat) + Guid.NewGuid().ToString("N", CultureInfo.CurrentUICulture.NumberFormat); return result; }
下面是SQL中的实现方法:
/* * Author : Harvey Hu * Created On : 2021-04-02 * Description : 返回一个40位长度的字符串Id: 前8位数为UTC时间相对值, 后32位为GUID * Sample call [dbo].[fn_NewID40](NEWID()) , return '6066AFB6020C76DC0AC0445F8E882E3801D0F239' */ CREATE FUNCTION [dbo].[fn_NewID40] (@uid uniqueidentifier) RETURNS nvarchar(40) AS BEGIN -- 注意UTC时区, 目前是东8, 所以 RETURN FORMAT(Datediff(s, '1970-1-1', Dateadd(hour, -8, Getdate())), 'X') + Replace( Cast(@uid AS nvarchar(36)), '-', '') END