English 中文(简体)
如何生成唯一订单号?
原标题:How to generate unique order number?

我正在寻找一种生成独特订单ID的好方法。您能看出以下代码中是否存在问题吗?

int customerId = 10000000;

long ticks = DateTime.UtcNow.Ticks;

long orderId = customerId + ticks;

int orderNumber = orderId.GetHashCode();

我会核实该数字在建立订单之前在数据库中是独一无二的。

问题回答

如果您正在将记录存储在数据库中,您应该真正地了解可用于生成唯一替代键的功能。在SQL Server中,这将是一个IDENTITY字段,在Oracle中,它将是使用序列生成新值的字段。

如果有充分的理由,不能使用您的数据库生成唯一键,那么您应该查看类似Guid这样的东西——它比日期时间操作更具生成唯一值的可能性。 Guid可以轻松转换为字符串,因此在这种情况下,您的标识符将是字符串。

您使用哈希的方式并不是一个好主意 - 没有什么东西可以保证哈希是唯一的 - 在许多情况下,它们确实会发生碰撞。Guids-不能提供100%的唯一性保证,跨机器但在单个机器上,它们应该始终是唯一的。即使在跨机器的情况下,他们发生碰撞的可能性也极为遥远。此外,使用机器时间作为构建基础值的方法会受到竞争条件的影响(就像Eric所描述的那样)。

Guid是128位的值,因此您无法将它们表示为简单的intlong。这将要求您将字符串用作您的ID,这可能取决于其他考虑因素(例如您是否控制数据模型)。如果可以使用它们,使用Guid非常容易:

string customerId = Guid.NewGuid().ToString(); // fetch new guid and save as string
string orderNumber = Guid.NewGuid().ToString(); // same story here...

如果您确实必须使用数字标识符,并且愿意放弃轻松跨多个服务器扩展应用程序的能力,您可以使用自动增量的全局数字来提供唯一键。您需要在应用程序启动时使用数据库中的下一个可用值(max+1)来预置此数字。您还需要保护此值不受多个线程的并发使用。我会在类中包装这个责任:

class static UniqueIDGenerator 
{
    // reads Max+1 from DB on startup
    private static long m_NextID = InitializeFromDatabase(); 

    public static long GetNextID() { return Interlocked.Increment( ref m_NextID ); }
}

这是一个测试。

<<>strong>EDIT:>,在这一天和年龄段,令人信服的理由在你的申请层而不是数据库中生成独特的身份证,这是非常罕见的。 您应真正利用数据库提供的能力。

假设您有两个客户ID之间相差100,并且他们恰好都下了一个间隔为100时间单位的订单。您的唯一性就消失了。

你说你要检查数据库的唯一性;但你并没有说如果发生冲突你会怎么做。你也没有说你会如何处理竞态条件;假设在同一时间创建了两个冲突的订单编号且它们都不在数据库中。你在两个不同的线程上询问数据库,查询结果都是唯一的。然后你将它们同时插入数据库,尽管做了检查,但唯一性还是被破坏了。

这确实是获得独一无二之处。 最好把这一数据输入数据库层。 你们可以维持一个全球性的、可怕的指令,并将每个新命令排列为下一个最高命令号。

顺便提一下,多年来,我一直将这个问题的变体作为技术面试问题。我注意到,试图将时间作为独特性的来源的人和没有被雇用的人之间存在着强烈的相关性。时间是一个糟糕的独特性来源;很多不同的事情可以同时发生。

更糟糕的是使用随机数字。随机数字比时间戳更糟糕的唯一性来源。假设您有一个真正的随机数生成器,可以为订单ID生成随机的32位整数。您需要多少订单才能使生成两个具有相同ID的订单的可能性超过五十比分之一呢?答案惊讶了很多人:只需要大约77,000个订单,就有50%的机会生成两个具有相同数字的订单(而只需要9300个订单,就有1%的机会) 。

记住:你要追求的是绝对的唯一性保证。不是大概率的唯一性,而是一种铁一般的保证,即一个订单号仅指一个订单。如果这正是你所需要的,那么一定要确保实现这一点。

在数据库中加入一个IDENTITY字段怎么样?

它还有一个优点,即删除/取消的订单号不会被重复使用(这对会计来说是好的,甚至可能是必需的)。

如果您正在使用SQL Server,您应该真正查找IDENTITY规范。它可以让您轻松快速地完成此操作。

你的解决方案不是独一无二的,因为在系统中可能会发生太快的事情,导致两个进程无论是按顺序运行还是同时运行,都可能会获得相同的tick值。

我会使用IDENTITY列,如果没有的话,会使用System.Guid.NewGuid()为您生成GUID。

请参阅《计算机程序设计艺术》卷2第3章关于随机数的内容(链接:http://en.wikipedia.org/wiki/The_Art_of_Computer_Programming)。

You can use a separate table called OrderCounter. The table would look like this:

[OrderCounter] Id CurrentCount - long

The table will contain only one row. Id: 1, CurrentCount = 1.

When you want to generate a new order number you access the table using a service, OrderNumberGeneratorService that will lock the access to the table, get the current number, increment it by how much you want. After increment, store the current value in the table.

So for example if you want to go 1 by 1. var ordNum = _orderNumberGeneratorService.GenerateNextOrderNum(); ordNum will be 2





相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...