github.com/pingcap/br@v5.3.0-alpha.0.20220125034240-ec59c7b6ce30+incompatible/pkg/restore/systable_restore.go (about) 1 // Copyright 2020 PingCAP, Inc. Licensed under Apache-2.0. 2 3 package restore 4 5 import ( 6 "context" 7 "fmt" 8 9 "github.com/pingcap/errors" 10 "github.com/pingcap/log" 11 "github.com/pingcap/parser/model" 12 "github.com/pingcap/parser/mysql" 13 filter "github.com/pingcap/tidb-tools/pkg/table-filter" 14 "go.uber.org/multierr" 15 "go.uber.org/zap" 16 17 berrors "github.com/pingcap/br/pkg/errors" 18 "github.com/pingcap/br/pkg/logutil" 19 "github.com/pingcap/br/pkg/utils" 20 ) 21 22 var statsTables = map[string]struct{}{ 23 "stats_buckets": {}, 24 "stats_extended": {}, 25 "stats_feedback": {}, 26 "stats_fm_sketch": {}, 27 "stats_histograms": {}, 28 "stats_meta": {}, 29 "stats_top_n": {}, 30 } 31 32 var unRecoverableTable = map[string]struct{}{ 33 // some variables in tidb (e.g. gc_safe_point) cannot be recovered. 34 "tidb": {}, 35 "global_variables": {}, 36 37 // all user related tables cannot be recovered for now. 38 "columns_priv": {}, 39 "db": {}, 40 "default_roles": {}, 41 "global_grants": {}, 42 "global_priv": {}, 43 "role_edges": {}, 44 "tables_priv": {}, 45 "user": {}, 46 47 // gc info don't need to recover. 48 "gc_delete_range": {}, 49 "gc_delete_range_done": {}, 50 51 // schema_index_usage has table id need to be rewrite. 52 "schema_index_usage": {}, 53 } 54 55 func isUnrecoverableTable(tableName string) bool { 56 _, ok := unRecoverableTable[tableName] 57 return ok 58 } 59 60 func isStatsTable(tableName string) bool { 61 _, ok := statsTables[tableName] 62 return ok 63 } 64 65 // RestoreSystemSchemas restores the system schema(i.e. the `mysql` schema). 66 // Detail see https://github.com/pingcap/br/issues/679#issuecomment-762592254. 67 func (rc *Client) RestoreSystemSchemas(ctx context.Context, f filter.Filter) { 68 sysDB := mysql.SystemDB 69 70 temporaryDB := utils.TemporaryDBName(sysDB) 71 defer rc.cleanTemporaryDatabase(ctx, sysDB) 72 73 if !f.MatchSchema(sysDB) { 74 log.Debug("system database filtered out", zap.String("database", sysDB)) 75 return 76 } 77 originDatabase, ok := rc.databases[temporaryDB.O] 78 if !ok { 79 log.Info("system database not backed up, skipping", zap.String("database", sysDB)) 80 return 81 } 82 db, ok := rc.getDatabaseByName(sysDB) 83 if !ok { 84 // Or should we create the database here? 85 log.Warn("target database not exist, aborting", zap.String("database", sysDB)) 86 return 87 } 88 89 tablesRestored := make([]string, 0, len(originDatabase.Tables)) 90 for _, table := range originDatabase.Tables { 91 tableName := table.Info.Name 92 if f.MatchTable(sysDB, tableName.O) { 93 if err := rc.replaceTemporaryTableToSystable(ctx, tableName.L, db); err != nil { 94 log.Warn("error during merging temporary tables into system tables", 95 logutil.ShortError(err), 96 zap.Stringer("table", tableName), 97 ) 98 } 99 tablesRestored = append(tablesRestored, tableName.L) 100 } 101 } 102 if err := rc.afterSystemTablesReplaced(ctx, tablesRestored); err != nil { 103 for _, e := range multierr.Errors(err) { 104 log.Warn("error during reconfigurating the system tables", zap.String("database", sysDB), logutil.ShortError(e)) 105 } 106 } 107 } 108 109 // database is a record of a database. 110 // For fast querying whether a table exists and the temporary database of it. 111 type database struct { 112 ExistingTables map[string]*model.TableInfo 113 Name model.CIStr 114 TemporaryName model.CIStr 115 } 116 117 // getDatabaseByName make a record of a database from info schema by its name. 118 func (rc *Client) getDatabaseByName(name string) (*database, bool) { 119 infoSchema := rc.dom.InfoSchema() 120 schema, ok := infoSchema.SchemaByName(model.NewCIStr(name)) 121 if !ok { 122 return nil, false 123 } 124 db := &database{ 125 ExistingTables: map[string]*model.TableInfo{}, 126 Name: model.NewCIStr(name), 127 TemporaryName: utils.TemporaryDBName(name), 128 } 129 for _, t := range schema.Tables { 130 db.ExistingTables[t.Name.L] = t 131 } 132 return db, true 133 } 134 135 // afterSystemTablesReplaced do some extra work for special system tables. 136 // e.g. after inserting to the table mysql.user, we must execute `FLUSH PRIVILEGES` to allow it take effect. 137 func (rc *Client) afterSystemTablesReplaced(ctx context.Context, tables []string) error { 138 var err error 139 for _, table := range tables { 140 switch { 141 case table == "user": 142 // We cannot execute `rc.dom.NotifyUpdatePrivilege` here, because there isn't 143 // sessionctx.Context provided by the glue. 144 // TODO: update the glue type and allow we retrive a session context from it. 145 err = multierr.Append(err, errors.Annotatef(berrors.ErrUnsupportedSystemTable, 146 "restored user info may not take effect, until you should execute `FLUSH PRIVILEGES` manually")) 147 } 148 } 149 return err 150 } 151 152 // replaceTemporaryTableToSystable replaces the temporary table to real system table. 153 func (rc *Client) replaceTemporaryTableToSystable(ctx context.Context, tableName string, db *database) error { 154 execSQL := func(sql string) error { 155 // SQLs here only contain table name and database name, seems it is no need to redact them. 156 if err := rc.db.se.Execute(ctx, sql); err != nil { 157 log.Warn("failed to execute SQL restore system database", 158 zap.String("table", tableName), 159 zap.Stringer("database", db.Name), 160 zap.String("sql", sql), 161 zap.Error(err), 162 ) 163 return berrors.ErrUnknown.Wrap(err).GenWithStack("failed to execute %s", sql) 164 } 165 log.Info("successfully restore system database", 166 zap.String("table", tableName), 167 zap.Stringer("database", db.Name), 168 zap.String("sql", sql), 169 ) 170 return nil 171 } 172 173 // The newly created tables have different table IDs with original tables, 174 // hence the old statistics are invalid. 175 // 176 // TODO: 177 // 1 ) Rewrite the table IDs via `UPDATE _temporary_mysql.stats_xxx SET table_id = new_table_id WHERE table_id = old_table_id` 178 // BEFORE replacing into and then execute `rc.statsHandler.Update(rc.dom.InfoSchema())`. 179 // 1.5 ) (Optional) The UPDATE statement sometimes costs, the whole system tables restore step can be place into the restore pipeline. 180 // 2 ) Deprecate the origin interface for backing up statistics. 181 if isStatsTable(tableName) { 182 return berrors.ErrUnsupportedSystemTable.GenWithStack("restoring stats via `mysql` schema isn't support yet: " + 183 "the table ID is out-of-date and may corrupt existing statistics") 184 } 185 186 if isUnrecoverableTable(tableName) { 187 return berrors.ErrUnsupportedSystemTable.GenWithStack("restoring unsupported `mysql` schema table") 188 } 189 190 if db.ExistingTables[tableName] != nil { 191 log.Info("table existing, using replace into for restore", 192 zap.String("table", tableName), 193 zap.Stringer("schema", db.Name)) 194 replaceIntoSQL := fmt.Sprintf("REPLACE INTO %s SELECT * FROM %s;", 195 utils.EncloseDBAndTable(db.Name.L, tableName), 196 utils.EncloseDBAndTable(db.TemporaryName.L, tableName)) 197 return execSQL(replaceIntoSQL) 198 } 199 200 renameSQL := fmt.Sprintf("RENAME TABLE %s TO %s;", 201 utils.EncloseDBAndTable(db.TemporaryName.L, tableName), 202 utils.EncloseDBAndTable(db.Name.L, tableName), 203 ) 204 return execSQL(renameSQL) 205 } 206 207 func (rc *Client) cleanTemporaryDatabase(ctx context.Context, originDB string) { 208 database := utils.TemporaryDBName(originDB) 209 log.Debug("dropping temporary database", zap.Stringer("database", database)) 210 sql := fmt.Sprintf("DROP DATABASE IF EXISTS %s", utils.EncloseName(database.L)) 211 if err := rc.db.se.Execute(ctx, sql); err != nil { 212 logutil.WarnTerm("failed to drop temporary database, it should be dropped manually", 213 zap.Stringer("database", database), 214 logutil.ShortError(err), 215 ) 216 } 217 }