Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b6e5aac
vtgate: add VEXPLAIN MYSQLPLAN statement
ejortegau Aug 10, 2026
6b9a248
vtgate: make VEXPLAIN MYSQLPLAN support check an allowlist
ejortegau Aug 10, 2026
40aa115
vtgate: test VEXPLAIN MYSQLPLAN rejects INSERT ... SELECT
ejortegau Aug 10, 2026
6a70115
vtgate: mirror no-route fallback in VEXPLAIN MYSQLPLAN
ejortegau Aug 10, 2026
2faefeb
changelog: document VEXPLAIN MYSQLPLAN baseline-plan limitation
ejortegau Aug 10, 2026
12cabf9
vtgate: bind __upper_limit before EXPLAIN for pushed-down scatter limits
ejortegau Aug 10, 2026
42c6562
vtgate: support explicitly targeted Send plans in VEXPLAIN MYSQLPLAN
ejortegau Aug 10, 2026
c83065a
vtadmin: route VEXPLAIN MYSQLPLAN through VExplain result conversion
ejortegau Aug 11, 2026
1e0da3f
vtgate: reject VEXPLAIN MYSQLPLAN in reserved-connection sessions
ejortegau Aug 12, 2026
c55ccbf
vtgate: reject sequence next-value queries in VEXPLAIN MYSQLPLAN
ejortegau Aug 12, 2026
cc71f06
vtgate: bound VEXPLAIN MYSQLPLAN fanout and account for its shard que…
ejortegau Aug 12, 2026
5026e1d
vtgate: copy bind vars per input when collecting MYSQLPLAN tasks
ejortegau Aug 13, 2026
b56aad1
changelog: document VEXPLAIN MYSQLPLAN per-shard EXPLAIN failure beha…
ejortegau Aug 13, 2026
46513b3
vtgate: unify VEXPLAIN plan-description walkers
ejortegau Aug 13, 2026
dd306e7
vtgate: count only successfully-explained shards in MYSQLPLAN accounting
ejortegau Aug 13, 2026
878891b
Merge remote-tracking branch 'upstream/main' into ejortegau/vexplain-…
ejortegau Aug 13, 2026
b811b48
test/e2e: add unsharded keyspace for VEXPLAIN MYSQLPLAN reserved-conn…
ejortegau Aug 14, 2026
cf692e6
vtgate: reject nested query blocks in VEXPLAIN MYSQLPLAN at plan time
ejortegau Aug 17, 2026
b8593e6
vtgate: reject VEXPLAIN MYSQLPLAN sequence queries before bypass plan…
ejortegau Aug 17, 2026
0b6608e
vtgate: count completed shards when a MYSQLPLAN EXPLAIN fails
ejortegau Aug 17, 2026
f8fc5c1
vtgate: count every attempted shard in MYSQLPLAN accounting
ejortegau Aug 18, 2026
5ef7ad2
vtgate: reject VEXPLAIN MYSQLPLAN advisory lock functions
ejortegau Aug 18, 2026
bc3d25b
vtgate: don't pin a VEXPLAIN MYSQLPLAN session for a SET_VAR-only sysvar
ejortegau Aug 19, 2026
2b9f32f
vtgate: reject VEXPLAIN MYSQLPLAN of SQL_CALC_FOUND_ROWS with GROUP B…
ejortegau Aug 19, 2026
0355407
docs: note VEXPLAIN MYSQLPLAN does not table-ACL-check per-shard EXPL…
ejortegau Aug 19, 2026
e52ccf9
vtgate: give VEXPLAIN MYSQLPLAN derived-table/view rejection an hones…
ejortegau Aug 19, 2026
04dbb37
vtgate: make MYSQLPLAN shard-query accounting an optional VCursor cap…
ejortegau Aug 19, 2026
bdca6b0
vtgate: run VEXPLAIN MYSQLPLAN EXPLAINs through the shared scatter path
ejortegau Aug 20, 2026
6cdbdda
vtgate: drop dead MergeSort entry from VEXPLAIN MYSQLPLAN allowlist
ejortegau Aug 20, 2026
ce2eef5
test/e2e: probe MySQL version from the sharded keyspace by name
ejortegau Aug 20, 2026
421d6f3
test/e2e: cover VEXPLAIN MYSQLPLAN scatter fan-out across shards
ejortegau Aug 20, 2026
afb986f
vtgate: rename MYSQLPLAN output key to mysql_explain_json_by_shard
ejortegau Aug 20, 2026
5880984
vtgate: fold VEXPLAIN MYSQLPLAN AST checks into one walk
ejortegau Aug 20, 2026
ac3f349
vtgate: reject VEXPLAIN MYSQLPLAN DML by statement type
ejortegau Aug 20, 2026
e5cd381
vtgate: move mysqlExplainTask into the top type() block
ejortegau Aug 20, 2026
44077e6
vtgate: warn when a MYSQLPLAN shard returns an empty EXPLAIN result
ejortegau Aug 20, 2026
eaee2cc
changelog: note VEXPLAIN MYSQLPLAN in-transaction reads pre-tx state
ejortegau Aug 20, 2026
aa5d20d
test/e2e: assert VEXPLAIN MYSQLPLAN of INTO OUTFILE/DUMPFILE has no s…
ejortegau Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions changelog/25.0/25.0.0/summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- [Preparing a statement no longer starts an implicit transaction](#vtgate-prepare-no-implicit-tx)
- [Stricter validation of SQL-level PREPARE statements](#vtgate-prepare-stricter-validation)
- [Stricter PROXY protocol v1 header validation](#vtgate-proxy-protocol-v1-strictness)
- [New `VEXPLAIN MYSQLPLAN` statement](#vtgate-vexplain-mysqlplan)
- **[Reparent](#minor-changes-reparent)**
- [`EmergencyReparentShard` no longer waits on replicas that cannot win the election](#ers-lagging-relay-log-wait)
- [Reparent candidate ordering now respects partially ordered GTID histories](#reparent-gtid-candidate-ordering)
Expand Down Expand Up @@ -267,6 +268,14 @@ Specification-conformant v1 headers, as emitted by HAProxy, AWS load balancers,

See [#20733](https://github.com/vitessio/vitess/pull/20733) for details.

#### <a id="vtgate-vexplain-mysqlplan"/>New `VEXPLAIN MYSQLPLAN` statement</a>

A new `VEXPLAIN MYSQLPLAN <query>` statement runs MySQL's `EXPLAIN FORMAT=JSON` against the shards a `SELECT` would target, **without executing the query itself**. It resolves each `Route`'s target shards from its vindex at resolution time and issues `EXPLAIN` against every resolved shard, attaching the per-shard MySQL plan to the VTGate plan tree keyed by shard, so per-shard plan and cost differences are visible.

Unlike `VEXPLAIN ALL`, which executes the query to discover the shard-level queries before explaining them, `VEXPLAIN MYSQLPLAN` never runs the wrapped query.

Only `SELECT` statements whose target shards can be resolved from a vindex without reading cluster data are supported. DML (`INSERT`/`UPDATE`/`DELETE`), and any query whose shard set depends on data — cross-shard joins, subqueries, and lookup vindexes — are rejected with an error suggesting `VEXPLAIN ALL` instead.

### <a id="minor-changes-reparent"/>Reparent</a>

#### <a id="ers-lagging-relay-log-wait"/>`EmergencyReparentShard` no longer waits on replicas that cannot win the election</a>
Expand Down
43 changes: 43 additions & 0 deletions go/test/endtoend/vtgate/queries/vexplain/vexplain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ package vexplain

import (
"fmt"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"vitess.io/vitess/go/mysql"
"vitess.io/vitess/go/mysql/capabilities"
"vitess.io/vitess/go/sqltypes"
"vitess.io/vitess/go/test/endtoend/onlineddl"
"vitess.io/vitess/go/test/endtoend/utils"
)

Expand Down Expand Up @@ -149,3 +152,43 @@ func TestVExplainAll(t *testing.T) {
utils.AssertMatchesContains(t, conn, `vexplain /*vt+ EXECUTE_DML_QUERIES */ all insert into user (id,lookup,lookup_unique) values (4,'apa','foo'),(5,'apa','bar'),(6,'monkey','nobar')`, "Insert", "mysql_explain_json")
utils.AssertMatchesContains(t, conn, `vexplain all select id from user where lookup = "apa"`, "mysql_explain_json", "ByDestination")
}

func TestVExplainMySQLPlan(t *testing.T) {
// VEXPLAIN MYSQLPLAN is a v25 syntax; an older vtgate cannot parse it. Under the
// upgrade/downgrade CI this suite can run current test code against an N-1 vtgate.
utils.SkipIfBinaryIsBelowVersion(t, 25, "vtgate")

conn, closer := start(t)
defer closer()

// The result must carry the VTGate plan tree (Route) with real per-shard MySQL
// EXPLAIN output attached, without executing the query.
utils.AssertMatchesContains(t, conn,
`vexplain mysqlplan select id from user where id = 1`,
"mysql_explain_json", "Route")

// query_block is a key emitted only by a genuine MySQL EXPLAIN FORMAT=JSON, so it
// proves we actually reached MySQL. MariaDB's EXPLAIN JSON does not use it, so gate
// this assertion to MySQL/Percona 8.0+ (which covers the 8.0 and 8.4 CI flavors).
mysqlVersion := onlineddl.GetMySQLVersion(t, clusterInstance.Keyspaces[0].Shards[0].PrimaryTablet())
require.NotEmpty(t, mysqlVersion)
atLeast80, err := capabilities.ServerVersionAtLeast(mysqlVersion, 8, 0, 0)
require.NoError(t, err)
if atLeast80 && !strings.Contains(mysqlVersion, "MariaDB") {
utils.AssertMatchesContains(t, conn,
`vexplain mysqlplan select id from user where id = 1`,
"query_block")
}

// DML is not supported (its plans are not Route primitives): it must fail closed
// and point the user to VEXPLAIN ALL, not silently produce a plan with no EXPLAIN.
utils.AssertContainsError(t, conn,
`vexplain mysqlplan insert into user (id,lookup,lookup_unique) values (99,'apa','apa')`,
"use VEXPLAIN ALL instead")

// A lookup vindex cannot resolve shards without executing, so it must fail closed
// and point the user to VEXPLAIN ALL.
utils.AssertContainsError(t, conn,
`vexplain mysqlplan select id from user where lookup_unique = "apa"`,
"use VEXPLAIN ALL instead")
}
2 changes: 2 additions & 0 deletions go/vt/sqlparser/ast_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2136,6 +2136,8 @@ func (ty VExplainType) ToString() string {
return TraceStr
case KeysVExplainType:
return KeysStr
case MySQLVExplainType:
return MySQLStr
default:
return "Unknown VExplainType"
}
Expand Down
2 changes: 2 additions & 0 deletions go/vt/sqlparser/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ const (
PlanStr = "plan"
TraceStr = "trace"
KeysStr = "keys"
MySQLStr = "mysqlplan"

// Lock Types
ReadStr = "read"
Expand Down Expand Up @@ -864,6 +865,7 @@ const (
AllVExplainType
TraceVExplainType
KeysVExplainType
MySQLVExplainType
Comment thread
ejortegau marked this conversation as resolved.
)

// Constant for Enum Type - SelectIntoType
Expand Down
1 change: 1 addition & 0 deletions go/vt/sqlparser/keywords.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ var keywords = []keyword{
{"multipoint", MULTIPOINT},
{"multipolygon", MULTIPOLYGON},
{"month", MONTH},
{"mysqlplan", MYSQLPLAN},
{"mysql_errno", MYSQL_ERRNO},
{"name", NAME},
{"names", NAMES},
Expand Down
2 changes: 2 additions & 0 deletions go/vt/sqlparser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2802,6 +2802,8 @@ var validSQL = []struct {
input: "vexplain trace select * from t",
}, {
input: "vexplain keys select * from t",
}, {
input: "vexplain mysqlplan select * from t",
}, {
input: "explain analyze select * from t",
}, {
Expand Down
Loading
Loading