Duas transações mexendo na mesma linha ao mesmo tempo abrem brechas: dirty read, non-repeatable read, phantom. O nível de isolamento decide quais brechas você aceita. Roda o mesmo cenário em níveis diferentes aqui embaixo e veja a anomalia aparecer ou ser barrada.
Quem decide o nível é a transação, não o banco inteiro. O padrão da maioria dos bancos é Read Committed: você sobe o nível só onde a anomalia dói mais que a perda de concorrência.
Nível
Dirty
Non-repeat
Phantom
Read Uncommitted
pode
pode
pode
Read Committed
barra
pode
pode
Repeatable Read
barra
barra
pode
Serializable
barra
barra
barra
Essa é a matriz do padrão SQL. No Postgres tem um detalhe: o Repeatable Read dele é snapshot isolation, então já barra phantom também.
Escolhendo o nível em Go
tx, err := db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelSerializable,
})
if err != nil { return err }
defer tx.Rollback()
// todas as queries usam tx, não dbif err := tx.Commit(); err != nil {
// em Serializable o commit pode falhar// com erro de serialização: faça retryreturn err
}