C# 订单流水号生成
例如流水号格式如下:XX201604120001,2位前缀加8位日期加4位流水号
首先各种搜索出现如下解决方案
public class SerialNoHelper { /// <summary> /// 生成流水号 /// </summary> /// <param name="serialno">从数据库读取最大的流水号</param> /// <returns></returns> public String Generate(String serialno) { var today = DateTime.Today.ToString("yyyyMMdd"); if (String.IsNullOrEmpty(serialno)) return $"XX{today}0001"; var date = serialno.Substring(2, 8); if (date == today) { var no = Convert.ToInt32(serialno.Substring(10)); return $"XX{today}{++no:0000}"; } return $"XX{today}0001"; } }
然后测试
class Program { static void Main(string[] args) { //模拟数据库 var array = new List<String>(); //模拟订单号生成 var tasks = new Task[1000]; for (var i = 0; i < tasks.Length; i++) { tasks[i] = Task.Run(() => { var helper = new SerialNoHelper(); var sno = array.LastOrDefault();//模拟从数据库读取最大的流水号 var serialno = helper.Generate(sno);
//各种逻辑操作
array.Add(serialno);//模拟保存到数据库 Console.WriteLine(serialno); }); } //等待执行完成 Task.WaitAll(tasks); Console.WriteLine("-----------------------------------分割线-----------------------------------"); //测试是否重复 var repeat = array.GroupBy(m => m).Where(m => m.Count() > 1).Select(m => m.Key).ToList(); foreach (var item in repeat) Console.WriteLine(item); Console.ReadLine(); } }
测试后不难发现,在高并发下很容易就出现重复。
好像哪里不对啊,修改SerialNoHelper类实现单例,然后给Generate方法加锁。这下应该可以了吧。
public sealed class SerialNoHelper { private static volatile SerialNoHelper helper; private static readonly Object syncRoot = new Object(); private SerialNoHelper() { } public static SerialNoHelper Helper { get { if (helper == null) { lock (syncRoot) { if (helper == null) helper = new SerialNoHelper(); } } return helper; } } /// <summary> /// 生成流水号 /// </summary> /// <param name="serialno">从数据库读取最大的流水号</param> /// <returns></returns> public String Generate(String serialno) { lock (syncRoot) { var today = DateTime.Today.ToString("yyyyMMdd"); if (String.IsNullOrEmpty(serialno)) return $"XX{today}0001"; var date = serialno.Substring(2, 8); if (date == today) { var no = Convert.ToInt32(serialno.Substring(10)); return $"XX{today}{++no:0000}"; } return $"XX{today}0001"; } } }
心情忐忑的按下F5,WTF,居然还是有重复。
不慌,走到窗口猛吸两口雾霾压压惊。接下来分析一下为什么还是会出现重复呢?
生成序列号的时候依赖的是从数据库获取最大的流水号,但是在生成序列号之后,到保存序列号到数据库这之间一般会有一些逻辑操作。
这就导致在高并发的时候,前一个流水号还没有保存到数据库,那就有可能从数据库获取到的流水号是相同的,那么生成的流水号自然就会出现重复。
怎么解决这个问题呢?在Generate方法内就把生成的流水号保存到数据库?这显然不太合适,上面提到保存流水号到数据库一般会有一些逻辑操作。
最终版本
public sealed class SerialNoHelper { private static volatile SerialNoHelper helper; private static readonly Object syncRoot = new Object(); private static String lastdate; private static Int32 lastno; private SerialNoHelper() { } public static SerialNoHelper Helper { get { if (helper == null) { lock (syncRoot) { if (helper == null) helper = new SerialNoHelper(); } } return helper; } } /// <summary> /// 生成流水号 /// </summary> /// <param name="serialno">从数据库读取最大的流水号</param> /// <returns></returns> public String Generate(String serialno) { lock (syncRoot) { var today = DateTime.Today.ToString("yyyyMMdd"); if (today == lastdate) return $"XX{today}{++lastno:0000}"; lastdate = today; lastno = 0;
if (!String.IsNullOrEmpty(serialno) && serialno.Substring(2, 8) == today) lastno = Convert.ToInt32(serialno.Substring(10)); return $"XX{today}{++lastno:0000}"; } } }
终于成功了。
当然这种处理方式也有不好的地方。
比如当生成流水号最终没有使用,会造成浪费。
最后
感谢阅读,希望可以帮到你。也欢迎留言指正文中的错误与不足,大家共同进步。