PG.L3.VACUUM_IN_TRANSACTION — VACUUM cannot run inside Laravel's transaction
- Category: safety
- Level: 3
- Confidence: deterministic
- Downtime class:
online - Stability: stable
- Suites: lint
- Applies to: PostgreSQL 18
PostgreSQL refuses VACUUM inside a transaction block, in every form: the plain VACUUM,
VACUUM ANALYZE and VACUUM FULL alike. Laravel wraps a migration's up() in a transaction by
default, so the statement does nothing and the migration fails on its first migrate with error
25001. Measured on PostgreSQL 18.0: BEGIN followed by VACUUM FULL orders failed with
ERROR: 25001: VACUUM cannot run inside a transaction block.
Flagged
// This body runs in the migration's default transaction, where VACUUM is refused:
DB::statement('VACUUM ANALYZE orders');
Preferred
// Set public $withinTransaction = false; on the migration class so the
// statement runs outside a transaction, as VACUUM requires:
DB::statement('VACUUM ANALYZE orders');
The fix is one line, public $withinTransaction = false; on the migration class. If the statements
beside the VACUUM need their transaction, as several ALTERs that must succeed together do, the
VACUUM moves into a migration of its own that runs after them in the same deploy.
What it costs once it runs
The plain VACUUM takes SHARE UPDATE EXCLUSIVE, which no read and no write waits behind.
VACUUM FULL rewrites the table under ACCESS EXCLUSIVE, which
PG.L2.VACUUM_FULL reports. A VACUUM FULL in a transaction carries
both findings, each about one thing.
When the context is unclear
The transaction context comes from the migration's own $withinTransaction. Where it cannot be
resolved, for example because an explicit transaction opens inside the migrator's, the finding is
undetermined rather than silent: the failure it would hide is an aborted deploy.
Sources
- PostgreSQL 18 —
VACUUM—VACUUMcannot be executed inside a transaction block
The fix material this rule carries
A finding from this rule carries a payload whose strategy is none: this rule has looked,
and there is no safe standard sequence. That is a conclusion rather than an omission — a
finding with no payload at all says only that nobody wrote one.
The payload is material for you or an agent to apply. SQLens writes no migration and runs no DDL. See the remediation payload for every field, the placeholder semantics, and the version rules a consumer has to follow.