對於MySql的全局ID(主鍵),我們一般采用自增整數列、程序生成GUID、單獨的表作為ID生成器,這幾種方案各有優劣,最終效率都不能說十分理想(尤其海量數據下),其實通過Redis的INCR可以很方便生成自增數,因為是操作緩存,生成的效率也不錯。
插入數據庫的主鍵也是連續增長的,配合索引,讀取效率也很高。
下面是從Redis中獲取新的自增數的代碼:
public sealed class Utils { private static readonly object sequence_locker = new object(); /// <summary> /// 從Redis獲取一個自增序列標識 /// </summary> /// <param name="key">鍵名</param> /// <param name="getting">獲取序列標識替代生成方法(若緩存中不存在)</param> public static int NewSequenceFromRedis(string key, Func<int> alternative) { if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key"); lock (sequence_locker) { RedisHelper redis = new RedisHelper(1); //in db1 long value = redis.StringIncrement(key, 1); if (value > Int32.MaxValue || value < Int32.MinValue) throw new OverflowException("The sequence overflow."); if (value <= 1 && alternative != null) { value = alternative(); redis.StringSet(key, value.ToString()); //update } return (int)value; } } }
我的項目用的Repository模式,所以獲取新主鍵的方法我寫到Repository父類中(在接口IRepository中有定義),這樣各個Repository可以重載屬性TableName,當然你完全可以把NewIdentity獨立出去作為公共方法,只要傳入TableName即可
public abstract class RepositoryBase : IRepository { protected IDbConnection _db; public RepositoryBase(IDbConnection connection) { _db = connection; } protected virtual string TableName { get; } public virtual int NewIdentity() { if (string.IsNullOrEmpty(this.TableName)) throw new NoNullAllowedException("TableName is null."); var redisKey = $"Sequence_{TableName}.Id"; //eg. Sequence_lottery.Id, Sequence_player.Id var id = Utils.NewSequenceFromRedis(redisKey, () => { //如果從Redis中沒獲取到主鍵標識(比如Redis鍵被刪除),則用數據表最大標識+1替代 return _db.ExecuteScalar<int>("SELECT MAX(id) AS MaxId FROM " + TableName) + 1; }); return id; } }
下面是測試代碼,並且用StopWatch測試每次執行效率:
using (var ctx = DI.Resolve<IRepositoryContext>()) { System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); var userId = ctx.Resolve<IUserRepository>().NewIdentity(); sw.Stop(); Console.WriteLine("userId={0}, elapsed: {1}ms", userId, sw.ElapsedMilliseconds); sw.Restart(); var gameId = ctx.Resolve<IGameRepository>().NewIdentity(); sw.Stop(); Console.WriteLine("gameId={0}, elapsed: {1}ms", gameId, sw.ElapsedMilliseconds); sw.Restart(); var roomId = ctx.Resolve<IGameRepository>().NewRoomIdentity(); sw.Stop(); Console.WriteLine("roomId={0}, elapsed: {1}ms", roomId, sw.ElapsedMilliseconds); sw.Restart(); var betItemId = ctx.Resolve<IGameRepository>().NewBetItemIdentity(); sw.Stop(); Console.WriteLine("betItemId={0}, elapsed: {1}ms", betItemId, sw.ElapsedMilliseconds); sw.Restart(); var lotteryId = ctx.Resolve<ILotteryRepository>().NewIdentity(); sw.Stop(); Console.WriteLine("lotteryId={0}, elapsed: {1}ms", lotteryId, sw.ElapsedMilliseconds); //省略的代碼。。。 }
運行結果如下,除第一次獲取主鍵開銷98毫秒(估計建立redis連接有關),后面的幾乎都是0毫秒(Redis本來就飛快,這里不用考慮數據庫連接開閉的時間消耗)
查看Redis中的鍵值:
當然,代碼還需要完善,比如Redis掛了的情況,ID主鍵可以讀取MAX(ID)+1來替代主鍵生成,但是Redis又恢復后,自增數怎么同步