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 }