Using Transactions to Prevent Database Errors
What happens when a database receives commands from two different users? We look at the problems that can arise and how to avoid them.
Every time you execute a statement in your database, you change the database’s state. When working in a multi-user environment with asynchronous database access, it will sometimes happen that two users are trying to change the same record at the same time. If both of their statements is an operation that changes the database’s state, this can create erroneous results.
Let’s illustrate this with something we are familiar with: bank accounts. You’ve probably spotted the occasional mistake in your bank statement that like a simple math error. How might this happen?
The Bank Example
Suppose we have a bank application and a user that is trying to withdraw $20 from their account and send it to PayPal. We’re going to experience an unexpected power outage in the middle of this transaction and check out the database’s state.
USER |
SELECT money FROM bank WHERE account = “User1” returns $358 |
Remove $20 from the account UPDATE bank SET money = 338 WHERE account = “User1” |
POWER OUTAGE |
At this moment, the money is lost. It’s left the user’s account, but it wasn’t added anywhere and the system has failed.
What can we do in a situation like this?
Use the Transaction System
Most of today’s databases are transactional, meaning that they support transactions. Transactions are nothing more that groups of commands that have a defined beginning and end. A transaction is an atomic set of operations: either all of them will get executed or none will. In case a system fails, the previous valid state is preserved.
As we’ve seen, using individual commands to complete a multi-step change can be problematic. Maybe a power failure interrupts them and leaves the change half-done. Maybe another user unwittingly starts working on the same record. Either way, there will be errors unless transactions are used. The transaction system has been designed to prevent these errors.
Let’s see how our previous example looks when we use transactions instead of separate commands:
USER |
BEGIN |
SELECT money FROM bank WHERE account = “User1” returns $358 |
Remove $20 from the account UPDATE bank SET money = 338 WHERE account = “User1” |
POWER OUTAGE |
We start the transaction with the BEGIN
clause (note that we didn’t use this in the first example) and we issue the same statements. Because of the power outage, the transaction can’t be finished. In this case, none of our changes actually happen – no money leaves the account.
Worried about #infosec? Let us explain how you can keep your #database safe –> https://t.co/f5ybLVfTf3 <– #cybersecurity #howto pic.twitter.com/zJ1ZGuir64
— Vertabelo (@Vertabelo) May 4, 2017
Finishing the Transaction
After we have successfully executed all the statements in the transaction, it’s time to close it. If everything is correct, we enter a COMMIT
statement and the transaction is executed in the database, changing its state. Otherwise, we use the ROLLBACK
statement, which cancels our transaction and does not change the database.
Transactions are Isolated
Another thing to know about transactions is that they are isolated. This means that if one user is performing an operation on a record, the changes he or she makes are not visible to other users until the transaction is completed.
Let’s illustrate this by adding a second account user that PayPal is reimbursing $10. If this transaction is carried out at the same time as the first account user is sending $20 to PayPal, what happens?
USER 1 | USER 2 |
BEGIN |
BEGIN |
SELECT money FROM bank WHERE account = “User1” returns $358 |
|
SELECT money FROM bank WHERE account = “PayPal” returns $12854 |
|
Remove $20 from the account UPDATE bank SET money = 338 WHERE account = “User1” |
|
Remove $10 from the account UPDATE bank SET money = 12844 WHERE account = “PayPal” |
|
SELECT money FROM bank WHERE account = “PayPal” returns $12854 |
|
Add $20 to the account UPDATE bank SET money = 12874 WHERE account = “PayPal” |
|
SELECT money FROM bank WHERE account = “User2” returns $227 |
|
Add $10 to the account UPDATE bank SET money = 237 WHERE account = “User2” |
|
COMMIT |
COMMIT |
At the end of User 1’s transaction, there’s $12,874 in the PayPal account. At the end of User 2’s transaction, there’s $12,844. Depending which transaction is recorded first, there will be either $10 more or $20 less in the system than there should be.
See #howto use LIKE (and more) to Match #Patterns in #SQL –> https://t.co/hOiH7hOSEd pic.twitter.com/pB4pb5V50i
— Vertabelo (@Vertabelo) April 20, 2017
Depending on the order of the users’ actions and their interlacing, these two transactions might be carried out correctly – or not. However, we like to have deterministic results, and we can get these when the transactions are properly isolated.
This situation can be avoided by setting the right isolation level and having one user wait until the other finishes their transaction. However, we won’t get into how to do that here.
Transactions are a great tool for preventing errors from unexpected failures and complicated logical scenarios. As you continue learning about SQL in Vertabelo SQL Academy, you’ll discover many ways to work with the data in your database. Why not check out one of our courses today?