Skip to content
OpenAgentsbeta
text
1# SQL Safety Rules
2
3## Writes
4
51. **Never write an UPDATE or DELETE without a WHERE clause.** If you truly mean every
6 row, say so explicitly in a comment on the line above and get it approved.
72. **Count before you write.** Run the SELECT with the same WHERE first and check the
8 row count against what you expect. A number two orders of magnitude off means the
9 predicate is wrong.
103. **Wrap multi-statement writes in a transaction**, and know whether your DDL is
11 transactional. In Postgres it mostly is. In MySQL it mostly is not.
124. **Bound every bulk write.** Delete or update in batches with a LIMIT and a loop, not
13 in one statement that locks a million rows.
145. **Never write to production from an interactive session** when a reviewed migration
15 would do.
16
17## Reads
18
196. **Every query on a large table needs an index-supported predicate.** Check the plan.
20 A sequential scan in a hot path is a future incident.
217. **No SELECT * in application code.** It breaks silently when a column is added and
22 ships columns you do not need over the wire.
238. **Paginate by a stable key, not by OFFSET.** OFFSET on a large table gets slower the
24 deeper it goes, and skips rows when data shifts underneath it.
25
26## Migrations
27
289. **Every migration needs a down, or an explicit note that it is irreversible** and
29 why that is acceptable.
3010. **Adding a NOT NULL column with a default rewrites the table** on older engines.
31 Add nullable, backfill in batches, then add the constraint.
3211. **Never rename or drop a column in the same deploy that stops using it.** Ship the
33 code that ignores it, deploy, then drop in a later migration. Otherwise the old
34 running version breaks the instant the migration lands.
3512. **Create indexes concurrently** where the engine supports it. A plain CREATE INDEX
36 takes a write lock for the duration.
3713. **Backfill outside the migration**, in batches, with a resumable cursor. A backfill
38 inside a migration holds a lock for as long as it runs.
39
40## Injection and identity
41
4214. **Parameterize every value. Always.** String interpolation into SQL is the bug, even
43 when the input "cannot" contain a quote.
4415. **Identifiers cannot be parameterized**, so validate table and column names against
45 an allowlist rather than passing them through.
46
47## Before running anything against production
48
4916. **Know the row count** the statement will touch.
5017. **Know the lock** it takes and for how long.
5118. **Know the rollback.** If it is a restore from backup, know how long the restore
52 takes and say so out loud before running.
53

Keyboard shortcuts

Focus search
/
Go to Explore
ge
Go to Home
gh
Go to Tags
gt
Go to Collections
gc
Show this help
?
Close suggestions or this dialog
Esc