vitessio/vitess · error
BeforeSchema differs
Error message
BeforeSchema differs
What it means
ApplySchemaChange optionally captures the schema before executing the DDL and compares it to the caller-supplied expected before-schema (change.BeforeSchema). If they differ, the DDL is refused with 'BeforeSchema differs' unless change.Force is set, protecting against applying a change on top of an unexpected schema state.
Source
Thrown at go/vt/mysqlctl/schema.go:527
// let's see if the schema was already applied
if change.AfterSchema != nil {
schemaDiffs = tmutils.DiffSchemaToArray("actual", beforeSchema, "expected", change.AfterSchema)
if len(schemaDiffs) == 0 {
// no diff between the schema we expect
// after the change and the current
// schema, we already applied it
return &tabletmanagerdatapb.SchemaChangeResult{
BeforeSchema: beforeSchema,
AfterSchema: beforeSchema,
}, nil
}
}
if change.Force {
log.Warn("BeforeSchema differs, applying anyway")
} else {
return nil, errors.New("BeforeSchema differs")
}
}
}
sql := change.SQL
// The session used is closed after applying the schema change so we do not need
// to worry about saving and restoring the session state here
if change.SQLMode != "" {
sql = fmt.Sprintf("SET @@session.sql_mode='%s';\n%s", change.SQLMode, sql)
}
if !change.AllowReplication {
sql = "SET sql_log_bin = 0;\n" + sql
}
if change.DisableForeignKeyChecks {
sql = "SET foreign_key_checks = 0;\n" + sqlView on GitHub (pinned to 01a25a7d17)
Solutions
- Re-fetch the current schema (GetSchema) and supply it as BeforeSchema, then retry
- Set change.Force = true only if the diff is understood and acceptable (a warning is logged instead)
- Investigate schema drift: compare the diff reported in logs and reconcile (e.g. re-run online DDL or fix manually)
- Ensure only one schema-change workflow targets the tablet at a time (check workflow/vreplication state)
Example fix
// before
res, err := tm.ApplySchemaChange(ctx, &tms.SchemaChange{SQL: sql, BeforeSchema: staleSchema})
// after
before, err := tm.GetSchema(ctx, "/", nil, true)
res, err := tm.ApplySchemaChange(ctx, &tms.SchemaChange{SQL: sql, BeforeSchema: before}) Defensive patterns
Strategy: retry
Validate before calling
before, err := tm.GetSchema(ctx, "/", nil, true)
if err != nil {
return err
}
if !schemaEqual(before, expectedBefore) {
return fmt.Errorf("tablet schema drifted from expectation; refresh before applying")
} Type guard
func schemasMatch(a, b *tabletmanagerdatapb.SchemaDefinition) bool {
return reflect.DeepEqual(a.GetTableDefinitions(), b.GetTableDefinitions())
} Try / catch
res, err := tm.ApplySchemaChange(ctx, change)
if err != nil && strings.Contains(err.Error(), "BeforeSchema differs") {
fresh, ferr := tm.GetSchema(ctx, "/", nil, true)
if ferr != nil { return ferr }
change.BeforeSchema = fresh
res, err = tm.ApplySchemaChange(ctx, change) // retry once with fresh snapshot
} Prevention
- Always build BeforeSchema from a live GetSchema immediately before applying
- Serialize schema changes through a single workflow/controller
- Diff and reconcile drift before scheduling DDL
- Use Force only after reviewing the logged diff
When it happens
Trigger: Calling ApplySchemaChange with a non-nil BeforeSchema in tabletmanagerdatapb.SchemaChange whose contents don't match the tablet's actual current schema; concurrent DDL changed the schema between the caller's snapshot and execution; caller built BeforeSchema against a different tablet/keyspace.
Common situations: Race between two DDL workflows; applying a change generated on a replica to a tablet whose schema drifted (manual changes, failed prior migration); stale automation cache of the schema.
Related errors
- AfterSchema 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/91af7d8a6d95f565.
Report an issue: GitHub.