如何生成唯一的订单号?

我正在寻找一种生成唯一订单ID的好方法。 你能看到下面的代码有什么问题吗?

int customerId = 10000000; long ticks = DateTime.UtcNow.Ticks; long orderId = customerId + ticks; int orderNumber = orderId.GetHashCode(); 

在创建订单之前,我将检查数据库中的数字是否唯一。

如果要将记录存储在数据库中,则应该真正研究可用于生成唯一代理键的function。 在SQLServer中,这将是一个IDENTITY字段,在Oracle中,它将是一个使用SEQUENCE生成新值的字段。

如果有一个令人信服的理由为什么你不能使用你的数据库来生成一个唯一的密钥,你应该看一下像Guid这样的东西 – 它比日期时间操作具有更高的概率来生成一个唯一的值。 Guid可以简单地转换为字符串,因此在这种情况下您的标识符将是一个字符串。

你用哈希做什么并不是一个好主意 – 没有任何关于哈希值得特别注意的东西 – 在许多情况下它们确实会发生碰撞。 Guids – 不提供100%保证机器的唯一性但在一台机器上它们应始终是唯一的。 甚至在机器上,他们碰撞的机会极其遥远。 此外,使用机器时间作为建立潜在价值的方式受竞争条件的限制(如Eric描述的那些)。

Guids是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 ); } } 

编辑: 在这个时代,在应用程序层而不是在数据库中生成唯一ID的令人信服的理由非常罕见。 您应该真正使用数据库提供的function。

假设您有两个相差100的客户ID,并且它们碰巧发出的订单相隔100个单位。 你的独特性就在窗外。

你说你要检查数据库的唯一性; 如果发生碰撞,你不会说你要做什么。 你也没有说你将如何处理竞争条件; 假设同时创建了两个冲突订单ID,数据库中也没有。 您在两个不同的线程上询问数据库该项是否唯一; 它是。 然后输入两者,即使检查已完成,也违反了唯一性。

这是获得独特性的一种非常非常糟糕的方式。 更好的是将其移动到数据库层。 您可以维护一个全局的线程安全的订单计数器,并为每个新订单分配下一个最高订单号。

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

更糟糕的是使用随机数。 随机数是比时间戳更糟糕的唯一性来源。 假设您有一个真正的随机数生成器,可为订单ID生成随机32位整数。 在赔率高于五十五美元之前,您需要多少订单才能生成两个具有相同ID的订单? 这个答案让很多人感到惊讶:在你有50%的可能性生成两个相同数量的订单之前,它只有大约77,000个(只有9300,直到有1%的机会。)

记住:你所追求的是独特性的保证 。 不是一个可能的唯一性,而是一个铁板保证,一个订单号只指一个订单。 如果这就是您所需要的,那么请确保您实现它。

那么在数据库中有一个IDENTITY字段可以帮你吗?

它还具有以下优点:不会重复使用已删除/取消的订单号(这对于会计来说是好的或甚至可能是必需的)。

如果您使用的是SQL Server,那么您应该查找IDENTITY规范。 它可以让您轻松快捷地完成此任务。

您的解决方案并不是唯一的,因为系统中的事情可能发生得如此之快,以至于两个进程(按顺序或同时运行)可以获得相同的tick值。

我将使用IDENTITY列,如果不是,请使用System.Guid.NewGuid()为您生成GUID。

见Knuth Vol。 2第3章随机数