Row-Level Locks
FOR UPDATE, SKIP LOCKED, and queue patterns. Production-oriented PostgreSQL guidance.
Recipe
BEGIN;
SELECT id FROM app.jobs WHERE status = 'queued' ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1;
COMMIT;When to reach for this: You implement or review SQL using this concept in PostgreSQL 18.
Working Example
CREATE TABLE IF NOT EXISTS app.jobs (id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, status text NOT NULL);
INSERT INTO app.jobs (status) VALUES ('queued');What this demonstrates:
- Idiomatic PostgreSQL 18 SQL
- Patterns suitable for EXPLAIN review
- Safe defaults for OLTP workloads
Deep Dive
Mechanism
- Parser builds query tree.
- Executor runs plan nodes.
- MVCC provides snapshot isolation per statement or transaction.
Practice
- Keep examples readable.
- Test at realistic row counts.
Gotchas
- Long transactions - Block vacuum and increase bloat.. Fix: Commit quickly; set timeouts.
- Missing indexes - Seq scans under load.. Fix: Index join and filter columns.
- Implicit locks - Surprise blocking.. Fix: Use explicit FOR UPDATE when needed.
- Wrong isolation assumption - Anomalies under concurrent writes.. Fix: Pick level deliberately.
- Untested migrations - DDL lock or invalid objects.. Fix: Stage migrations with production-like data.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Application-level logic | Simple CRUD | Integrity spread across services |
| Materialized view | Heavy repeated reads | Refresh complexity |
| External stream processor | Cross-system events | Extra operational surface |
FAQs
Does this apply to replicas?
Read-only replicas execute SELECT; writes go to primary.
What about PgBouncer?
Transaction pooling affects session-level features; use session mode when needed.
Related
- Transactions Basics - BEGIN/COMMIT
Stack versions: This page was written for PostgreSQL 18.4 (stable 18, maintenance 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x, and PostGIS 3.5+.