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  }