Comment by mrkeen

Comment by mrkeen a day ago

2 replies

And then they take that toy transaction model and think that they're on ACID when they're not.

Are you stepping out of SQL to write application logic? You probably broke ACID. Begin a transaction, read a value (n), do a calculation (n+1), write it back and commit: The DB cannot see that you did (+1). All it knows is that you're trying to write a 6. If someone else wrote a 6 or a 7 in the meantime, then your transaction may have 'meant' (+0) or (-1).

Same problem when running on reduced isolation level (which you probably are). If you do two reads in your 'transaction', the first read can be at state 1, and the second read can be at state 2.

I think more conversations about the single "fully consistent" db approach should start with it not being fit-for-purpose - even without considering that it can't address soft-modification (which you should recognise as a need immediately whenever someone brings up soft-delete) or two-generals (i.e. consistency with a partner - you and VISA don't live in the same MySql instance, do you? Or to put it in moron words - partitions between your DB and VISA's DB "don't happen often" (they happen always!))

fishstamp82 a day ago

RE: "All it knows is that you're trying to write a 6. If someone else wrote a 6 or a 7 in the meantime, then your transaction may have 'meant' (+0) or (-1)."

This is not how it works at all. This is called dirty writes and is by default prevented by ACID compliant databases, no matter the isolation level. The second transaction commit will be rejected by the transaction manager.

Even if you start a transaction from your application, it does not change this still.

  • mrkeen a day ago

    I have no problem with ACID the concept. It's a great ideal to strive towards. I'm sure your favourite RDBMS does a fine job of it. If you send it a single SQL string, it will probably behave well no matter how many other callers are sending it SQL strings (as long as the statements are grouped appropriately with BEGIN/COMMIT).

    I'm just pointing out two ways in which you can make your system non-ACID.

    1) Leave it on the default isolation level (READ_COMMITTED):

    You have ten accounts, which sum to $100. You know your code cannot create or destroy money, only move it around. If no other thread is currently moving money, you will always see it sum to $100. However, if another thread moves money (e.g. from account 9 to account 1) while your summation is in progress, you will undercount the money. Perfectly legal in READ_COMMITTED. You made a clean read of account 1, kept going, and by the time you reach account 9, you READ_ what the other thread _COMMITTED. Nothing dirty about it, you under-reported money for no other reason than your transactions being less-than-Isolated. You can then take that SUM and cleanly write it elsewhere. Not dirty, just wrong.

    2) Use an ORM like LINQ. (Assume FULL ISOLATION - even though you probably don't have it)

    If you were to withdraw money from the largest account, split it into two parts, and deposit it into two random accounts, you could do it ACID-compliantly with this SQL snippet:

        SELECT @bigBalance = Max(Balance) FROM MyAccounts
        SELECT @part1 = @bigBalance / 2;
        SELECT @part2 = @bigBalance - @part1;
        ..
        -- Only showing one of the deposits for brevity
        UPDATE MyAccounts
        SET Balance = Balance + @part1
        WHERE Id IN (
            SELECT TOP 1 Id
            FROM MyAccounts
            ORDER BY NewId()
        );
    
    Under a single thread it will preserve money. Under multiple threads it will preserve money (as long as BEGIN and COMMIT are included ofc.). Perfectly ACID. But who wants to write SQL? Here's a snippet from the equivalent C#/EF/LINQ program:

        // Split the balance in two
        var onePart = maxAccount.Balance / 2;
        var otherPart = maxAccount.Balance - onePart;
    
        // Move one half
        maxAccount.Balance -= onePart;
        recipient1.Balance += onePart;
    
        // Move the other half
        maxAccount.Balance -= otherPart;
        recipient2.Balance += otherPart;
    
    Now the RDBMS couldn't manage this transactionally even if it wanted to. By the final lines, 'otherPart' is no longer "half of the balance of the biggest account", it's a number like 1144 or 1845. The RDBMS thinks it's just writing a constant and can't connect it back to its READ site:

        info: 1/31/2026 17:30:57.906 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) 
            Executed DbCommand (7ms) [Parameters=[@p1='a49f1b75-4510-4375-35f5-08de60e61cdd', @p0='1845'], CommandType='Text', CommandTimeout='30']
            SET NOCOUNT ON;
            UPDATE [MyAccounts] SET [Balance] = @p0
            WHERE [Id] = @p1;
            SELECT @@ROWCOUNT;