例如流水号格式如下: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. web富客户端应用下,前端架构、系列(二)。

    序 我们在上面的文章中已经建立起来一个比较简单的 前端架构 虽然这个看上去很简陋. 不过毕竟也是思想的结晶. 从这一篇文章开始,我将陆续完善这一个前端架构.. 重新构思 上一篇我们把前端架构分为3个模 ...

  2. Javascript学习2 - Javascript中的表达式和运算符

    原文:Javascript学习2 - Javascript中的表达式和运算符 Javascript中的运算符与C/C++中的运算符相似,但有几处不同的地方,相对于C/C++,也增加了几个不同的运算符, ...

  3. 如何构建高性能web网站:分布式缓存

    一.数据库前端缓冲区 要清除数据库前缓冲区,首先必须明确什么是文件系统的内核缓冲区(Buffer Area):它位于内核的物理内存地址空间,除了使用O_DIRECT比其他标签中打开文件,所有的磁盘的读 ...

  4. crawler_URL编码原理详解

    经常写爬虫的童鞋,难免要处理含有中文的url,大部分时间,都知道url_encode,各个语言也都有支持,今天简单整理下原理,供大家科普 1.特征: 如果URL中含有非ASCII字符的话, 浏览器会对 ...

  5. crawler_java应用集锦9:httpclient4.2.2的几个常用方法,登录之后访问页面问题,下载文件_设置代理

    在工作中要用到android,然后进行网络请求的时候,打算使用httpClient. 总结一下httpClient的一些基本使用. 版本是4.2.2. 使用这个版本的过程中,百度很多,结果都是出现的o ...

  6. oracle创建user具体指示

    一个.用户的概念 用户,这是user,通俗的讲就是参观oracle数据库"人".在oracle在.的各种安全参数的用户可控制,为了保持数据库的安全性,的概念包括模型(schema) ...

  7. Angularjs -- 核心概念

     angularjs旨在减轻使用AJAX开发应用程序的复杂度,使得程序的创建.測试.扩展和维护变得easy.以下是angularjs中的一些核心概念. 1. client模板      多页面的应用通 ...

  8. SVN提交忽略*.class、.classpath、.mymetadata、.project、.settings、.myeclipse和其他非版本控制文件

    1.忽略*.class 在TortoiseSVN -->setting(设定)--规设置 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHVrZTY ...

  9. Android 它们的定义ListView实现底部和页下拉刷新刷新的顶

    在项目开发.由于数据量过大,寻呼需要加载或下拉刷新.为了缓解长期等待-time负载.这个博客的评论中被自己的定义实例ListView实现底部的下拉刷新页面正在加载结果和顶部. 其效果图: 一.List ...

  10. canvas绘制贝塞尔曲线

    原文:canvas绘制贝塞尔曲线 1.绘制二次方贝塞尔曲线 quadraticCurveTo(cp1x,cp1y,x,y); 其中参数cp1x和cp1y是控制点的坐标,x和y是终点坐标 数学公式表示如 ...