在設計表結構時,我們首先遇到的問題就是主鍵設置為什么類型的。之前我用過int 也用過GUID,都不太理想:
使用int做主鍵的缺點
1、如果經常有合並表的操作,就可能會出現主鍵重復的情況。
2、使用int 數據范圍有限制。如果存在大量的數據,可能會超出int 的取值范圍。
3、很難處理分布式存儲的數據表。
使用GUID做主鍵的缺點:
1、存儲空間大(16 byte),因此它將會占用更多的磁盤大小。
2、很難記憶。join操作性能比int要低。
3、沒有內置的函數獲取最新產生的guid主鍵。
4、GUID做主鍵將會添加到表上的所以其他索引中,因此會降低性能。
5、不宜排序。
這次我選擇了使用雪花ID。雪花ID是用一個64位的整形數字來做ID,對應.net中的long,數據庫中的bigint。

算法描述:
- 最高位是符號位,始終為0,不可用。
- 41位的時間序列,精確到毫秒級,41位的長度可以使用69年。時間位還有一個很重要的作用是可以根據時間進行排序。
- 10位的機器標識,10位的長度最多支持部署1024個節點。
- 12位的計數序列號,序列號即一系列的自增id,可以支持同一節點同一毫秒生成多個ID序號,12位的計數序列號支持每個節點每毫秒產生4096個ID序號。
源碼如下:
1 public class SnowflakeIdWorker//雪花ID 2 { 3 //機器ID 4 private static long workerId; 5 private static long twepoch = 687888001020L; //唯一時間,這是一個避免重復的隨機量,自行設定不要大於當前時間戳 6 private static long sequence = 0L; 7 private static int workerIdBits = 4; //機器碼字節數。4個字節用來保存機器碼(定義為Long類型會出現,最大偏移64位,所以左移64位沒有意義) 8 public static long maxWorkerId = -1L ^ -1L << workerIdBits; //最大機器ID 9 private static int sequenceBits = 10; //計數器字節數,10個字節用來保存計數碼 10 private static int workerIdShift = sequenceBits; //機器碼數據左移位數,就是后面計數器占用的位數 11 private static int timestampLeftShift = sequenceBits + workerIdBits; //時間戳左移動位數就是機器碼和計數器總字節數 12 public static long sequenceMask = -1L ^ -1L << sequenceBits; //一微秒內可以產生計數,如果達到該值則等到下一微妙在進行生成 13 private long lastTimestamp = -1L; 14 15 /// <summary> 16 /// 機器碼 17 /// </summary> 18 /// <param name="workerId"></param> 19 public SnowflakeIdWorker() 20 { 21 long workerId = 1; 22 if (workerId > maxWorkerId || workerId < 0) 23 throw new Exception(string.Format("worker Id can't be greater than {0} or less than 0 ", workerId)); 24 SnowflakeIdWorker.workerId = workerId; 25 } 26 27 public long NextId() 28 { 29 lock (this) 30 { 31 long timestamp = timeGen(); 32 if (this.lastTimestamp == timestamp) 33 { //同一微妙中生成ID 34 SnowflakeIdWorker.sequence = (SnowflakeIdWorker.sequence + 1) & SnowflakeIdWorker.sequenceMask; //用&運算計算該微秒內產生的計數是否已經到達上限 35 if (SnowflakeIdWorker.sequence == 0) 36 { 37 //一微妙內產生的ID計數已達上限,等待下一微妙 38 timestamp = tillNextMillis(this.lastTimestamp); 39 } 40 } 41 else 42 { //不同微秒生成ID 43 SnowflakeIdWorker.sequence = 0; //計數清0 44 } 45 if (timestamp < lastTimestamp) 46 { //如果當前時間戳比上一次生成ID時時間戳還小,拋出異常,因為不能保證現在生成的ID之前沒有生成過 47 throw new Exception(string.Format("Clock moved backwards. Refusing to generate id for {0} milliseconds", 48 this.lastTimestamp - timestamp)); 49 } 50 this.lastTimestamp = timestamp; //把當前時間戳保存為最后生成ID的時間戳 51 long nextId = (timestamp - twepoch << timestampLeftShift) | SnowflakeIdWorker.workerId << SnowflakeIdWorker.workerIdShift | SnowflakeIdWorker.sequence; 52 return nextId; 53 } 54 } 55 56 /// <summary> 57 /// 獲取下一微秒時間戳 58 /// </summary> 59 /// <param name="lastTimestamp"></param> 60 /// <returns></returns> 61 private long tillNextMillis(long lastTimestamp) 62 { 63 long timestamp = timeGen(); 64 while (timestamp <= lastTimestamp) 65 { 66 timestamp = timeGen(); 67 } 68 return timestamp; 69 } 70 71 /// <summary> 72 /// 生成當前時間戳 73 /// </summary> 74 /// <returns></returns> 75 private long timeGen() 76 { 77 return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds; 78 } 79 }
調用時:
SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker();
ID= snowflakeIdWorker.NextId().ToString();
