Skip to main content

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​

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.