MY.L1.TRUNCATE — TRUNCATE in a migration, where no transaction takes it back
- Category: safety
- Level: 1
- Confidence: deterministic
- Downtime class:
rewrite - Stability: stable
- Suites: lint
- Applies to: MySQL 8.4
- Not transferable to MariaDB: this page describes MySQL 8.4 behavior. MariaDB answers the same driver and does not share these semantics, so SQLens refuses it outright rather than reasoning about it — see drivers/unsupported.
TRUNCATE in a migration's up() empties the table with no confirmation and no predicate to limit
it. On MySQL there is also no way back.
TRUNCATE is DDL here, so it causes an implicit commit: any open transaction is committed
before the statement runs. The rows are gone the instant it executes — whatever fails later in the
same migration — and down() is the only path back, one that cannot restore data.
Why this is a MySQL rule of its own
The destruction is the same on PostgreSQL. The recovery is not, and the recovery is what a reader needs.
On PostgreSQL a TRUNCATE inside a transaction rolls back with it, so a migration that fails after
one can still leave the table intact; the PostgreSQL rule therefore leads with the ACCESS EXCLUSIVE lock it takes. On MySQL that consolation does not exist. A rule that reused the
PostgreSQL sentence here would tell a reader their transaction protects them — making them more
confident and no safer, which is worse than saying nothing.
Flagged
DB::statement('TRUNCATE TABLE sessions');
Preferred
// Bounded batches: reversible while it runs, and no implicit commit throws away
// the transaction you thought you were in.
DB::statement('DELETE FROM sessions WHERE last_activity < UNIX_TIMESTAMP(NOW() - INTERVAL 30 DAY) LIMIT 1000');
If you genuinely need the whole table emptied and the rows recoverable, take a backup first.
When the loss is intended
Annotate the migration with #[SqlensAllowDestructive] and a reason. That does not hide the
finding: it becomes a named, visible suppression with the reason shown, so the loss is recorded
rather than silenced.
When the rule stays quiet
A table this same migration created holds no data a deploy would miss, so truncating a helper table you just built reports nothing.
Why rewrite and not online
MySQL's TRUNCATE is fast — it drops and recreates the table rather than deleting rows one by one.
The downtime axis models what happens to the table, not how long it takes, and a statement that
discards every row is not one a release gate treating rewrite as "look at this" is wrong to pause
on. The class comes from the package's non-DDL derivation rather than from the online-DDL matrix,
which describes schema changes and has nothing to say about storage replacement.
Sources
TRUNCATE TABLE— MySQL 8.4- Statements that cause an implicit commit — MySQL 8.4
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.