例如流水号格式如下: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}";
}
}
}

终于成功了。

当然这种处理方式也有不好的地方。

比如当生成流水号最终没有使用,会造成浪费。

最后

感谢阅读,希望可以帮到你。也欢迎留言指正文中的错误与不足,大家共同进步。

C# 订单流水号生成的更多相关文章

  1. .net EF 事物 订单流水号的生成 (二):观察者模式、事物、EF

    针对.net EF 事物 订单流水号的生成 (一)  的封装. 数据依然不变. using System; using System.Linq; using System.Transactions; ...

  2. SQLSERVER之高灵活的业务单据流水号生成

    SQLSERVER之高灵活的业务单据流水号生成 最近的工作中要用到流水号,而且业务单据流水号生成的规则分好几种,并非以前那种千篇一律的前缀+日期+流水号的简单形式,经过对业务的分析,以及参考网上程序员 ...

  3. 通过序列号Sequence零代码实现订单流水号

    序列号管理 本文通过产品编码和订单流水号介绍一下序列号(Sequence)在crudapi中的应用. 概要 序列号 MySQL数据库没有单独的Sequence,只支持自增长(increment)主键, ...

  4. Winform通用模块之流水号生成

    打算接下来的时间里把自己觉得用起来还比较好用的通用模块,在这里向大家介绍一下,如果你有更好的想法时,也希望你不吝指点. 1.数据库表及存储过程 在介绍这个通用流水号生成的模块前,我们先来看一下其相关的 ...

  5. Java订单号生成,唯一订单号(日均千万级别不重复)

    Java订单号生成,唯一订单号 相信大家都可以搜索到很多的订单的生成方式,不懂的直接百度.. 1.订单号需要具备以下几个特点. 1.1 全站唯一性. 1.2 最好可读性. 1.3 随机性,不能重复,同 ...

  6. 偶尔在网上看到的,相对比较好的c#端订单号生成规则

    偶尔在网上看到的,相对比较好的c#端订单号生成规则 public class BillNumberBuilder{     private static object locker = new obj ...

  7. .net EF 事物 订单流水号的生成 (一)

    首先需要 添加 System.Transactions 程序集 数据表: create table SalesOrder ( ID ,) primary key not null, OrderNo ) ...

  8. 业务订单号生成算法,每秒50W左右,不同机器保证不重复,包含日期可读性好

    参考snowflace算法,基本思路: 序列12位(更格式化的输出后,性能损耗导致每毫秒生成不了这么多,所以可以考虑减少这里的位,不过留着也并无影响) 机器位10位 毫秒为左移 22位 上述几个做或运 ...

  9. 分布式系统-主键唯一id,订单编号生成-雪花算法-SnowFlake

    分布式系统下 我们每台设备(分布式系统-独立的应用空间-或者docker环境) * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作 ...

随机推荐

  1. 创建和分析excel文件

    jxl.jar:下载地址:http://download.csdn.net/detail/xuxu198899223/7717737 package excel; public class BookV ...

  2. 用Markdown来写作

    Markdown 是一种简单的.轻量级的标记语法.github上面很多的README就是用markdonw语法写的. Markdown 的语法十分简单,常用的标记符号也不超过十个,且一旦熟悉这种语法规 ...

  3. SICP 练习 (2.12)解决摘要 :不同的实现时间

    SICP 2.12 要求我们定义一个构造函数make-center-percent,它接收两个參数,分别代表中心点和一个误差百分比.我们须要通过这个构造函数产生一个区间.此外还须要定义一个选择函数pe ...

  4. Dos命令将合并两个文本文件的内容

    当生产线的问题,有一个放b.txt的内容被添加到a.txt这需要采取.在考虑这个问题.我的第一感觉是敲代码.阅读b.txt内容,渐进写a.txt.想起昨天在加工处理生产线600M决的方法,我用java ...

  5. Atitit.软体guibuttonand面板---通信子系统(范围)-- github 采用....

    Atitit.软体guibuttonand面板---通讯子系统(区)-- github 的使用.... 1. 1.注冊账户以及创建仓库 1 2. 二.在GitHub中创建项目(create a new ...

  6. ASP.NET MVC2.0 自定义filters

    今天大家共同学习下ASP.NET MVC2.0中自定义filters,这一节主要学习下ActionFilterAttribute, ActionFilterAttribute继承IActionFilt ...

  7. ArcGIS API for Silverlight 使用GP服务实现要素裁剪功能

    原文:ArcGIS API for Silverlight 使用GP服务实现要素裁剪功能 昨天一QQ好友问了一个关于裁剪的问题,感觉自己也没有帮上什么忙,之后自己做了一个裁剪的例子,不过在做这个例子的 ...

  8. C++11 virtual函数学习笔记

    #include<iostream> #include<string> using namespace std; class Base { public: Base(){} ~ ...

  9. LSM树存储模型

    ----<大规模分布式存储系统:原理解析与架构实战>读书笔记 之前研究了Bitcask存储模型,今天来看看LSM存储模型,两者尽管同属于基于键值的日志型存储模型.可是Bitcask使用哈希 ...

  10. 深入理解PHP中赋值与引用

    原文:深入理解PHP中赋值与引用 先看下面的问题: <?php $a = 10;//将常量值赋给变量,会为a分配内存空间 $b = $a;//变量赋值给变量,是不是copy了一份副本,b也分配了 ...