最新代码在这儿:CombGuid.cs
首先这里不做GUID与整形作为主键的优劣之争,GUID自有它优势,但GUID本身是乱序的,会对索引的维护带来性能上的损耗,数据量越大越明显。
COMB 类型 GUID 是由Jimmy Nilsson在他的“The Cost of GUIDs as PRimary Keys”一文中设计出来的。
基本设计思路是这样的:既然GUID数据因毫无规律可言造成索引效率低下,影响了系统的性能,那么能不能通过组合的方式,保留GUID的前10个字节,用后6个字节表示GUID生成的时间(DateTime),这样我们将时间信息与GUID组合起来,在保留GUID的唯一性的同时增加了有序性,以此来提高索引效率。
在NHibernate已经有代码实现,基于1/300秒为单位生成的,但实际Windows操作系统时钟更新频率多核15ms(1/67秒),单核10ms,是无法精确到1/300秒的,而且在短时间内生成大量CombGuid时,DateTime.Now有可能是不变化的,我在测试中遇到了这种情况,结果生成了5万条CombGuid记录,其中有两万多条是重复的,这样在实际生产环境中如果数据插入量非常大而且频繁的情况下,CombGuid排序的优势就没有了。
针对NHibernate的代码进行了些优化,已当前时间1/10000秒为基础创建CombGuid,如果当前时间基数与上次创建时相同,直接累加1,确保不产生重复的CombGuid:
private static readonly DateTime _CombBaseDate = new DateTime(1900, 1, 1);private static Int32 _LastDays; // 天数private static Int32 _LastTenthMilliseconds; // 单位:1/10 毫秒private static readonly Int32 _MaxTenthMilliseconds = 863999999;#region - NewComb -/// <summary>初始化 CombGuid 结构的新实例。</summary>/// <returns>一个新的 CombGuid 对象。</returns>public static CombGuid NewComb(){var guidArray = Guid.NewGuid().ToByteArray();var now = DateTime.Now;// Get the days and milliseconds which will be used to build the Byte Stringvar days = new TimeSpan(now.Ticks - _CombBaseDate.Ticks).Days;var tenthMilliseconds = (Int32)(now.TimeOfDay.TotalMilliseconds * 10D);var lastDays = _LastDays;var lastTenthMilliseconds = _LastTenthMilliseconds;if (days > lastDays){Interlocked.CompareExchange(ref _LastDays, days, lastDays);Interlocked.CompareExchange(ref _LastTenthMilliseconds, tenthMilliseconds, lastTenthMilliseconds);}else{if (tenthMilliseconds > lastTenthMilliseconds){Interlocked.CompareExchange(ref _LastTenthMilliseconds, tenthMilliseconds, lastTenthMilliseconds);}else{if (_LastTenthMilliseconds < Int32.MaxValue) { Interlocked.Increment(ref _LastTenthMilliseconds); }tenthMilliseconds = _LastTenthMilliseconds;}}// Convert to a Byte arrayvar daysArray = BitConverter.GetBytes(days);var msecsArray = BitConverter.GetBytes(tenthMilliseconds);// Reverse the bytes to match SQL Servers orderingArray.Reverse(daysArray);Array.Reverse(msecsArray);// Copy the bytes into the guidArray.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);//Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);Array.Copy(msecsArray, 0, guidArray, guidArray.Length - 4, 4);return new CombGuid(guidArray, true);}#endregion
这样理论上一天之内允许生成 864000000 个不重复的CombGuid;如果当天生成的个数大于 864000000 ,会一直累加 1 直到 2147483647 ,也就是说实际一天之内能生成 2147483647 个不重复的CombGuid。
完整CombGuid代码,参考了.net fx内部guid、sqlguid的部分方法:
/* * CombGuid * * 作者:海洋饼干 * 时间:2014-09-10 18:42:51 * 博客:http://www.VEVb.com/hmking/ * 版权:版权所有 (C) Eme Development Team 2014*/using System;using System.Collections.Generic;using System.ComponentModel;using System.Data.SqlTypes;using System.Runtime.InteropServices;using System.Threading;using System.xml;using System.Xml.Schema;using System.Xml.Serialization;namespace HmFramework{/// <summary>COMB 类型 GUID,要存储在数据库中或要从数据库中检索的 GUID。</summary>/// <remarks>COMB 类型 GUID 是由Jimmy Nilsson在他的“The Cost of GUIDs as Primary Keys”一文中设计出来的。/// <para>基本设计思路是这样的:既然GUID数据因毫无规律可言造成索引效率低下,影响了系统的性能,那么能不能通过组合的方式,/// 保留GUID的前10个字节,用后6个字节表示GUID生成的时间(DateTime),这样我们将时间信息与GUID组合起来,/// 在保留GUID的唯一性的同时增加了有序性,以此来提高索引效率。</para>/// <para>也许有人会担心GUID减少到10字节会造成数据出现重复,其实不用担心,/// 后6字节的时间精度可以达到 1/10000 秒,两个COMB类型数据完全相同的可能性是在这 1/10000 秒内生成的两个GUID前10个字节完全相同,这几乎是不可能的!</para>/// <para>理论上一天之内允许生成 864000000 个不重复的CombGuid;如果当天生成的个数大于 864000000 ,会一直累加 1 直到 2147483647 ,/// 也就是说实际一天之内能生成 2147483647 个不重复的CombGuid。</para>/// </remarks>public struct CombGuid : INullable, IComparable, IComparable<CombGuid>, IEquatable<CombGuid>, IXmlSerializable{#region -- Fields --private static readonly String _NullString = "nil";private static readonly Int32 _SizeOfGuid = 16;// Comparison orders.private static readonly Int32[] _GuidComparisonOrders = new Int32[16] { 10, 11, 12, 13, 14, 15, 8, 9, 6, 7, 4, 5, 0, 1, 2, 3 };// Parse orders.private static readonly Int32[] _GuidParSEOrders32 = new Int32[32] { 30, 31, 28, 29, 26, 27, 24, 25, 22, 23, 20, 21,18, 19, 16, 17,12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};private static readonly Int32[] _GuidParseOrders36 = new Int32[36] { 34, 35, 32, 33, 30, 31, 28, 29, 27,25, 26, 23, 24,22,20, 21, 18, 19,17,13, 14, 15, 16, 12,0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};// the CombGuid is null if m_value is nullprivate Byte[] m_value;#endregion#region -- 属性 --/// <summary>CombGuid 结构的只读实例,其值空。</summary>public static readonly CombGuid Null = new CombGuid(true);/// <summary>获取 CombGuid 结构的值。 此属性为只读。</summary>public Guid Value{get{if (IsNull){throw new SqlNullValueException();}else{return new Guid(m_value);}}}/// <summary>获取 CombGuid 结构的日期时间属性。/// <para>如果同一时间批量生成了大量的 CombGuid 时,返回的日期时间是不准确的!</para>/// </summary>public DateTime DateTime{get{if (IsNull){throw new SqlNullValueException();}else{var daysArray = new Byte[4];var msecsArray = new Byte[4];// Copy the date parts of the guid to the respective Byte arrays.Array.Copy(m_value, m_value.Length - 6, daysArray, 2, 2);Array.Copy(m_value, m_value.Length - 4, msecsArray, 0, 4);// Reverse the arrays to put them into the appropriate orderArray.Reverse(daysArray);Array.Reverse(msecsArray);// Convert the bytes to intsvar days = BitConverter.ToInt32(daysArray, 0);var msecs = BitConverter.ToInt32(msecsArray, 0);var date = _CombBaseDate.AddDays(days);if (msecs > _MaxTenthMilliseconds) { msecs = _MaxTenthMilliseconds; }msecs /= 10;return date.AddMilliseconds(msecs);}}}#endregion#region -- 构造 --/// <summary>实例化一个空 CombGuid 结构</summary>private CombGuid(Boolean isNull){m_value = null;}/// <summary>使用指定的字节数组初始化 CombGuid 结构的新实例。</summary>/// <param name="value">包含初始化 CombGuid 的值的 16 元素字节数组。</param>public CombGuid(Byte[] value): this(value, false) { }/// <summary>使用指定的字节数组初始化 CombGuid 结构的新实例。</summary>/// <param name="value">包含初始化 CombGuid 的值的 16 元素字节数组。</param>/// <param name="isOwner">是否 CombGuid 结构直接使用</param>private CombGuid(Byte[] value, Boolean isOwner){if (value == null || value.Length != _SizeOfGuid){throw new ArgumentException("value 的长度不是 16 个字节。");}if (isOwner){m_value = value;}else{m_value = new Byte[_SizeOfGuid];value.CopyTo(m_value, 0);}}/// <summary>使用指定字符串所表示的值初始化 CombGuid 结构的新实例。</summary>/// <param name="comb">包含下面任一格式的 CombGuid 的字符串(“d”表示忽略大小写的十六进制数字):/// <para>32 个连续的数字 dddddddddddddddddddddddddddddddd </para>/// <para>- 或 - </para>/// <para>12 和 4、4、4、8 位数字的分组,各组之间有连线符,dddddddddddd-dddd-dddd-dddd-dddddddd</para>/// </param>public CombGuid(String comb){ValidationHelper.ArgumentNullOrEmpty(comb, "comb");Int32 a; Int16 b, c; Byte[] d;if (new GuidParser(comb).TryParse(out a, out b, out c, out d)){m_value = new Byte[_SizeOfGuid];Init(a, b, c, d);}else{throw CreateFormatException(comb);}}private static Exception CreateFormatException(String s){return new FormatException(String.Format("Invalid CombGuid format: {0}", s));}/// <summary>使用指定的 Guid 参数初始化 CombGuid 结构的新实例。</summary>/// <param name="g">一个 Guid</param>public CombGuid(Guid g){m_value = g.ToByteArray();}/// <summary>使用指定的整数和字节数组初始化 CombGuid 类的新实例。</summary>/// <param name="a">CombGuid 的开头四个字节。</param>/// <param name="b">CombGuid 的下两个字节。</param>/// <param name="c">CombGuid 的下两个字节。</param>/// <param name="d">CombGuid 的其余 8 个字节</param>public CombGuid(Int32 a, Int16 b, Int16 c, Byte[] d){ValidationHelper.ArgumentNull(d, "d");// Check that array is not too bigif (d.Length != 8) { throw new ArgumentException("d 的长度不是 8 个字节。"); }m_value = new Byte[_SizeOfGuid];Init(a, b, c, d);}private void Init(Int32 a, Int16 b, Int16 c, Byte[] d){m_value[0] = (Byte)(a);m_value[1] = (Byte)(a >> 8);m_
新闻热点
疑难解答