github.com/pingcap/br@v5.3.0-alpha.0.20220125034240-ec59c7b6ce30+incompatible/pkg/lightning/restore/tidb.go (about)

     1  // Copyright 2019 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package restore
    15  
    16  import (
    17  	"context"
    18  	"database/sql"
    19  	"fmt"
    20  	"strconv"
    21  	"strings"
    22  
    23  	tmysql "github.com/go-sql-driver/mysql"
    24  	"github.com/pingcap/errors"
    25  	"github.com/pingcap/parser"
    26  	"github.com/pingcap/parser/ast"
    27  	"github.com/pingcap/parser/format"
    28  	"github.com/pingcap/parser/model"
    29  	"github.com/pingcap/parser/mysql"
    30  	"github.com/pingcap/parser/terror"
    31  	"go.uber.org/zap"
    32  
    33  	"github.com/pingcap/br/pkg/lightning/checkpoints"
    34  	"github.com/pingcap/br/pkg/lightning/common"
    35  	"github.com/pingcap/br/pkg/lightning/config"
    36  	"github.com/pingcap/br/pkg/lightning/glue"
    37  	"github.com/pingcap/br/pkg/lightning/log"
    38  	"github.com/pingcap/br/pkg/lightning/metric"
    39  	"github.com/pingcap/br/pkg/lightning/mydump"
    40  )
    41  
    42  // defaultImportantVariables is used in ObtainImportantVariables to retrieve the system
    43  // variables from downstream which may affect KV encode result. The values record the default
    44  // values if missing.
    45  var defaultImportantVariables = map[string]string{
    46  	"tidb_row_format_version": "1",
    47  	"max_allowed_packet":      "67108864",
    48  	"div_precision_increment": "4",
    49  	"time_zone":               "SYSTEM",
    50  	"lc_time_names":           "en_US",
    51  	"default_week_format":     "0",
    52  	"block_encryption_mode":   "aes-128-ecb",
    53  	"group_concat_max_len":    "1024",
    54  }
    55  
    56  type TiDBManager struct {
    57  	db     *sql.DB
    58  	parser *parser.Parser
    59  }
    60  
    61  // getSQLErrCode returns error code if err is a mysql error
    62  func getSQLErrCode(err error) (terror.ErrCode, bool) {
    63  	mysqlErr, ok := errors.Cause(err).(*tmysql.MySQLError)
    64  	if !ok {
    65  		return -1, false
    66  	}
    67  
    68  	return terror.ErrCode(mysqlErr.Number), true
    69  }
    70  
    71  func isUnknownSystemVariableErr(err error) bool {
    72  	code, ok := getSQLErrCode(err)
    73  	if !ok {
    74  		return strings.Contains(err.Error(), "Unknown system variable")
    75  	}
    76  	return code == mysql.ErrUnknownSystemVariable
    77  }
    78  
    79  func DBFromConfig(dsn config.DBStore) (*sql.DB, error) {
    80  	param := common.MySQLConnectParam{
    81  		Host:             dsn.Host,
    82  		Port:             dsn.Port,
    83  		User:             dsn.User,
    84  		Password:         dsn.Psw,
    85  		SQLMode:          dsn.StrSQLMode,
    86  		MaxAllowedPacket: dsn.MaxAllowedPacket,
    87  		TLS:              dsn.TLS,
    88  		Vars: map[string]string{
    89  			"tidb_build_stats_concurrency":       strconv.Itoa(dsn.BuildStatsConcurrency),
    90  			"tidb_distsql_scan_concurrency":      strconv.Itoa(dsn.DistSQLScanConcurrency),
    91  			"tidb_index_serial_scan_concurrency": strconv.Itoa(dsn.IndexSerialScanConcurrency),
    92  			"tidb_checksum_table_concurrency":    strconv.Itoa(dsn.ChecksumTableConcurrency),
    93  
    94  			// after https://github.com/pingcap/tidb/pull/17102 merge,
    95  			// we need set session to true for insert auto_random value in TiDB Backend
    96  			"allow_auto_random_explicit_insert": "1",
    97  			// allow use _tidb_rowid in sql statement
    98  			"tidb_opt_write_row_id": "1",
    99  			// always set auto-commit to ON
   100  			"autocommit": "1",
   101  		},
   102  	}
   103  	db, err := param.Connect()
   104  	if err != nil {
   105  		if isUnknownSystemVariableErr(err) {
   106  			// not support allow_auto_random_explicit_insert, retry connect
   107  			delete(param.Vars, "allow_auto_random_explicit_insert")
   108  			db, err = param.Connect()
   109  			if err != nil {
   110  				return nil, errors.Trace(err)
   111  			}
   112  		} else {
   113  			return nil, errors.Trace(err)
   114  		}
   115  	}
   116  	return db, nil
   117  }
   118  
   119  func NewTiDBManager(dsn config.DBStore, tls *common.TLS) (*TiDBManager, error) {
   120  	db, err := DBFromConfig(dsn)
   121  	if err != nil {
   122  		return nil, errors.Trace(err)
   123  	}
   124  
   125  	return NewTiDBManagerWithDB(db, dsn.SQLMode), nil
   126  }
   127  
   128  // NewTiDBManagerWithDB creates a new TiDB manager with an existing database
   129  // connection.
   130  func NewTiDBManagerWithDB(db *sql.DB, sqlMode mysql.SQLMode) *TiDBManager {
   131  	parser := parser.New()
   132  	parser.SetSQLMode(sqlMode)
   133  
   134  	return &TiDBManager{
   135  		db:     db,
   136  		parser: parser,
   137  	}
   138  }
   139  
   140  func (timgr *TiDBManager) Close() {
   141  	timgr.db.Close()
   142  }
   143  
   144  func InitSchema(ctx context.Context, g glue.Glue, database string, tablesSchema map[string]string) error {
   145  	logger := log.With(zap.String("db", database))
   146  	sqlExecutor := g.GetSQLExecutor()
   147  
   148  	var createDatabase strings.Builder
   149  	createDatabase.WriteString("CREATE DATABASE IF NOT EXISTS ")
   150  	common.WriteMySQLIdentifier(&createDatabase, database)
   151  	err := sqlExecutor.ExecuteWithLog(ctx, createDatabase.String(), "create database", logger)
   152  	if err != nil {
   153  		return errors.Trace(err)
   154  	}
   155  
   156  	task := logger.Begin(zap.InfoLevel, "create tables")
   157  	var sqlCreateStmts []string
   158  loopCreate:
   159  	for tbl, sqlCreateTable := range tablesSchema {
   160  		task.Debug("create table", zap.String("schema", sqlCreateTable))
   161  
   162  		sqlCreateStmts, err = createTableIfNotExistsStmt(g.GetParser(), sqlCreateTable, database, tbl)
   163  		if err != nil {
   164  			break
   165  		}
   166  
   167  		// TODO: maybe we should put these createStems into a transaction
   168  		for _, s := range sqlCreateStmts {
   169  			err = sqlExecutor.ExecuteWithLog(
   170  				ctx,
   171  				s,
   172  				"create table",
   173  				logger.With(zap.String("table", common.UniqueTable(database, tbl))),
   174  			)
   175  			if err != nil {
   176  				break loopCreate
   177  			}
   178  		}
   179  	}
   180  	task.End(zap.ErrorLevel, err)
   181  
   182  	return errors.Trace(err)
   183  }
   184  
   185  func createDatabaseIfNotExistStmt(dbName string) string {
   186  	var createDatabase strings.Builder
   187  	createDatabase.WriteString("CREATE DATABASE IF NOT EXISTS ")
   188  	common.WriteMySQLIdentifier(&createDatabase, dbName)
   189  	return createDatabase.String()
   190  }
   191  
   192  func createTableIfNotExistsStmt(p *parser.Parser, createTable, dbName, tblName string) ([]string, error) {
   193  	stmts, _, err := p.Parse(createTable, "", "")
   194  	if err != nil {
   195  		return []string{}, err
   196  	}
   197  
   198  	var res strings.Builder
   199  	ctx := format.NewRestoreCtx(format.DefaultRestoreFlags|format.RestoreTiDBSpecialComment, &res)
   200  
   201  	retStmts := make([]string, 0, len(stmts))
   202  	for _, stmt := range stmts {
   203  		switch node := stmt.(type) {
   204  		case *ast.CreateTableStmt:
   205  			node.Table.Schema = model.NewCIStr(dbName)
   206  			node.Table.Name = model.NewCIStr(tblName)
   207  			node.IfNotExists = true
   208  		case *ast.CreateViewStmt:
   209  			node.ViewName.Schema = model.NewCIStr(dbName)
   210  			node.ViewName.Name = model.NewCIStr(tblName)
   211  		case *ast.DropTableStmt:
   212  			node.Tables[0].Schema = model.NewCIStr(dbName)
   213  			node.Tables[0].Name = model.NewCIStr(tblName)
   214  			node.IfExists = true
   215  		}
   216  		if err := stmt.Restore(ctx); err != nil {
   217  			return []string{}, err
   218  		}
   219  		ctx.WritePlain(";")
   220  		retStmts = append(retStmts, res.String())
   221  		res.Reset()
   222  	}
   223  
   224  	return retStmts, nil
   225  }
   226  
   227  func (timgr *TiDBManager) DropTable(ctx context.Context, tableName string) error {
   228  	sql := common.SQLWithRetry{
   229  		DB:     timgr.db,
   230  		Logger: log.With(zap.String("table", tableName)),
   231  	}
   232  	return sql.Exec(ctx, "drop table", "DROP TABLE "+tableName)
   233  }
   234  
   235  func LoadSchemaInfo(
   236  	ctx context.Context,
   237  	schemas []*mydump.MDDatabaseMeta,
   238  	getTables func(context.Context, string) ([]*model.TableInfo, error),
   239  ) (map[string]*checkpoints.TidbDBInfo, error) {
   240  	result := make(map[string]*checkpoints.TidbDBInfo, len(schemas))
   241  	for _, schema := range schemas {
   242  		tables, err := getTables(ctx, schema.Name)
   243  		if err != nil {
   244  			return nil, err
   245  		}
   246  
   247  		tableMap := make(map[string]*model.TableInfo, len(tables))
   248  		for _, tbl := range tables {
   249  			tableMap[tbl.Name.L] = tbl
   250  		}
   251  
   252  		dbInfo := &checkpoints.TidbDBInfo{
   253  			Name:   schema.Name,
   254  			Tables: make(map[string]*checkpoints.TidbTableInfo),
   255  		}
   256  
   257  		for _, tbl := range schema.Tables {
   258  			tblInfo, ok := tableMap[strings.ToLower(tbl.Name)]
   259  			if !ok {
   260  				return nil, errors.Errorf("table '%s' schema not found", tbl.Name)
   261  			}
   262  			tableName := tblInfo.Name.String()
   263  			if tblInfo.State != model.StatePublic {
   264  				err := errors.Errorf("table [%s.%s] state is not public", schema.Name, tableName)
   265  				metric.RecordTableCount(metric.TableStatePending, err)
   266  				return nil, err
   267  			}
   268  			metric.RecordTableCount(metric.TableStatePending, err)
   269  			if err != nil {
   270  				return nil, errors.Trace(err)
   271  			}
   272  			tableInfo := &checkpoints.TidbTableInfo{
   273  				ID:   tblInfo.ID,
   274  				DB:   schema.Name,
   275  				Name: tableName,
   276  				Core: tblInfo,
   277  			}
   278  			dbInfo.Tables[tableName] = tableInfo
   279  		}
   280  
   281  		result[schema.Name] = dbInfo
   282  	}
   283  	return result, nil
   284  }
   285  
   286  func ObtainGCLifeTime(ctx context.Context, db *sql.DB) (string, error) {
   287  	var gcLifeTime string
   288  	err := common.SQLWithRetry{DB: db, Logger: log.L()}.QueryRow(
   289  		ctx,
   290  		"obtain GC lifetime",
   291  		"SELECT VARIABLE_VALUE FROM mysql.tidb WHERE VARIABLE_NAME = 'tikv_gc_life_time'",
   292  		&gcLifeTime,
   293  	)
   294  	return gcLifeTime, err
   295  }
   296  
   297  func UpdateGCLifeTime(ctx context.Context, db *sql.DB, gcLifeTime string) error {
   298  	sql := common.SQLWithRetry{
   299  		DB:     db,
   300  		Logger: log.With(zap.String("gcLifeTime", gcLifeTime)),
   301  	}
   302  	return sql.Exec(ctx, "update GC lifetime",
   303  		"UPDATE mysql.tidb SET VARIABLE_VALUE = ? WHERE VARIABLE_NAME = 'tikv_gc_life_time'",
   304  		gcLifeTime,
   305  	)
   306  }
   307  
   308  func ObtainImportantVariables(ctx context.Context, g glue.SQLExecutor) map[string]string {
   309  	var query strings.Builder
   310  	query.WriteString("SHOW VARIABLES WHERE Variable_name IN ('")
   311  	first := true
   312  	for k := range defaultImportantVariables {
   313  		if first {
   314  			first = false
   315  		} else {
   316  			query.WriteString("','")
   317  		}
   318  		query.WriteString(k)
   319  	}
   320  	query.WriteString("')")
   321  	kvs, err := g.QueryStringsWithLog(ctx, query.String(), "obtain system variables", log.L())
   322  	if err != nil {
   323  		// error is not fatal
   324  		log.L().Warn("obtain system variables failed, use default variables instead", log.ShortError(err))
   325  	}
   326  
   327  	// convert result into a map. fill in any missing variables with default values.
   328  	result := make(map[string]string, len(defaultImportantVariables))
   329  	for _, kv := range kvs {
   330  		result[kv[0]] = kv[1]
   331  	}
   332  	for k, defV := range defaultImportantVariables {
   333  		if _, ok := result[k]; !ok {
   334  			result[k] = defV
   335  		}
   336  	}
   337  
   338  	return result
   339  }
   340  
   341  func ObtainNewCollationEnabled(ctx context.Context, g glue.SQLExecutor) bool {
   342  	newCollationEnabled := false
   343  	newCollationVal, err := g.ObtainStringWithLog(
   344  		ctx,
   345  		"SELECT variable_value FROM mysql.tidb WHERE variable_name = 'new_collation_enabled'",
   346  		"obtain new collation enabled",
   347  		log.L(),
   348  	)
   349  	if err == nil && newCollationVal == "True" {
   350  		newCollationEnabled = true
   351  	}
   352  
   353  	return newCollationEnabled
   354  }
   355  
   356  // AlterAutoIncrement rebase the table auto increment id
   357  //
   358  // NOTE: since tidb can make sure the auto id is always be rebase even if the `incr` value is smaller
   359  // the the auto incremanet base in tidb side, we needn't fetch currently auto increment value here.
   360  // See: https://github.com/pingcap/tidb/blob/64698ef9a3358bfd0fdc323996bb7928a56cadca/ddl/ddl_api.go#L2528-L2533
   361  func AlterAutoIncrement(ctx context.Context, g glue.SQLExecutor, tableName string, incr int64) error {
   362  	logger := log.With(zap.String("table", tableName), zap.Int64("auto_increment", incr))
   363  	query := fmt.Sprintf("ALTER TABLE %s AUTO_INCREMENT=%d", tableName, incr)
   364  	task := logger.Begin(zap.InfoLevel, "alter table auto_increment")
   365  	err := g.ExecuteWithLog(ctx, query, "alter table auto_increment", logger)
   366  	task.End(zap.ErrorLevel, err)
   367  	if err != nil {
   368  		task.Error(
   369  			"alter table auto_increment failed, please perform the query manually (this is needed no matter the table has an auto-increment column or not)",
   370  			zap.String("query", query),
   371  		)
   372  	}
   373  	return errors.Annotatef(err, "%s", query)
   374  }
   375  
   376  func AlterAutoRandom(ctx context.Context, g glue.SQLExecutor, tableName string, randomBase int64) error {
   377  	logger := log.With(zap.String("table", tableName), zap.Int64("auto_random", randomBase))
   378  	query := fmt.Sprintf("ALTER TABLE %s AUTO_RANDOM_BASE=%d", tableName, randomBase)
   379  	task := logger.Begin(zap.InfoLevel, "alter table auto_random")
   380  	err := g.ExecuteWithLog(ctx, query, "alter table auto_random_base", logger)
   381  	task.End(zap.ErrorLevel, err)
   382  	if err != nil {
   383  		task.Error(
   384  			"alter table auto_random_base failed, please perform the query manually (this is needed no matter the table has an auto-random column or not)",
   385  			zap.String("query", query),
   386  		)
   387  	}
   388  	return errors.Annotatef(err, "%s", query)
   389  }