vitessio/vitess · error · VitessError

VT09015

VT09015

Error message

VT09015: schema tracking required

What it means

VT09015 signals that the planned DML (UPDATE/DELETE executed via DMLWithInput) requires schema tracking in vtgate. When the target table's primary key is unknown — because schema tracking is disabled, vtgate never learned the table's PrimaryKey — the planner cannot build the input-based DML and raises this error instead. It is a configuration/feature-availability error, not a bug.

Source

Thrown at go/vt/vtgate/planbuilder/operators/phases.go:149

}

func findDMLAboveRoute(ctx *plancontext.PlanningContext, root Operator) Operator {
	visitor := func(in Operator, _ semantics.TableSet, isRoot bool) (Operator, *ApplyResult) {
		switch op := in.(type) {
		case *Delete:
			return createDMLWithInput(ctx, op, op.Source, op.DMLCommon)
		case *Update:
			return createDMLWithInput(ctx, op, op.Source, op.DMLCommon)
		}
		return in, NoRewrite
	}

	return BottomUp(root, TableID, visitor, stopAtRoute)
}

func createDMLWithInput(ctx *plancontext.PlanningContext, op, src Operator, in *DMLCommon) (Operator, *ApplyResult) {
	if len(in.Target.VTable.PrimaryKey) == 0 {
		panic(vterrors.VT09015())
	}
	dm := &DMLWithInput{}
	leftComp := make(sqlparser.ValTuple, 0, len(in.Target.VTable.PrimaryKey))
	proj := newAliasedProjection(src)
	dm.cols = make([][]*sqlparser.ColName, 1)
	for _, col := range in.Target.VTable.PrimaryKey {
		colName := sqlparser.NewColNameWithQualifier(col.String(), in.Target.Name)
		ctx.SemTable.Recursive[colName] = in.Target.ID
		proj.AddColumn(ctx, true, false, aeWrap(colName))
		dm.cols[0] = append(dm.cols[0], colName)
		leftComp = append(leftComp, colName)
	}

	dm.Source = proj

	var targetTable *Table
	_ = Visit(src, func(operator Operator) error {
		if tbl, ok := operator.(*Table); ok && tbl.QTable != nil && tbl.QTable.ID == in.Target.ID {

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Start vtgate with schema tracking enabled (--schema_change_signal-enabled / the schema-tracking flag for your release) so the planner can learn table primary keys
  2. Ensure the target table actually has a PRIMARY KEY defined in MySQL
  3. If schema tracking cannot be enabled, rewrite the DML to a form not requiring input-based execution
  4. Check the release notes/flags for your Vitess version, as the enabling flag naming has changed across versions

Example fix

// before: vtgate started without schema tracking
vtgate --topo_implementation etcd2 ...
// after
vtgate --topo_implementation etcd2 --schema_change_signal_enabled ...
Defensive patterns

Strategy: validation

Validate before calling

// check before issuing DML that requires input-based execution
if (!vtgateFlags.schemaTrackingEnabled) {
  throw new Error('enable schema tracking on vtgate before running this DML');
}
// and confirm the table has a primary key
SHOW CREATE TABLE target_table; -- must contain a PRIMARY KEY

Type guard

null

Try / catch

try {
  await vtgate.execute(session, dml, bindVars);
} catch (e) {
  if (String(e.message).includes('VT09015')) {
    // restart/enable schema tracking or rewrite the DML
  }
  throw e;
}

Prevention

When it happens

Trigger: Executing an UPDATE or DELETE that is expanded to DML-with-input (e.g. DML through a view or on a table needing the input-based plan) while the target VTable has no PrimaryKey recorded because vtgate was started without schema tracking enabled.

Common situations: Deployments with default vtgate flags running such DMLs; enabling views/DML-with-input features without turning on schema tracking; tables defined without a primary key in MySQL, so even with tracking there is no key to use.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/43241c1abb10b281. Report an issue: GitHub.