Background and System View
We have implemented a Billing system in a distributed environment. There are 4 terminals that generate around 2 bills per minute per terminal. We use Mysql as backend and C#, winforms as our client tech.
The most important constraint in any billing system is that the invoice number must be sequential. To do that I run a query similar to
In pseudo-code
let x ="SELECT count(*) from Orders where IsInvoiceGenerated=1 and FinancialYear=val
new invoicenum = x + 1;
The Problem
Everything was running ok till 411th invoice, after which the system suddenly skipped 2 invoices and generated Invoice 414. We investigated the system state and found that system was not tampered externally and we also inferred that nobody accessed database from workbench. This is a major issue since it also has legal ramifications.
Can you please suggest the best way to ensure that billing number always remains sequential.?
Before I start, I’d just like to apologize to @Grumbler85 – you were right. This question bugged me for a while and I’ll try to answer it as best to my knowledge as I can.
Both transactions and locking are insufficient solutions.
Reasons: Locks aren’t good because once you lock a table, you have to unlock it. Unlock might fail, we all know the unstable nature of networks and computers in general. Bottom line is – you’d have to use your C# application to issue locks and unlocks.
Every time you generate an invoice, you’d have to lock the table that is being used as a counter, forcing every other MySQL session to wait until you release the lock. From my experience, within a few days you’d have to hire an administrator whose job would be to release the locks.
Transactions aren’t good enough because each transaction operates on a snapshot of the data (simplified explanation, transaction isolation level can be modified). That means that 1 transaction can calculate that invoice number must be 6, while another transaction would calculate also that invoice number must be 6.
What you could do is make the invoice_number unique so if 2 (or more) transactions try to insert the same number, you’d get an exception for at least 1 of them, thus preventing gaps but failing the invoice creation.
Using auto_increment is also not an option. Auto_increment is just a simple counter. That means that auto_increment doesn’t “reuse” numbers dropped for some reason – reason being that an error occurred and transaction couldn’t be saved, effectively making the auto_increment calculated for that record to be lost.
So what options are there? Personally, I would create a simple service which would run at predefined time-intervals which would update invoices that haven’t got
invoice_numberset. The service wouldn’t offer concurrent access, and there would always be one connection active which would work on a set of already inserted invoices.It is true that there are laws in place (in certain countries, such as England) which specify that there MUST be a sequenced invoice numbering, I was wrong about that as well. Source: http://www.hmrc.gov.uk/vat/managing/charging/vat-invoices.htm and excerpt from the source:
The final option is that you are satisfied with invoice creation failure if two or more transactions acquire the same invoice number, which means you’d have to implement a way of re-running the failed transaction (which is everything but simple).