{"record":{"id":"05d5c0741f9c6aa8","repo":"vitessio/vitess","slug":"afterschema-differs","errorCode":null,"errorMessage":"AfterSchema differs","messagePattern":"AfterSchema differs","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/mysqlctl/schema.go","lineNumber":573,"sourceCode":"\t}\n\n\t// get AfterSchema\n\tafterSchema, err := mysqld.GetSchema(ctx, dbName, req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// compare to the provided AfterSchema\n\tif change.AfterSchema != nil {\n\t\tschemaDiffs := tmutils.DiffSchemaToArray(\"actual\", afterSchema, \"expected\", change.AfterSchema)\n\t\tif len(schemaDiffs) > 0 {\n\t\t\tfor _, msg := range schemaDiffs {\n\t\t\t\tlog.Warn(fmt.Sprintf(\"AfterSchema differs: %v\", msg))\n\t\t\t}\n\t\t\tif change.Force {\n\t\t\t\tlog.Warn(\"AfterSchema differs, not reporting error\")\n\t\t\t} else {\n\t\t\t\treturn nil, errors.New(\"AfterSchema differs\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &tabletmanagerdatapb.SchemaChangeResult{BeforeSchema: beforeSchema, AfterSchema: afterSchema}, nil\n}\n\n// GetPrimaryKeyEquivalentColumns can be used if the table has\n// no defined PRIMARY KEY. It will return the columns in a\n// viable PRIMARY KEY equivalent (PKE) -- a NON-NULL UNIQUE\n// KEY -- along with that index's name in the specified table.\n// When multiple PKE indexes are available it will attempt to\n// choose the most efficient one based on the column data types\n// and the number of columns in the index. See here for the data\n// type storage sizes:\n//\n//\thttps://dev.mysql.com/doc/refman/en/storage-requirements.html\n//","sourceCodeStart":555,"sourceCodeEnd":591,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/mysqlctl/schema.go#L555-L591","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nres, err := tm.ApplySchemaChange(ctx, &tms.SchemaChange{SQL: sql, AfterSchema: handWritten})\n// after\nstatus, _ := tm.ShowMigrationStatus(ctx, uuid) // wait for online DDL completion\nres, err := tm.ApplySchemaChange(ctx, &tms.SchemaChange{SQL: sql, AfterSchema: computedFromBeforePlusDiff})","handlingStrategy":"try-catch","validationCode":"// wait for online DDL completion before verifying after-schema\nstatus, err := tm.ShowMigrationStatus(ctx, uuid)\nif err != nil || !status.IsComplete() {\n    return fmt.Errorf(\"migration not complete; cannot assert AfterSchema yet\")\n}","typeGuard":null,"tryCatchPattern":"res, err := tm.ApplySchemaChange(ctx, change)\nif err != nil && strings.Contains(err.Error(), \"AfterSchema differs\") {\n    log.Warn(\"post-DDL schema differs from expectation; inspect logged schemaDiffs\")\n    // re-fetch and compare, or set Force only if diff reviewed\n}","preventionTips":["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"],"tags":["schema","ddl","validation","vttablet"],"backgroundTag":"schema-drift-detected","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}