对于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毫秒(估计是StopWatch初始化),后面的几乎都是0毫秒(Redis本来就飞快,这里不用考虑数据库连接开闭的时间消耗)
查看Redis中的键值:
当然,代码还需要完善,比如Redis挂了的情况,ID主键可以读取MAX(ID)+1来替代主键生成,但是Redis又恢复后,自增数怎么同步