如何生成唯一的订单号?

时间:2010-02-15 16:06:22

标签: c# random numbers

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

int customerId = 10000000;

long ticks = DateTime.UtcNow.Ticks;

long orderId = customerId + ticks;

int orderNumber = orderId.GetHashCode();

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

7 个答案:

答案 0 :(得分:24)

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

如果有一个令人信服的理由说明为什么你不能使用你的数据库来生成一个唯一的密钥,那么你应该看一下像Guid 这样的东西 - 它的概率比日期更高-time操作以生成唯一值。 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的令人信服的理由非常罕见。您应该真正使用数据库提供的功能。

答案 1 :(得分:15)

假设您有两个相差100的客户ID,他们碰巧订购的时间间隔为100个单位。你的独特性就在窗外。

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

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

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

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

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

答案 2 :(得分:7)

如果在数据库中有IDENTITY字段,请为您做什么?

它还具有以下优点:删除/取消的订单号不会被重复使用(这很好,甚至可能需要进行核算)。

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

答案 6 :(得分:0)

您可以使用名为 OrderCounter 的单独表。 该表将如下所示:

[订单计数器] ID CurrentCount - 长

该表将仅包含一行。 Id:1,CurrentCount = 1。

当您想生成一个新的订单号时,您可以使用服务访问该表, OrderNumberGeneratorService 将锁定对表的访问,获取当前数字,按您想要的数量增加它。 递增后,将当前值存入表中。

例如,如果您想 1 乘 1。 var ordNum = _orderNumberGeneratorService.GenerateNextOrderNum(); ordNum 将为 2