Skip to main content

Locks and hints

A lock holds rows until the transaction ends, so locks belong inside a Tx.

Row locks​

err := db.Tx(ctx, func(tx *AppDB) error {
user, err := tx.Users.Where(ant.Eq(&U.ID, id)).ForUpdate().Single(ctx)
if err != nil {
return err
}

user.Age++

return tx.SaveChanges(ctx)
})
MethodMeans
ForUpdate()Lock for writing. Others wait
ForShare()Lock against writers. Other readers are fine
NoWait()Fail at once on a locked row
SkipLocked()Leave locked rows out

ForUpdate is always sent to the primary, even with replicas.

A job queue​

SkipLocked lets each worker claim a row nobody else holds, so no job runs twice.

err := db.Tx(ctx, func(tx *AppDB) error {
job, err := tx.Jobs.OrderBy(&J.ID).ForUpdate().SkipLocked().First(ctx)
if err != nil {
return err
}

job.Done = true

return tx.SaveChanges(ctx)
})

Row locks depend on the database: see databases.

Optimizer hints​

Hint writes an optimizer hint at the start of a statement. PostgreSQL ignores hints unless you install the pg_hint_plan extension. A hint tends to outlive the reason for it, so prefer fixing the query.

users, err := db.Users.Query().Hint("SeqScan(users)").All(ctx)

Query comments​

Tag adds a comment to the SQL, so a slow query is easy to spot in database logs. Neither Tag nor Hint can break out of its comment.

users, err := db.Users.Where(ant.Gt(&U.Age, 18)).Tag("dashboard: adults").All(ctx)