例如流水號格式如下: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}";
}
}
}
終於成功了。
當然這種處理方式也有不好的地方。
比如當生成流水號最終沒有使用,會造成浪費。
最后
感謝閱讀,希望可以幫到你。也歡迎留言指正文中的錯誤與不足,大家共同進步。
