@@ -304,10 +304,11 @@ const (
304
304
charset TEXT NOT NULL,
305
305
collation TEXT NOT NULL,
306
306
source VARCHAR(10) NOT NULL DEFAULT 'unknown',
307
- sql_digest varchar(64),
308
- plan_digest varchar(64),
307
+ sql_digest varchar(64) DEFAULT NULL ,
308
+ plan_digest varchar(64) DEFAULT NULL ,
309
309
INDEX sql_index(original_sql(700),default_db(68)) COMMENT "accelerate the speed when add global binding query",
310
- INDEX time_index(update_time) COMMENT "accelerate the speed when querying with last update time"
310
+ INDEX time_index(update_time) COMMENT "accelerate the speed when querying with last update time",
311
+ UNIQUE INDEX digest_index(plan_digest, sql_digest) COMMENT "avoid duplicated records"
311
312
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;`
312
313
313
314
// CreateRoleEdgesTable stores the role and user relationship information.
@@ -1275,11 +1276,14 @@ const (
1275
1276
1276
1277
// version245 updates column types of mysql.bind_info.
1277
1278
version245 = 245
1279
+
1280
+ // version246 adds new unique index for mysql.bind_info.
1281
+ version246 = 246
1278
1282
)
1279
1283
1280
1284
// currentBootstrapVersion is defined as a variable, so we can modify its value for testing.
1281
1285
// please make sure this is the largest version
1282
- var currentBootstrapVersion int64 = version245
1286
+ var currentBootstrapVersion int64 = version246
1283
1287
1284
1288
// DDL owner key's expired time is ManagerSessionTTL seconds, we should wait the time and give more time to have a chance to finish it.
1285
1289
var internalSQLTimeout = owner .ManagerSessionTTL + 15
@@ -1460,6 +1464,7 @@ var (
1460
1464
upgradeToVer243 ,
1461
1465
upgradeToVer244 ,
1462
1466
upgradeToVer245 ,
1467
+ upgradeToVer246 ,
1463
1468
}
1464
1469
)
1465
1470
@@ -3406,6 +3411,60 @@ func upgradeToVer245(s sessiontypes.Session, ver int64) {
3406
3411
doReentrantDDL (s , "ALTER TABLE mysql.bind_info MODIFY COLUMN bind_sql LONGTEXT NOT NULL" )
3407
3412
}
3408
3413
3414
+ func upgradeToVer246 (s sessiontypes.Session , ver int64 ) {
3415
+ if ver >= version246 {
3416
+ return
3417
+ }
3418
+
3419
+ // log duplicated digests that will be set to null.
3420
+ ctx := kv .WithInternalSourceType (context .Background (), kv .InternalTxnBootstrap )
3421
+ rs , err := s .ExecuteInternal (ctx ,
3422
+ `select plan_digest, sql_digest from mysql.bind_info group by plan_digest, sql_digest having count(1) > 1` )
3423
+ if err != nil {
3424
+ logutil .BgLogger ().Fatal ("failed to get duplicated plan and sql digests" , zap .Error (err ))
3425
+ return
3426
+ }
3427
+ req := rs .NewChunk (nil )
3428
+ duplicatedDigests := make (map [string ]struct {})
3429
+ for {
3430
+ err = rs .Next (ctx , req )
3431
+ if err != nil {
3432
+ logutil .BgLogger ().Fatal ("failed to get duplicated plan and sql digests" , zap .Error (err ))
3433
+ return
3434
+ }
3435
+ if req .NumRows () == 0 {
3436
+ break
3437
+ }
3438
+ for i := 0 ; i < req .NumRows (); i ++ {
3439
+ planDigest , sqlDigest := req .GetRow (i ).GetString (0 ), req .GetRow (i ).GetString (1 )
3440
+ duplicatedDigests [sqlDigest + ", " + planDigest ] = struct {}{}
3441
+ }
3442
+ req .Reset ()
3443
+ }
3444
+ if err := rs .Close (); err != nil {
3445
+ logutil .BgLogger ().Warn ("failed to close record set" , zap .Error (err ))
3446
+ }
3447
+ if len (duplicatedDigests ) > 0 {
3448
+ digestList := make ([]string , 0 , len (duplicatedDigests ))
3449
+ for k := range duplicatedDigests {
3450
+ digestList = append (digestList , "(" + k + ")" )
3451
+ }
3452
+ logutil .BgLogger ().Warn ("set the following (plan digest, sql digest) in mysql.bind_info to null " +
3453
+ "for adding new unique index: " + strings .Join (digestList , ", " ))
3454
+ }
3455
+
3456
+ // to avoid the failure of adding the unique index, remove duplicated rows on these 2 digest columns first.
3457
+ // in most cases, there should be no duplicated rows, since now we only store one binding for each sql_digest.
3458
+ // compared with upgrading failure, it's OK to set these 2 columns to null.
3459
+ doReentrantDDL (s , `UPDATE mysql.bind_info SET plan_digest=null, sql_digest=null
3460
+ WHERE (plan_digest, sql_digest) in (
3461
+ select plan_digest, sql_digest from mysql.bind_info
3462
+ group by plan_digest, sql_digest having count(1) > 1)` )
3463
+ doReentrantDDL (s , "ALTER TABLE mysql.bind_info MODIFY COLUMN sql_digest VARCHAR(64) DEFAULT NULL" )
3464
+ doReentrantDDL (s , "ALTER TABLE mysql.bind_info MODIFY COLUMN plan_digest VARCHAR(64) DEFAULT NULL" )
3465
+ doReentrantDDL (s , "ALTER TABLE mysql.bind_info ADD UNIQUE INDEX digest_index(plan_digest, sql_digest)" , dbterror .ErrDupKeyName )
3466
+ }
3467
+
3409
3468
// initGlobalVariableIfNotExists initialize a global variable with specific val if it does not exist.
3410
3469
func initGlobalVariableIfNotExists (s sessiontypes.Session , name string , val any ) {
3411
3470
ctx := kv .WithInternalSourceType (context .Background (), kv .InternalTxnBootstrap )
0 commit comments