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 }