Code Quiz

Lost Update in a Transfer Transaction

A money-transfer function wrapped in a transaction still loses updates under concurrency — find out why.

Codejavascript
async function withdraw(client, accountId, amount) {
  await client.query('BEGIN'); // default READ COMMITTED
  const { rows } = await client.query(
    'SELECT balance FROM accounts WHERE id = $1',
    [accountId]
  );
  const current = rows[0].balance;
  if (current < amount) {
    await client.query('ROLLBACK');
    throw new Error('Insufficient funds');
  }
  const newBalance = current - amount;
  await client.query(
    'UPDATE accounts SET balance = $1 WHERE id = $2',
    [newBalance, accountId]
  );
  await client.query('COMMIT');
}

Two concurrent calls to withdraw() can leave the balance too high (a lost update). Why, and how should it be fixed?