如何确保无差距和安全的发票编号生成(法律问题)

时间:2013-01-08 13:21:29

标签: c# mysql concurrency

背景和系统视图

我们在分布式环境中实施了计费系统。有4个终端每个终端每分钟产生大约2张票据。我们使用Mysql作为后端和C#,winforms作为我们的客户端技术。

任何结算系统中最重要的限制因素是发票编号必须是连续的。为此,我运行类似于

的查询

在伪代码中

let x ="SELECT count(*) from Orders where IsInvoiceGenerated=1 and FinancialYear=val

new invoicenum = x + 1;

问题 一切都运行正常,直到411发票,之后系统突然跳过2张发票并生成发票414.我们调查了系统状态,发现系统没有外部篡改,我们还推断没有人从工作台访问数据库。这是一个重大问题,因为它也有法律后果。

是否可以建议确保帐单号始终的最佳方法仍然是顺序的。

5 个答案:

答案 0 :(得分:2)

要创建唯一编号,您应将当前编号存储在表格中,然后在创建新发票时必须执行以下步骤:

  1. 开始交易
  2. 从表中获取数字
  3. 将号码设置为发票
  4. 之前的表格中设置数字+ 1
  5. 提交

答案 1 :(得分:2)

这是我提出的解决方案:

CREATE TABLE `inv` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `invNo` int(10) DEFAULT NULL,
  `invName` varchar(100) DEFAULT NULL,
  `cratetedAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE     CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=latin1;

并使用此查询创建新发票

INSERT INTO `inv` (`invNo`, `invName`) SELECT (SELECT MAX(invNo)+1 FROM `inv` FOR UPDATE) AS `invNo`, 'Invoice 1';

使用SELECT FOR UPDATE将获取表上的写锁定,因此同时插入将是块,同时对读取没有限制。因此,唯一的瓶颈可能是发票创建,一次只能发生一次,我认为这是可以接受的。

现在唯一关心的是如果我的服务器停止,崩溃,网络丢失或发生了一些特殊情况并且代码没有机会完成交易会发生什么,并且最终可能会出现死锁。

坦率地说我不知道​​如何处理这最后一种情况,但我正在阅读某个地方,我们可以使用MySql wait_timeout属性,但不知道如何使用它。但是我使用Spring作为我的服务器代码并使用@Transactional timeout属性,不确定是否会覆盖我。

答案 2 :(得分:1)

在开始之前,我只想向@ Grumbler85道歉 - 你是对的。这个问题困扰了我一段时间,我会尽力回答这个问题。

交易和锁定都是解决方案不足。

原因:锁定不好,因为一旦你锁定了一个表,你就必须解锁它。解锁可能会失败,我们都知道网络和计算机的不稳定性。底线是 - 你必须使用你的C#应用​​程序发出锁定和解锁。 每次生成发票时,都必须锁定用作计数器的表,强制每个其他MySQL会话等到释放锁。根据我的经验,在几天之内你就必须雇用一名管理员,他的工作就是释放锁。

事务不够好,因为每个事务都在数据快照上运行(简化说明,事务隔离级别可以修改)。这意味着1个交易可以计算出发票编号必须为6,而另一个交易也会计算发票编号必须为6.

你可以做的是使invoice_number唯一,所以如果2个(或更多)交易试图插入相同的数字,你至少会有1个例外,从而防止出现差距但创建发票失败。 / p>

使用auto_increment也不是一种选择。 Auto_increment只是一个简单的计数器。这意味着auto_increment不会“重用”由于某种原因丢弃的数字 - 原因是发生了错误并且无法保存事务,这有效地使得为该记录计算的auto_increment丢失。

那有什么选择?就个人而言,我会创建一个简单的服务,它将以预定义的时间间隔运行,这将更新未设置invoice_number的发票。该服务不会提供并发访问,并且总会有一个活动连接可用于一组已插入的发票。

确实存在法律(在某些国家,例如英格兰),这些法律规定必须有一个有序的发票编号,我也错了。资料来源:http://www.hmrc.gov.uk/vat/managing/charging/vat-invoices.htm,摘自来源:

  

一个唯一的发票号码,后面跟着号码   以前的发票 - 如果您破坏或取消连续编号   发票,您必须保留下一个增值税的增值税官员   检查

最后一个选项是,如果两个或多个交易获得相同的发票号,您对发票创建失败感到满意,这意味着您必须实施一种重新运行失败事务的方法(这一切都很简单)

答案 3 :(得分:0)

嗯,它可能更简单。

  • BEGIN;
  • 保存发票,但没有数字,但精确的created_at datetime
  • 计算在此created_at datetime&之前创建了多少发票。 autoincrement pk
  • 更新发票号码
  • COMMIT;

无需锁定任何东西。

答案 4 :(得分:0)

我现在已经在这个主题上工作了半天,我能想出的最佳解决方案如下:

我创建了一个表格,用于存储下一个发票编号。它从1开始。我还指定了一个名称,以便我可以使用相同的表处理更多序列。

CREATE TABLE invoice_numbers (name VARCHAR(50), number int4 DEFAULT 1); 
INSERT INTO invoice_numbers VALUES ('main');

我以草稿模式创建发票,草稿发票没有发票编号。发票草稿也可以删除。完成最终检查后,可以最终确定发票。当然,最终确定的发票是不可变的。最终化过程开始,事务从invoice_numbers表中选择适当的行进行更新。这确保了在任何给定时间只有一个进程可以访问记录。我将号码分配给发票并将号码增加一个。最后我提交了交易。

START TRANSACTION;
SELECT number FROM invoice_numbers WHERE name='main' FOR UPDATE;

assign the number to the invoice and set its status to finalized;

UPDATE invoice_numbers SET number = number + 1 WHERE name='main';
COMMIT;