Attempt instant DDL if supported
This commit is contained in:
parent
9b3fa793ac
commit
1be6a4c082
@ -101,6 +101,7 @@ type MigrationContext struct {
|
||||
AliyunRDS bool
|
||||
GoogleCloudPlatform bool
|
||||
AzureMySQL bool
|
||||
AttemptInstantDDL bool
|
||||
|
||||
config ContextConfig
|
||||
configMutex *sync.Mutex
|
||||
|
@ -67,6 +67,8 @@ func main() {
|
||||
flag.StringVar(&migrationContext.DatabaseName, "database", "", "database name (mandatory)")
|
||||
flag.StringVar(&migrationContext.OriginalTableName, "table", "", "table name (mandatory)")
|
||||
flag.StringVar(&migrationContext.AlterStatement, "alter", "", "alter statement (mandatory)")
|
||||
flag.BoolVar(&migrationContext.AttemptInstantDDL, "attempt-instant-ddl", true, "Attempt to use instant DDL for this migration first.")
|
||||
|
||||
flag.BoolVar(&migrationContext.CountTableRows, "exact-rowcount", false, "actually count table rows as opposed to estimate them (results in more accurate progress estimation)")
|
||||
flag.BoolVar(&migrationContext.ConcurrentCountTableRows, "concurrent-rowcount", true, "(with --exact-rowcount), when true (default): count rows after row-copy begins, concurrently, and adjust row estimate later on; when false: first count rows, then start row copy")
|
||||
flag.BoolVar(&migrationContext.AllowedRunningOnMaster, "allow-on-master", false, "allow this migration to run directly on master. Preferably it would run on a replica")
|
||||
|
@ -188,6 +188,54 @@ func (this *Applier) ValidateOrDropExistingTables() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttemptInstantDDL attempts to use instant DDL (from MySQL 8.0, and earlier in Aurora and some others.)
|
||||
// to apply the ALTER statement immediately. If it errors, the original
|
||||
// gh-ost algorithm can be used. However, if it's successful -- a lot
|
||||
// of time can potentially be saved. Instant operations include:
|
||||
// - Adding a column
|
||||
// - Dropping a column
|
||||
// - Dropping an index
|
||||
// - Extending a varchar column
|
||||
// It is safer to attempt the change than try and parse the DDL, since
|
||||
// there might be specifics about the table which make it not possible to apply instantly.
|
||||
func (this *Applier) AttemptInstantDDL() error {
|
||||
|
||||
query := fmt.Sprintf(`ALTER /* gh-ost */ TABLE %s.%s %s, ALGORITHM=INSTANT`,
|
||||
sql.EscapeName(this.migrationContext.DatabaseName),
|
||||
sql.EscapeName(this.migrationContext.OriginalTableName),
|
||||
this.migrationContext.AlterStatementOptions,
|
||||
)
|
||||
this.migrationContext.Log.Infof("INSTANT DDL Query is: %s", query)
|
||||
|
||||
err := func() error {
|
||||
tx, err := this.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
sessionQuery := fmt.Sprintf(`SET SESSION time_zone = '%s'`, this.migrationContext.ApplierTimeZone)
|
||||
sessionQuery = fmt.Sprintf("%s, %s", sessionQuery, this.generateSqlModeQuery())
|
||||
|
||||
if _, err := tx.Exec(sessionQuery); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(query); err != nil {
|
||||
this.migrationContext.Log.Infof("INSTANT DDL failed: %s", err)
|
||||
return err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
// Neither SET SESSION nor ALTER are really transactional, so strictly speaking
|
||||
// there's no need to commit; but let's do this the legit way anyway.
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
// CreateGhostTable creates the ghost table on the applier host
|
||||
func (this *Applier) CreateGhostTable() error {
|
||||
query := fmt.Sprintf(`create /* gh-ost */ table %s.%s like %s.%s`,
|
||||
|
@ -351,12 +351,26 @@ func (this *Migrator) Migrate() (err error) {
|
||||
if err := this.initiateInspector(); err != nil {
|
||||
return err
|
||||
}
|
||||
// In MySQL 8.0 (and possibly earlier) some DDL statements can be applied instantly.
|
||||
// As just a metadata change. We can't detect this unless we attempt the statement
|
||||
// (i.e. there is no explain for DDL).
|
||||
if this.migrationContext.AttemptInstantDDL {
|
||||
this.migrationContext.Log.Infof("Attempting to execute ALTER TABLE as INSTANT DDL")
|
||||
if err := this.attemptInstantDDL(); err == nil {
|
||||
this.migrationContext.Log.Infof("Success! Table %s.%s migrated instantly", sql.EscapeName(this.migrationContext.DatabaseName), sql.EscapeName(this.migrationContext.OriginalTableName))
|
||||
return nil
|
||||
} else {
|
||||
this.migrationContext.Log.Infof("INSTANT DDL failed, will proceed with original algorithm: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := this.initiateStreaming(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := this.initiateApplier(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := this.createFlagFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -734,6 +748,17 @@ func (this *Migrator) initiateServer() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// attemptInstantDDL tries to apply the DDL statement directly to the table
|
||||
// using a ALGORITHM=INSTANT assertion. If this fails, it will return an error,
|
||||
// in which case the original algorithm should be used.
|
||||
func (this *Migrator) attemptInstantDDL() (err error) {
|
||||
this.applier = NewApplier(this.migrationContext)
|
||||
if err := this.applier.InitDBConnections(); err != nil {
|
||||
return err
|
||||
}
|
||||
return this.applier.AttemptInstantDDL()
|
||||
}
|
||||
|
||||
// initiateInspector connects, validates and inspects the "inspector" server.
|
||||
// The "inspector" server is typically a replica; it is where we issue some
|
||||
// queries such as:
|
||||
|
Loading…
Reference in New Issue
Block a user