Skip to main content

MY.L1.DROP_COLUMN — Dropping a column on MySQL commits itself

  • Category: safety
  • Level: 1
  • Confidence: deterministic
  • Downtime class: derived per statement — see below
  • Stability: stable
  • Suites: lint
  • Applies to: MySQL 8.4

DROP COLUMN deletes the column and every value in it. down() can add the column back; it cannot bring back what was in it.

On MySQL there is no transaction either​

ALTER TABLE … DROP COLUMN is DDL, so it causes an implicit commit. The values are gone the instant the statement runs, whatever fails afterwards in the same migration.

PostgreSQL only marks the column dropped — no rewrite, the space returns later. MySQL has no such state, which is why the two engines get their own rule and their own sentence.

The downtime class is conditional, and that is deliberate​

This rule never names a class. It comes from the online-DDL matrix, where drop_column is INSTANT with lock: none — but only while none of its conditions applies:

conditionwhen it holdswhat it does
column_has_functional_indexa functional index covers the columntakes the drop off its instant path
column_has_indexany index covers the column, an ordinary KEY included, and InnoDB gives every foreign-key column onetakes the drop off its instant path; a pinned ALGORITHM=INSTANT is refused with error 1845 (measured on 8.4.10)
instant_row_version_limit_reachedthe table is out of instant row-version budgetthe statement is refused outright, not downgraded
row_format_compressedthe table uses ROW_FORMAT=COMPRESSEDforces a rebuild
table_has_fulltext_indexthe table carries a FULLTEXT indexforces a rebuild

A run that decided nothing about the live table cannot see which of those holds, so the class resolves to undetermined rather than to the cheap case. The finding says so in downtime_undetermined, with the reason online_ddl_condition_undecidable and a condition such as column_has_functional_index as its detail, and the report counts it in summary.downtime_undetermined. Reporting instant from a static read would be a promise the server has not made — and the row-version condition does not merely make the statement slower, it makes the server reject it.

Not transferable to MariaDB​

This rule reasons about MySQL 8.4 semantics — specifically that DDL causes an implicit commit there. MariaDB shares Laravel's driver key and the wire protocol, not those semantics, so the same sentence applied to it would be confident, specific and about another product. SQLens refuses that connection rather than guessing.

Flagged​

$table->dropColumn('legacy_ref');

Preferred​

// Stop writing and reading the column in a shipped release first, then drop it
// later behind #[SqlensAllowDestructive]. On MySQL the drop is DDL, so it commits
// itself the moment it runs - there is no transaction to take it back.

What the rule does not claim​

It does not know whether anything still reads the column — that lives in your application, not in the migration. It says the deletion is irreversible, and that on this engine the surrounding transaction will not save you.

Sources​

The fix material this rule carries​

A finding from this rule carries machine-readable fix material, using this sequence:

  • deploy_window_drop — Drop across two deploy windows so a rollback in between still finds what it needs.

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.