feat: add golang-database skill with best practices and reference documentation
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# Transactions, Isolation Levels, and Locking
|
||||
|
||||
## Basic transaction pattern
|
||||
|
||||
```go
|
||||
tx, err := db.BeginTxx(ctx, nil) // default isolation (READ COMMITTED)
|
||||
if err != nil {
|
||||
return fmt.Errorf("beginning transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback() // no-op if already committed
|
||||
|
||||
// ... execute queries using tx ...
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("committing transaction: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
## Custom isolation level
|
||||
|
||||
```go
|
||||
tx, err := db.BeginTxx(ctx, &sql.TxOptions{
|
||||
Isolation: sql.LevelSerializable, // strongest guarantee
|
||||
})
|
||||
```
|
||||
|
||||
| Level | Use when |
|
||||
| --- | --- |
|
||||
| `LevelReadCommitted` | Default — good for most operations |
|
||||
| `LevelRepeatableRead` | Need consistent reads within a transaction |
|
||||
| `LevelSerializable` | Financial operations, inventory, anything with strict consistency |
|
||||
|
||||
## SELECT FOR UPDATE — prevent race conditions
|
||||
|
||||
```go
|
||||
var balance int
|
||||
err := tx.GetContext(ctx, &balance, "SELECT balance FROM accounts WHERE id = $1 FOR UPDATE", accountID)
|
||||
// Row is locked until tx.Commit() or tx.Rollback()
|
||||
```
|
||||
|
||||
Use `FOR UPDATE` when you read a value, compute something from it, and then write it back. Without the lock, concurrent transactions can read stale data.
|
||||
|
||||
## Locking variants
|
||||
|
||||
| Clause | Effect |
|
||||
| --- | --- |
|
||||
| `FOR UPDATE` | Locks rows for write — other transactions block on same rows |
|
||||
| `FOR UPDATE NOWAIT` | Same, but fails immediately instead of waiting |
|
||||
| `FOR SHARE` | Locks rows for read — prevents writes but allows other reads |
|
||||
Reference in New Issue
Block a user