Analogous to the example from the article:
create table transaction (user_id integer, amount integer);
insert into transaction values (1, -100);
select sum(amount) as balance from transaction;Analogous to the example from the article:
create table transaction (user_id integer, amount integer);
insert into transaction values (1, -100);
select sum(amount) as balance from transaction;1) you make transactions serialized
BEGIN ISOLATION LEVEL SERIALIZABLE;
select sum(amount) as balance from transaction where user_id = 1;
-- do something with balance
insert into transaction values (1, -100);
COMMIT;
2) or you use row level locking on an additional table (say user, while this conflicts with the system table user, but for the sake of the example we assume this is not the case) to serialize the access. BEGIN;
select user_id from user where user_id = 1 for update;
select sum(amount) as balance from transaction where user_id = 1;
-- do something with balance
insert into transaction values (1, -100);
COMMIT;
Now it may be tempting to change this to BEGIN;
select balance from user where user_id = 1 for update;
-- do something with balance
update user set balance = balance + (-100) where user_id = 1;
insert into transaction values (1, -100);
COMMIT;
Just for clarity: in most cases you should register your transaction event anyway (with a time stamp) for later auditing, but you now will have a risk of introducing inconsistencies between calculated balance and transactions due to the programming errors (that is partially committed transactions).Also you should obviously create proper primary keys and indexes.
Also you should include proper error/exception handling.
Also if you already do locking, then you could also consider different locking strategies described in the article.
Then your sql code can check the balance without doing a sum (which might get expensive, and often the same value until a transaction changes it) just by joining to that view.
i.e. `WHERE total_view.balance > 0`
What are the space concerns for keeping data in this way? For something like a retail bank (audit issues aside which probably make this necessary anyway) does space suddenly become a big factor?
Double-entry book-keeping is done this way. The data certainly grows over time, but generally at a predictable and manageable rate.
Most people using long-lived event-sourced data structures have some kind of snapshot/checkpoint/archive mechanism, which going back to the accounting example might simply be an opening balance.
I think the more realistic concern with this approach is that some queries might get complicated when they involve more complex logic based on the balance.
Solved or made worse by caching?
Caching may relieve some stress (on say a dashboard), but if an application is doing calculations around account balances, and uses event sourcing-like structures in the database, then caching isn't going to help as those calculations are going to still need to be done for each new balance changing event.
So caching is never worse (unless you accidentally used the cached value where you needed the live value), but isn't necessarily going to relieve a lot of DB stress.
Triggers get a lot of hate because of separation from application level version control etc., but these are also long discussions to get into.
edit: This article's contents are good to be included with all the most junior level application design/development training material.