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