vitessio/vitess · error
AfterSchema differs
Error message
AfterSchema differs
What it means
After executing the DDL, ApplySchemaChange re-reads the schema and computes diffs against the caller-supplied expected AfterSchema. Mismatches are logged; unless change.Force is set, the call returns 'AfterSchema differs', signaling the DDL's result did not match expectations.
Source
Thrown at go/vt/mysqlctl/schema.go:573
}
// get AfterSchema
afterSchema, err := mysqld.GetSchema(ctx, dbName, req)
if err != nil {
return nil, err
}
// compare to the provided AfterSchema
if change.AfterSchema != nil {
schemaDiffs := tmutils.DiffSchemaToArray("actual", afterSchema, "expected", change.AfterSchema)
if len(schemaDiffs) > 0 {
for _, msg := range schemaDiffs {
log.Warn(fmt.Sprintf("AfterSchema differs: %v", msg))
}
if change.Force {
log.Warn("AfterSchema differs, not reporting error")
} else {
return nil, errors.New("AfterSchema differs")
}
}
}
return &tabletmanagerdatapb.SchemaChangeResult{BeforeSchema: beforeSchema, AfterSchema: afterSchema}, nil
}
// GetPrimaryKeyEquivalentColumns can be used if the table has
// no defined PRIMARY KEY. It will return the columns in a
// viable PRIMARY KEY equivalent (PKE) -- a NON-NULL UNIQUE
// KEY -- along with that index's name in the specified table.
// When multiple PKE indexes are available it will attempt to
// choose the most efficient one based on the column data types
// and the number of columns in the index. See here for the data
// type storage sizes:
//
// https://dev.mysql.com/doc/refman/en/storage-requirements.html
//View on GitHub (pinned to 01a25a7d17)
Solutions
- Wait for the online-DDL migration to complete (ShowMigrationStatus / wait until complete) before asserting AfterSchema
- Generate AfterSchema programmatically (apply the diff to BeforeSchema) rather than hand-writing it
- Inspect logged schemaDiffs messages and correct the expected AfterSchema accordingly
- Use Force=true only when the observed after-state is verified acceptable
Example fix
// before
res, err := tm.ApplySchemaChange(ctx, &tms.SchemaChange{SQL: sql, AfterSchema: handWritten})
// after
status, _ := tm.ShowMigrationStatus(ctx, uuid) // wait for online DDL completion
res, err := tm.ApplySchemaChange(ctx, &tms.SchemaChange{SQL: sql, AfterSchema: computedFromBeforePlusDiff}) Defensive patterns
Strategy: try-catch
Validate before calling
// wait for online DDL completion before verifying after-schema
status, err := tm.ShowMigrationStatus(ctx, uuid)
if err != nil || !status.IsComplete() {
return fmt.Errorf("migration not complete; cannot assert AfterSchema yet")
} Try / catch
res, err := tm.ApplySchemaChange(ctx, change)
if err != nil && strings.Contains(err.Error(), "AfterSchema differs") {
log.Warn("post-DDL schema differs from expectation; inspect logged schemaDiffs")
// re-fetch and compare, or set Force only if diff reviewed
} Prevention
- Compute AfterSchema from BeforeSchema plus the DDL's expected effect, not by hand
- Wait for online-DDL cutover before verification
- Review logged diff messages before using Force=true
- Pin MySQL version/flavor assumptions when asserting schema attributes
When it happens
Trigger: Calling ApplySchemaChange with AfterSchema set in the SchemaChange, and the post-DDL schema differs — e.g. online DDL migration still in progress, triggers/character-set differences not captured, DDL executed but with different effects than predicted.
Common situations: CHANGE/REVERT workflows where the expected after-state was hand-written; online DDL (gh-ost/pt-osc) still cutover-ing so the visible schema hasn't changed yet; MySQL version differences producing extra/different schema attributes.
Related errors
- BeforeSchema differs
- must be non-negative
- no app indicated
- schemas differ on table %v: %s: %v differs from: %s: %v
- Unknown online DDL strategy: '%v'
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/05d5c0741f9c6aa8.
Report an issue: GitHub.