MY.L3.ALGORITHM_LOCK_UNEXPRESSIBLE — An operation MySQL will not run online, in a statement that does not say so
- Category: safety
- Level: 3
- Confidence: deterministic
- Downtime class: derived — the operation's class from the online-DDL matrix, so a release gate reading the finding learns what deploying the statement costs
- 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.
The statement names neither ALGORITHM= nor LOCK=. Those two clauses are exactly what would
keep an InnoDB migration provably online. Without them, the statement takes whatever algorithm the
server picks — and you find out which one that was afterwards.
The rule reads the statement the migration sends, not how the migration was written. A raw
DB::statement() naming both and a builder modifier that emits one are the same answer to it.
What the schema builder can emit, by framework version
Laravel's MySQL grammar emits some of the clauses itself, through modifiers:
| Modifier | Emits | On | From Laravel |
|---|---|---|---|
->instant() | ALGORITHM=INSTANT | a column added, changed or dropped | 12.40.1 |
->lock('none') | LOCK=NONE (or the level you name) | a column, an index, a primary key, a foreign key | 12.46.0 |
->inplace() | ALGORITHM=INPLACE | an index, a primary key, a foreign key | 13.33.0 |
On a framework without the modifier, the grammar ignores it without a word: the migration looks
pinned and the statement is not. This rule keeps reporting such a statement, which is why it reads
the statement. Any other clause, such as ALGORITHM=INPLACE on a column or ALGORITHM=COPY
anywhere, still needs a raw statement, and so does any pair on an older framework.
What an explicit ALGORITHM actually buys
It does not make a copying operation online. It makes the server refuse.
ALGORITHM=INPLACE on an operation MySQL can only perform by copying fails with an error instead of
silently copying the table. The deploy therefore stops before it locks anything, rather than in
the middle of it — and that is the whole value. A reader who believes the clause makes the operation
cheap has been made more confident and no safer, which is why the finding says this in plain words.
Flagged
Schema::table('orders', fn (Blueprint $table) => $table->primary('id'));
Preferred
// Laravel 13.33.0 and later names both clauses itself:
// $table->primary('id')->inplace()->lock('none');
// An earlier framework cannot, so the statement says them directly, and naming them makes the
// server refuse rather than silently copy the table if it cannot do this in place.
DB::statement('ALTER TABLE orders ADD PRIMARY KEY (id), ALGORITHM=INPLACE, LOCK=NONE');
The comment is part of the fix, not decoration. Raw SQL in a migration is a deliberate exception here, and the next reader has to be able to tell a considered exception from a shortcut. On Laravel 13.33.0 and later the builder line in the comment emits the same statement, and it needs no such explanation.
When this rule fires, and when it says nothing
The trigger comes from the online-DDL matrix, never from a list written into the rule: the
operation's downtime class comes back as something other than online, and the statement carries
neither clause. A matrix revision therefore moves the rule with it, and the rule cannot disagree
with the rules that report those same operations.
It says nothing in three cases, and only one of them is about safety:
- the statement already names an algorithm or a lock level — the work is done;
- the operation is one MySQL runs online anyway — there is nothing to suggest, and a hint on every ordinary index build is how a rule gets switched off;
- the matrix could not settle the class, because it does not classify the operation or because its entry depends on a table fact no static reader can see.
That last silence means "no suggestion", not "safe". The safety verdict for those statements belongs to the rules that own them, and they report it. This rule only ever offers an improvement it can justify.
Sources
ALTER TABLE— ALGORITHM and LOCK clauses — MySQL 8.4- InnoDB online DDL operations — MySQL 8.4
MySqlGrammar— Laravel's schema grammar, where the modifiers become clauses
The fix material this rule carries
A finding from this rule carries machine-readable fix material. Which sequence depends on the statement:
algorithm_lock_hint— Name the algorithm and lock level the engine would otherwise pick for you.none— Looked at, and there is no safe standard sequence — a statement, not an absence.
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.