github.com/whtcorpsinc/MilevaDB-Prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/petri/acyclic/causet/optimize.go (about) 1 // Copyright 2020 WHTCORPS INC, 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 causet 15 16 import ( 17 "context" 18 "math" 19 "runtime/trace" 20 "strings" 21 "time" 22 23 "github.com/whtcorpsinc/BerolinaSQL" 24 "github.com/whtcorpsinc/BerolinaSQL/ast" 25 "github.com/whtcorpsinc/errors" 26 "github.com/whtcorpsinc/milevadb/bindinfo" 27 "github.com/whtcorpsinc/milevadb/causet/cascades" 28 causetembedded "github.com/whtcorpsinc/milevadb/causet/embedded" 29 "github.com/whtcorpsinc/milevadb/ekv" 30 "github.com/whtcorpsinc/milevadb/metrics" 31 "github.com/whtcorpsinc/milevadb/petri" 32 "github.com/whtcorpsinc/milevadb/privilege" 33 "github.com/whtcorpsinc/milevadb/schemareplicant" 34 "github.com/whtcorpsinc/milevadb/soliton/hint" 35 "github.com/whtcorpsinc/milevadb/soliton/logutil" 36 "github.com/whtcorpsinc/milevadb/stochastikctx" 37 "github.com/whtcorpsinc/milevadb/stochastikctx/stmtctx" 38 "github.com/whtcorpsinc/milevadb/stochastikctx/variable" 39 "github.com/whtcorpsinc/milevadb/types" 40 "go.uber.org/zap" 41 ) 42 43 // GetPreparedStmt extract the prepared memex from the execute memex. 44 func GetPreparedStmt(stmt *ast.InterDircuteStmt, vars *variable.StochastikVars) (ast.StmtNode, error) { 45 var ok bool 46 execID := stmt.InterDircID 47 if stmt.Name != "" { 48 if execID, ok = vars.PreparedStmtNameToID[stmt.Name]; !ok { 49 return nil, causetembedded.ErrStmtNotFound 50 } 51 } 52 if preparedPointer, ok := vars.PreparedStmts[execID]; ok { 53 preparedObj, ok := preparedPointer.(*causetembedded.CachedPrepareStmt) 54 if !ok { 55 return nil, errors.Errorf("invalid CachedPrepareStmt type") 56 } 57 return preparedObj.PreparedAst.Stmt, nil 58 } 59 return nil, causetembedded.ErrStmtNotFound 60 } 61 62 // IsReadOnly check whether the ast.Node is a read only memex. 63 func IsReadOnly(node ast.Node, vars *variable.StochastikVars) bool { 64 if execStmt, isInterDircStmt := node.(*ast.InterDircuteStmt); isInterDircStmt { 65 s, err := GetPreparedStmt(execStmt, vars) 66 if err != nil { 67 logutil.BgLogger().Warn("GetPreparedStmt failed", zap.Error(err)) 68 return false 69 } 70 return ast.IsReadOnly(s) 71 } 72 return ast.IsReadOnly(node) 73 } 74 75 // Optimize does optimization and creates a Causet. 76 // The node must be prepared first. 77 func Optimize(ctx context.Context, sctx stochastikctx.Context, node ast.Node, is schemareplicant.SchemaReplicant) (causetembedded.Causet, types.NameSlice, error) { 78 sessVars := sctx.GetStochastikVars() 79 80 // Because for write stmt, TiFlash has a different results when dagger the data in point get plan. We ban the TiFlash 81 // engine in not read only stmt. 82 if _, isolationReadContainTiFlash := sessVars.IsolationReadEngines[ekv.TiFlash]; isolationReadContainTiFlash && !IsReadOnly(node, sessVars) { 83 delete(sessVars.IsolationReadEngines, ekv.TiFlash) 84 defer func() { 85 sessVars.IsolationReadEngines[ekv.TiFlash] = struct{}{} 86 }() 87 } 88 89 if _, isolationReadContainEinsteinDB := sessVars.IsolationReadEngines[ekv.EinsteinDB]; isolationReadContainEinsteinDB { 90 var fp causetembedded.Causet 91 if fpv, ok := sctx.Value(causetembedded.PointCausetKey).(causetembedded.PointCausetVal); ok { 92 // point plan is already tried in a multi-memex query. 93 fp = fpv.Causet 94 } else { 95 fp = causetembedded.TryFastCauset(sctx, node) 96 } 97 if fp != nil { 98 if !useMaxTS(sctx, fp) { 99 sctx.PrepareTSFuture(ctx) 100 } 101 return fp, fp.OutputNames(), nil 102 } 103 } 104 105 sctx.PrepareTSFuture(ctx) 106 107 blockHints := hint.ExtractBlockHintsFromStmtNode(node, sctx) 108 stmtHints, warns := handleStmtHints(blockHints) 109 sessVars.StmtCtx.StmtHints = stmtHints 110 for _, warn := range warns { 111 sctx.GetStochastikVars().StmtCtx.AppendWarning(warn) 112 } 113 warns = warns[:0] 114 bestCauset, names, _, err := optimize(ctx, sctx, node, is) 115 if err != nil { 116 return nil, nil, err 117 } 118 if !(sessVars.UseCausetBaselines || sessVars.EvolveCausetBaselines) { 119 return bestCauset, names, nil 120 } 121 stmtNode, ok := node.(ast.StmtNode) 122 if !ok { 123 return bestCauset, names, nil 124 } 125 bindRecord, scope := getBindRecord(sctx, stmtNode) 126 if bindRecord == nil { 127 return bestCauset, names, nil 128 } 129 if sctx.GetStochastikVars().SelectLimit != math.MaxUint64 { 130 sctx.GetStochastikVars().StmtCtx.AppendWarning(errors.New("sql_select_limit is set, so plan binding is not activated")) 131 return bestCauset, names, nil 132 } 133 bestCausetHint := causetembedded.GenHintsFromPhysicalCauset(bestCauset) 134 if len(bindRecord.Bindings) > 0 { 135 orgBinding := bindRecord.Bindings[0] // the first is the original binding 136 for _, tbHint := range blockHints { // consider causet hints which contained by the original binding 137 if orgBinding.Hint.ContainBlockHint(tbHint.HintName.String()) { 138 bestCausetHint = append(bestCausetHint, tbHint) 139 } 140 } 141 } 142 bestCausetHintStr := hint.RestoreOptimizerHints(bestCausetHint) 143 144 defer func() { 145 sessVars.StmtCtx.StmtHints = stmtHints 146 for _, warn := range warns { 147 sctx.GetStochastikVars().StmtCtx.AppendWarning(warn) 148 } 149 }() 150 binding := bindRecord.FindBinding(bestCausetHintStr) 151 // If the best bestCauset is in baselines, just use it. 152 if binding != nil && binding.Status == bindinfo.Using { 153 if sctx.GetStochastikVars().UseCausetBaselines { 154 stmtHints, warns = handleStmtHints(binding.Hint.GetFirstBlockHints()) 155 } 156 return bestCauset, names, nil 157 } 158 bestCostAmongHints := math.MaxFloat64 159 var bestCausetAmongHints causetembedded.Causet 160 originHints := hint.DefCauslectHint(stmtNode) 161 // Try to find the best binding. 162 for _, binding := range bindRecord.Bindings { 163 if binding.Status != bindinfo.Using { 164 continue 165 } 166 metrics.BindUsageCounter.WithLabelValues(scope).Inc() 167 hint.BindHint(stmtNode, binding.Hint) 168 curStmtHints, curWarns := handleStmtHints(binding.Hint.GetFirstBlockHints()) 169 sctx.GetStochastikVars().StmtCtx.StmtHints = curStmtHints 170 plan, _, cost, err := optimize(ctx, sctx, node, is) 171 if err != nil { 172 binding.Status = bindinfo.Invalid 173 handleInvalidBindRecord(ctx, sctx, scope, bindinfo.BindRecord{ 174 OriginalALLEGROSQL: bindRecord.OriginalALLEGROSQL, 175 EDB: bindRecord.EDB, 176 Bindings: []bindinfo.Binding{binding}, 177 }) 178 continue 179 } 180 if cost < bestCostAmongHints { 181 if sctx.GetStochastikVars().UseCausetBaselines { 182 stmtHints, warns = curStmtHints, curWarns 183 } 184 bestCostAmongHints = cost 185 bestCausetAmongHints = plan 186 } 187 } 188 // 1. If there is already a evolution task, we do not need to handle it again. 189 // 2. If the origin binding contain `read_from_storage` hint, we should ignore the evolve task. 190 // 3. If the best plan contain TiFlash hint, we should ignore the evolve task. 191 if sctx.GetStochastikVars().EvolveCausetBaselines && binding == nil && 192 !originHints.ContainBlockHint(causetembedded.HintReadFromStorage) && 193 !bindRecord.Bindings[0].Hint.ContainBlockHint(causetembedded.HintReadFromStorage) { 194 handleEvolveTasks(ctx, sctx, bindRecord, stmtNode, bestCausetHintStr) 195 } 196 // Restore the hint to avoid changing the stmt node. 197 hint.BindHint(stmtNode, originHints) 198 if sctx.GetStochastikVars().UseCausetBaselines && bestCausetAmongHints != nil { 199 return bestCausetAmongHints, names, nil 200 } 201 return bestCauset, names, nil 202 } 203 204 func optimize(ctx context.Context, sctx stochastikctx.Context, node ast.Node, is schemareplicant.SchemaReplicant) (causetembedded.Causet, types.NameSlice, float64, error) { 205 // build logical plan 206 sctx.GetStochastikVars().CausetID = 0 207 sctx.GetStochastikVars().CausetDeferredCausetID = 0 208 hintProcessor := &hint.BlockHintProcessor{Ctx: sctx} 209 node.Accept(hintProcessor) 210 builder := causetembedded.NewCausetBuilder(sctx, is, hintProcessor) 211 212 // reset fields about rewrite 213 sctx.GetStochastikVars().RewritePhaseInfo.Reset() 214 beginRewrite := time.Now() 215 p, err := builder.Build(ctx, node) 216 if err != nil { 217 return nil, nil, 0, err 218 } 219 sctx.GetStochastikVars().RewritePhaseInfo.DurationRewrite = time.Since(beginRewrite) 220 221 sctx.GetStochastikVars().StmtCtx.Blocks = builder.GetDBBlockInfo() 222 activeRoles := sctx.GetStochastikVars().ActiveRoles 223 // Check privilege. Maybe it's better to move this to the Preprocess, but 224 // we need the causet information to check privilege, which is collected 225 // into the visitInfo in the logical plan builder. 226 if pm := privilege.GetPrivilegeManager(sctx); pm != nil { 227 if err := causetembedded.CheckPrivilege(activeRoles, pm, builder.GetVisitInfo()); err != nil { 228 return nil, nil, 0, err 229 } 230 } 231 232 if err := causetembedded.CheckBlockLock(sctx, is, builder.GetVisitInfo()); err != nil { 233 return nil, nil, 0, err 234 } 235 236 // Handle the execute memex. 237 if execCauset, ok := p.(*causetembedded.InterDircute); ok { 238 err := execCauset.OptimizePreparedCauset(ctx, sctx, is) 239 return p, p.OutputNames(), 0, err 240 } 241 242 names := p.OutputNames() 243 244 // Handle the non-logical plan memex. 245 logic, isLogicalCauset := p.(causetembedded.LogicalCauset) 246 if !isLogicalCauset { 247 return p, names, 0, nil 248 } 249 250 // Handle the logical plan memex, use cascades causet if enabled. 251 if sctx.GetStochastikVars().GetEnableCascadesCausetAppend() { 252 finalCauset, cost, err := cascades.DefaultOptimizer.FindBestCauset(sctx, logic) 253 return finalCauset, names, cost, err 254 } 255 256 beginOpt := time.Now() 257 finalCauset, cost, err := causetembedded.DoOptimize(ctx, sctx, builder.GetOptFlag(), logic) 258 sctx.GetStochastikVars().DurationOptimization = time.Since(beginOpt) 259 return finalCauset, names, cost, err 260 } 261 262 func extractSelectAndNormalizeDigest(stmtNode ast.StmtNode) (*ast.SelectStmt, string, string) { 263 switch x := stmtNode.(type) { 264 case *ast.ExplainStmt: 265 switch x.Stmt.(type) { 266 case *ast.SelectStmt: 267 causetembedded.EraseLastSemicolon(x) 268 normalizeExplainALLEGROSQL := BerolinaSQL.Normalize(x.Text()) 269 idx := strings.Index(normalizeExplainALLEGROSQL, "select") 270 normalizeALLEGROSQL := normalizeExplainALLEGROSQL[idx:] 271 hash := BerolinaSQL.DigestNormalized(normalizeALLEGROSQL) 272 return x.Stmt.(*ast.SelectStmt), normalizeALLEGROSQL, hash 273 } 274 case *ast.SelectStmt: 275 causetembedded.EraseLastSemicolon(x) 276 normalizedALLEGROSQL, hash := BerolinaSQL.NormalizeDigest(x.Text()) 277 return x, normalizedALLEGROSQL, hash 278 } 279 return nil, "", "" 280 } 281 282 func getBindRecord(ctx stochastikctx.Context, stmt ast.StmtNode) (*bindinfo.BindRecord, string) { 283 // When the petri is initializing, the bind will be nil. 284 if ctx.Value(bindinfo.StochastikBindInfoKeyType) == nil { 285 return nil, "" 286 } 287 selectStmt, normalizedALLEGROSQL, hash := extractSelectAndNormalizeDigest(stmt) 288 if selectStmt == nil { 289 return nil, "" 290 } 291 stochastikHandle := ctx.Value(bindinfo.StochastikBindInfoKeyType).(*bindinfo.StochastikHandle) 292 bindRecord := stochastikHandle.GetBindRecord(normalizedALLEGROSQL, ctx.GetStochastikVars().CurrentDB) 293 if bindRecord == nil { 294 bindRecord = stochastikHandle.GetBindRecord(normalizedALLEGROSQL, "") 295 } 296 if bindRecord != nil { 297 if bindRecord.HasUsingBinding() { 298 return bindRecord, metrics.ScopeStochastik 299 } 300 return nil, "" 301 } 302 globalHandle := petri.GetPetri(ctx).BindHandle() 303 if globalHandle == nil { 304 return nil, "" 305 } 306 bindRecord = globalHandle.GetBindRecord(hash, normalizedALLEGROSQL, ctx.GetStochastikVars().CurrentDB) 307 if bindRecord == nil { 308 bindRecord = globalHandle.GetBindRecord(hash, normalizedALLEGROSQL, "") 309 } 310 return bindRecord, metrics.ScopeGlobal 311 } 312 313 func handleInvalidBindRecord(ctx context.Context, sctx stochastikctx.Context, level string, bindRecord bindinfo.BindRecord) { 314 stochastikHandle := sctx.Value(bindinfo.StochastikBindInfoKeyType).(*bindinfo.StochastikHandle) 315 err := stochastikHandle.DropBindRecord(bindRecord.OriginalALLEGROSQL, bindRecord.EDB, &bindRecord.Bindings[0]) 316 if err != nil { 317 logutil.Logger(ctx).Info("drop stochastik bindings failed") 318 } 319 if level == metrics.ScopeStochastik { 320 return 321 } 322 323 globalHandle := petri.GetPetri(sctx).BindHandle() 324 globalHandle.AddDropInvalidBindTask(&bindRecord) 325 } 326 327 func handleEvolveTasks(ctx context.Context, sctx stochastikctx.Context, br *bindinfo.BindRecord, stmtNode ast.StmtNode, planHint string) { 328 bindALLEGROSQL := bindinfo.GenerateBindALLEGROSQL(ctx, stmtNode, planHint) 329 if bindALLEGROSQL == "" { 330 return 331 } 332 charset, collation := sctx.GetStochastikVars().GetCharsetInfo() 333 binding := bindinfo.Binding{ 334 BindALLEGROSQL: bindALLEGROSQL, 335 Status: bindinfo.PendingVerify, 336 Charset: charset, 337 DefCauslation: collation, 338 Source: bindinfo.Evolve, 339 } 340 globalHandle := petri.GetPetri(sctx).BindHandle() 341 globalHandle.AddEvolveCausetTask(br.OriginalALLEGROSQL, br.EDB, binding) 342 } 343 344 // useMaxTS returns true when meets following conditions: 345 // 1. ctx is auto commit tagged. 346 // 2. plan is point get by pk. 347 func useMaxTS(ctx stochastikctx.Context, p causetembedded.Causet) bool { 348 if !causetembedded.IsAutoCommitTxn(ctx) { 349 return false 350 } 351 352 v, ok := p.(*causetembedded.PointGetCauset) 353 return ok && (v.IndexInfo == nil || (v.IndexInfo.Primary && v.TblInfo.IsCommonHandle)) 354 } 355 356 // OptimizeInterDircStmt to optimize prepare memex protocol "execute" memex 357 // this is a short path ONLY does things filling prepare related params 358 // for point select like plan which does not need extra things 359 func OptimizeInterDircStmt(ctx context.Context, sctx stochastikctx.Context, 360 execAst *ast.InterDircuteStmt, is schemareplicant.SchemaReplicant) (causetembedded.Causet, error) { 361 defer trace.StartRegion(ctx, "Optimize").End() 362 var err error 363 builder := causetembedded.NewCausetBuilder(sctx, is, nil) 364 p, err := builder.Build(ctx, execAst) 365 if err != nil { 366 return nil, err 367 } 368 if execCauset, ok := p.(*causetembedded.InterDircute); ok { 369 err = execCauset.OptimizePreparedCauset(ctx, sctx, is) 370 return execCauset.Causet, err 371 } 372 err = errors.Errorf("invalid result plan type, should be InterDircute") 373 return nil, err 374 } 375 376 func handleStmtHints(hints []*ast.BlockOptimizerHint) (stmtHints stmtctx.StmtHints, warns []error) { 377 if len(hints) == 0 { 378 return 379 } 380 var memoryQuotaHint, useToJAHint, useCascadesHint, maxInterDircutionTime, forceNthCauset *ast.BlockOptimizerHint 381 var memoryQuotaHintCnt, useToJAHintCnt, useCascadesHintCnt, noIndexMergeHintCnt, readReplicaHintCnt, maxInterDircutionTimeCnt, forceNthCausetCnt int 382 for _, hint := range hints { 383 switch hint.HintName.L { 384 case "memory_quota": 385 memoryQuotaHint = hint 386 memoryQuotaHintCnt++ 387 case "use_toja": 388 useToJAHint = hint 389 useToJAHintCnt++ 390 case "use_cascades": 391 useCascadesHint = hint 392 useCascadesHintCnt++ 393 case "no_index_merge": 394 noIndexMergeHintCnt++ 395 case "read_consistent_replica": 396 readReplicaHintCnt++ 397 case "max_execution_time": 398 maxInterDircutionTimeCnt++ 399 maxInterDircutionTime = hint 400 case "nth_plan": 401 forceNthCausetCnt++ 402 forceNthCauset = hint 403 } 404 } 405 // Handle MEMORY_QUOTA 406 if memoryQuotaHintCnt != 0 { 407 if memoryQuotaHintCnt > 1 { 408 warn := errors.Errorf("MEMORY_QUOTA() s defined more than once, only the last definition takes effect: MEMORY_QUOTA(%v)", memoryQuotaHint.HintData.(int64)) 409 warns = append(warns, warn) 410 } 411 // InterlockingDirectorate use MemoryQuota <= 0 to indicate no memory limit, here use < 0 to handle hint syntax error. 412 if memoryQuota := memoryQuotaHint.HintData.(int64); memoryQuota < 0 { 413 warn := errors.New("The use of MEMORY_QUOTA hint is invalid, valid usage: MEMORY_QUOTA(10 MB) or MEMORY_QUOTA(10 GB)") 414 warns = append(warns, warn) 415 } else { 416 stmtHints.HasMemQuotaHint = true 417 stmtHints.MemQuotaQuery = memoryQuota 418 if memoryQuota == 0 { 419 warn := errors.New("Setting the MEMORY_QUOTA to 0 means no memory limit") 420 warns = append(warns, warn) 421 } 422 } 423 } 424 // Handle USE_TOJA 425 if useToJAHintCnt != 0 { 426 if useToJAHintCnt > 1 { 427 warn := errors.Errorf("USE_TOJA() is defined more than once, only the last definition takes effect: USE_TOJA(%v)", useToJAHint.HintData.(bool)) 428 warns = append(warns, warn) 429 } 430 stmtHints.HasAllowInSubqToJoinAnPosetDaggHint = true 431 stmtHints.AllowInSubqToJoinAnPosetDagg = useToJAHint.HintData.(bool) 432 } 433 // Handle USE_CASCADES 434 if useCascadesHintCnt != 0 { 435 if useCascadesHintCnt > 1 { 436 warn := errors.Errorf("USE_CASCADES() is defined more than once, only the last definition takes effect: USE_CASCADES(%v)", useCascadesHint.HintData.(bool)) 437 warns = append(warns, warn) 438 } 439 stmtHints.HasEnableCascadesCausetAppendHint = true 440 stmtHints.EnableCascadesCausetAppend = useCascadesHint.HintData.(bool) 441 } 442 // Handle NO_INDEX_MERGE 443 if noIndexMergeHintCnt != 0 { 444 if noIndexMergeHintCnt > 1 { 445 warn := errors.New("NO_INDEX_MERGE() is defined more than once, only the last definition takes effect") 446 warns = append(warns, warn) 447 } 448 stmtHints.NoIndexMergeHint = true 449 } 450 // Handle READ_CONSISTENT_REPLICA 451 if readReplicaHintCnt != 0 { 452 if readReplicaHintCnt > 1 { 453 warn := errors.New("READ_CONSISTENT_REPLICA() is defined more than once, only the last definition takes effect") 454 warns = append(warns, warn) 455 } 456 stmtHints.HasReplicaReadHint = true 457 stmtHints.ReplicaRead = byte(ekv.ReplicaReadFollower) 458 } 459 // Handle MAX_EXECUTION_TIME 460 if maxInterDircutionTimeCnt != 0 { 461 if maxInterDircutionTimeCnt > 1 { 462 warn := errors.Errorf("MAX_EXECUTION_TIME() is defined more than once, only the last definition takes effect: MAX_EXECUTION_TIME(%v)", maxInterDircutionTime.HintData.(uint64)) 463 warns = append(warns, warn) 464 } 465 stmtHints.HasMaxInterDircutionTime = true 466 stmtHints.MaxInterDircutionTime = maxInterDircutionTime.HintData.(uint64) 467 } 468 // Handle NTH_PLAN 469 if forceNthCausetCnt != 0 { 470 if forceNthCausetCnt > 1 { 471 warn := errors.Errorf("NTH_PLAN() is defined more than once, only the last definition takes effect: NTH_PLAN(%v)", forceNthCauset.HintData.(int64)) 472 warns = append(warns, warn) 473 } 474 stmtHints.ForceNthCauset = forceNthCauset.HintData.(int64) 475 if stmtHints.ForceNthCauset < 1 { 476 stmtHints.ForceNthCauset = -1 477 warn := errors.Errorf("the hintdata for NTH_PLAN() is too small, hint ignored.") 478 warns = append(warns, warn) 479 } 480 } else { 481 stmtHints.ForceNthCauset = -1 482 } 483 return 484 } 485 486 func init() { 487 causetembedded.OptimizeAstNode = Optimize 488 }