6

背景和系统视图

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

任何计费系统中最重要的约束是发票编号必须是连续的。为此,我运行类似于

在伪代码中

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

new invoicenum = x + 1;

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

您能否建议确保帐单编号始终保持连续的最佳方法。?

4

5 回答 5

2

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

  1. 开始交易
  2. 从表中获取数字
  3. 将编号设置为您的发票
  4. 从之前的表格中设置数字 + 1
  5. 犯罪
于 2013-01-08T13:25:54.890 回答
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';

UsingSELECT FOR UPDATE将在表上获得写锁,因此同时插入将被阻塞,同时对读取没有限制。所以唯一的瓶颈可能是发票创建,它一次只会发生一个,我认为这是可以接受的。

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

坦率地说,我不确定如何处理最后一种情况,但我在某处读到我们可以wait_timeout为此使用 MySql 属性,但不确定如何使用它。但是,我将 Spring 用于我的服务器代码并使用@Transactional timeout属性,不确定这是否会涵盖我。

于 2016-03-11T13:29:08.623 回答
1

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

事务和锁定都不是足够的解决方案。

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

事务还不够好,因为每个事务都对数据的快照进行操作(简化解释,可以修改事务隔离级别)。这意味着 1 笔交易可以计算出发票编号必须为 6,而另一笔交易也可以计算出发票编号必须为 6。

您可以做的是使 invoice_number 唯一,因此如果 2 个(或更多)交易尝试插入相同的编号,您将获得其中至少 1 个的例外,从而防止出现空白但无法创建发票。

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

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

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

一个唯一的发票编号,它与前一张发票的编号相同——如果您损坏或取消了一张有序列号的发票,您必须保留它,以便在下次增值税检查时向增值税官员出示

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

于 2013-01-10T16:23:08.540 回答
0

哼,它可能要简单得多。

  • 开始;
  • 保存没有数字但精确 created_at 日期时间的发票
  • 计算在此 created_at datetime & autoincrement pk 之前创建了多少张发票
  • 更新发票中的编号
  • 犯罪;

无需锁定任何东西。

于 2015-08-02T19:47:12.103 回答
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;
于 2015-09-30T12:31:12.240 回答