vitess.io/vitess@v0.16.2/go/vt/vtexplain/vtexplain.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package vtexplain analyzes a set of sql statements and returns the 18 // corresponding vtgate and vttablet query plans that will be executed 19 // on the given statements 20 package vtexplain 21 22 import ( 23 "bytes" 24 "fmt" 25 "sort" 26 "strings" 27 "time" 28 29 "github.com/spf13/pflag" 30 31 "vitess.io/vitess/go/vt/discovery" 32 "vitess.io/vitess/go/vt/servenv" 33 "vitess.io/vitess/go/vt/vtgate" 34 35 "vitess.io/vitess/go/jsonutil" 36 "vitess.io/vitess/go/sync2" 37 "vitess.io/vitess/go/vt/log" 38 "vitess.io/vitess/go/vt/sqlparser" 39 "vitess.io/vitess/go/vt/vtgate/engine" 40 41 querypb "vitess.io/vitess/go/vt/proto/query" 42 vtgatepb "vitess.io/vitess/go/vt/proto/vtgate" 43 ) 44 45 var ( 46 batchInterval = 10 * time.Millisecond 47 ) 48 49 func init() { 50 servenv.OnParseFor("vtexplain", func(fs *pflag.FlagSet) { 51 fs.DurationVar(&batchInterval, "batch-interval", 10*time.Millisecond, "Interval between logical time slots.") 52 }) 53 } 54 55 const ( 56 vtexplainCell = "explainCell" 57 58 // ModeMulti is the default mode with autocommit implemented at vtgate 59 ModeMulti = "multi" 60 61 // ModeTwoPC enables the twopc feature 62 ModeTwoPC = "twopc" 63 ) 64 65 type ( 66 // ExecutorMode controls the mode of operation for the vtexplain simulator 67 ExecutorMode string 68 69 // Options to control the explain process 70 Options struct { 71 // NumShards indicates the number of shards in the topology 72 NumShards int 73 74 // PlannerVersion indicates whether or not we should use the Gen4 planner 75 PlannerVersion querypb.ExecuteOptions_PlannerVersion 76 77 // ReplicationMode must be set to either "ROW" or "STATEMENT" before 78 // initialization 79 ReplicationMode string 80 81 // Normalize controls whether or not vtgate does query normalization 82 Normalize bool 83 84 // ExecutionMode must be set to one of the modes above 85 ExecutionMode string 86 87 // StrictDDL is used in unit tests only to verify that the schema 88 // is parsed properly. 89 StrictDDL bool 90 91 // Target is used to override the "database" target in the 92 // vtgate session to simulate `USE <target>` 93 Target string 94 } 95 96 // TabletQuery defines a query that was sent to a given tablet and how it was 97 // processed in mysql 98 TabletQuery struct { 99 // Logical time of the query 100 Time int 101 102 // SQL command sent to the given tablet 103 SQL string 104 105 // BindVars sent with the command 106 BindVars map[string]*querypb.BindVariable 107 } 108 109 // MysqlQuery defines a query that was sent to a given tablet and how it was 110 // processed in mysql 111 MysqlQuery struct { 112 // Sequence number of the query 113 Time int 114 115 // SQL command sent to the given tablet 116 SQL string 117 } 118 119 // Explain defines how vitess will execute a given sql query, including the vtgate 120 // query plans and all queries run on each tablet. 121 Explain struct { 122 // original sql statement 123 SQL string 124 125 // the vtgate plan(s) 126 Plans []*engine.Plan 127 128 // list of queries / bind vars sent to each tablet 129 TabletActions map[string]*TabletActions 130 } 131 132 outputQuery struct { 133 tablet string 134 Time int 135 sql string 136 } 137 138 VTExplain struct { 139 explainTopo *ExplainTopo 140 vtgateExecutor *vtgate.Executor 141 healthCheck *discovery.FakeHealthCheck 142 vtgateSession *vtgatepb.Session 143 spMap map[string]string 144 spCount int 145 146 // time simulator 147 batchTime *sync2.Batcher 148 globalTabletEnv *tabletEnv 149 } 150 ) 151 152 // MarshalJSON renders the json structure 153 func (tq *TabletQuery) MarshalJSON() ([]byte, error) { 154 // Convert Bindvars to strings for nicer output 155 bindVars := make(map[string]string) 156 for k, v := range tq.BindVars { 157 var b strings.Builder 158 sqlparser.EncodeValue(&b, v) 159 bindVars[k] = b.String() 160 } 161 162 return jsonutil.MarshalNoEscape(&struct { 163 Time int 164 SQL string 165 BindVars map[string]string 166 }{ 167 Time: tq.Time, 168 SQL: tq.SQL, 169 BindVars: bindVars, 170 }) 171 } 172 173 // TabletActions contains the set of operations done by a given tablet 174 type TabletActions struct { 175 // Queries sent from vtgate to the tablet 176 TabletQueries []*TabletQuery 177 178 // Queries that were run on mysql 179 MysqlQueries []*MysqlQuery 180 } 181 182 // Init sets up the fake execution environment 183 func Init(vSchemaStr, sqlSchema, ksShardMapStr string, opts *Options) (*VTExplain, error) { 184 // Verify options 185 if opts.ReplicationMode != "ROW" && opts.ReplicationMode != "STATEMENT" { 186 return nil, fmt.Errorf("invalid replication mode \"%s\"", opts.ReplicationMode) 187 } 188 189 parsedDDLs, err := parseSchema(sqlSchema, opts) 190 if err != nil { 191 return nil, fmt.Errorf("parseSchema: %v", err) 192 } 193 194 tabletEnv, err := newTabletEnvironment(parsedDDLs, opts) 195 if err != nil { 196 return nil, fmt.Errorf("initTabletEnvironment: %v", err) 197 } 198 vte := &VTExplain{vtgateSession: &vtgatepb.Session{ 199 TargetString: "", 200 Autocommit: true, 201 }} 202 vte.setGlobalTabletEnv(tabletEnv) 203 err = vte.initVtgateExecutor(vSchemaStr, ksShardMapStr, opts) 204 if err != nil { 205 return nil, fmt.Errorf("initVtgateExecutor: %v", err.Error()) 206 } 207 208 return vte, nil 209 } 210 211 // Stop and cleans up fake execution environment 212 func (vte *VTExplain) Stop() { 213 // Cleanup all created fake dbs. 214 if vte.explainTopo != nil { 215 for _, conn := range vte.explainTopo.TabletConns { 216 conn.tsv.StopService() 217 } 218 for _, conn := range vte.explainTopo.TabletConns { 219 conn.db.Close() 220 } 221 } 222 } 223 224 func parseSchema(sqlSchema string, opts *Options) ([]sqlparser.DDLStatement, error) { 225 parsedDDLs := make([]sqlparser.DDLStatement, 0, 16) 226 for { 227 sql, rem, err := sqlparser.SplitStatement(sqlSchema) 228 sqlSchema = rem 229 if err != nil { 230 return nil, err 231 } 232 if sql == "" { 233 break 234 } 235 sql, _ = sqlparser.SplitMarginComments(sql) 236 if sql == "" { 237 continue 238 } 239 240 var stmt sqlparser.Statement 241 if opts.StrictDDL { 242 stmt, err = sqlparser.ParseStrictDDL(sql) 243 if err != nil { 244 return nil, err 245 } 246 } else { 247 stmt, err = sqlparser.Parse(sql) 248 if err != nil { 249 log.Errorf("ERROR: failed to parse sql: %s, got error: %v", sql, err) 250 continue 251 } 252 } 253 ddl, ok := stmt.(sqlparser.DDLStatement) 254 if !ok { 255 log.Infof("ignoring non-DDL statement: %s", sql) 256 continue 257 } 258 if ddl.GetAction() != sqlparser.CreateDDLAction { 259 log.Infof("ignoring %s table statement", ddl.GetAction().ToString()) 260 continue 261 } 262 if ddl.GetTableSpec() == nil && ddl.GetOptLike() == nil { 263 log.Errorf("invalid create table statement: %s", sql) 264 continue 265 } 266 parsedDDLs = append(parsedDDLs, ddl) 267 } 268 return parsedDDLs, nil 269 } 270 271 // Run the explain analysis on the given queries 272 func (vte *VTExplain) Run(sql string) ([]*Explain, error) { 273 explains := make([]*Explain, 0, 16) 274 275 var ( 276 rem string 277 err error 278 ) 279 280 for { 281 // Need to strip comments in a loop to handle multiple comments 282 // in a row. 283 for { 284 s := sqlparser.StripLeadingComments(sql) 285 if s == sql { 286 break 287 } 288 sql = s 289 } 290 291 sql, rem, err = sqlparser.SplitStatement(sql) 292 if err != nil { 293 return nil, err 294 } 295 296 if sql != "" { 297 // Reset the global time simulator unless there's an open transaction 298 // in the session from the previous statement. 299 if vte.vtgateSession == nil || !vte.vtgateSession.GetInTransaction() { 300 vte.batchTime = sync2.NewBatcher(batchInterval) 301 } 302 log.V(100).Infof("explain %s", sql) 303 e, err := vte.explain(sql) 304 if err != nil { 305 return nil, err 306 } 307 explains = append(explains, e) 308 } 309 310 sql = rem 311 if sql == "" { 312 break 313 } 314 } 315 316 return explains, nil 317 } 318 319 func (vte *VTExplain) explain(sql string) (*Explain, error) { 320 plans, tabletActions, err := vte.vtgateExecute(sql) 321 if err != nil { 322 return nil, err 323 } 324 325 return &Explain{ 326 SQL: sql, 327 Plans: plans, 328 TabletActions: tabletActions, 329 }, nil 330 } 331 332 // ExplainsAsText returns a text representation of the explains in logical time 333 // order 334 func (vte *VTExplain) ExplainsAsText(explains []*Explain) (string, error) { 335 var b bytes.Buffer 336 for _, explain := range explains { 337 fmt.Fprintf(&b, "----------------------------------------------------------------------\n") 338 fmt.Fprintf(&b, "%s\n\n", explain.SQL) 339 340 queries := make([]outputQuery, 0, 4) 341 for tablet, actions := range explain.TabletActions { 342 for _, q := range actions.MysqlQueries { 343 // change savepoint for printing out, that are internal to vitess. 344 err := vte.specialHandlingOfSavepoints(q) 345 if err != nil { 346 return "", err 347 } 348 queries = append(queries, outputQuery{ 349 tablet: tablet, 350 Time: q.Time, 351 sql: q.SQL, 352 }) 353 } 354 } 355 356 // Make sure to sort first by the batch time and then by the 357 // shard to avoid flakiness in the tests for parallel queries 358 sort.SliceStable(queries, func(i, j int) bool { 359 if queries[i].Time == queries[j].Time { 360 return queries[i].tablet < queries[j].tablet 361 } 362 return queries[i].Time < queries[j].Time 363 }) 364 365 for _, q := range queries { 366 fmt.Fprintf(&b, "%d %s: %s\n", q.Time, q.tablet, q.sql) 367 } 368 fmt.Fprintf(&b, "\n") 369 } 370 fmt.Fprintf(&b, "----------------------------------------------------------------------\n") 371 return b.String(), nil 372 } 373 374 func (vte *VTExplain) specialHandlingOfSavepoints(q *MysqlQuery) error { 375 if !strings.HasPrefix(q.SQL, "savepoint") { 376 return nil 377 } 378 379 stmt, err := sqlparser.Parse(q.SQL) 380 if err != nil { 381 return err 382 } 383 384 sp, ok := stmt.(*sqlparser.Savepoint) 385 if !ok { 386 return fmt.Errorf("savepoint expected, got: %s", q.SQL) 387 } 388 if !strings.Contains(sp.Name.String(), "_vt") { 389 return nil 390 } 391 392 if vte.spMap == nil { 393 vte.spMap = map[string]string{} 394 } 395 spName := vte.spMap[sp.Name.String()] 396 if spName == "" { 397 spName = fmt.Sprintf("x%d", vte.spCount+1) 398 vte.spMap[sp.Name.String()] = spName 399 vte.spCount++ 400 } 401 sp.Name = sqlparser.NewIdentifierCI(spName) 402 q.SQL = sqlparser.String(sp) 403 404 return nil 405 } 406 407 // ExplainsAsJSON returns a json representation of the explains 408 func ExplainsAsJSON(explains []*Explain) string { 409 explainJSON, _ := jsonutil.MarshalIndentNoEscape(explains, "", " ") 410 return string(explainJSON) 411 }