vitessio/vitess · error

VT12001

VT12001

Error message

'%s' column referenced in update expression '%s' is itself updated

What it means

VT12001 "'column' referenced in update expression 'expr' is itself updated" is panicked by errIfDependentColumnUpdated (go/vt/vtgate/planbuilder/operators/update.go:233), invoked from prepareUpdateExpressionList. For a multi-table UPDATE, each update expression's join-column dependencies are checked; if an expression assigned in the SET clause references (per ctx.SemTable.EqualsExprWithDeps) a column that the same update also modifies, the statement is rejected — MySQL semantics forbid reading a column being updated in the same statement. Vitess turns this into a planning-time error instead of producing a wrong plan.

Source

Thrown at go/vt/vtgate/planbuilder/operators/update.go:233

		jc := breakExpressionInLHSandRHS(ctx, ue.Expr, exprDeps.Remove(target))
		ueMap[target] = append(ueMap[target], updColumn{ue.Name, jc})
	}

	// Check if any of the dependent columns are updated in the same query.
	// This can result in a mismatch of rows on how MySQL interprets it and how Vitess would have updated those rows.
	// It is safe to fail for those cases.
	errIfDependentColumnUpdated(ctx, upd, ueMap)

	return ueMap
}

func errIfDependentColumnUpdated(ctx *plancontext.PlanningContext, upd *sqlparser.Update, ueMap map[semantics.TableSet]updList) {
	for _, ue := range upd.Exprs {
		for _, list := range ueMap {
			for _, dc := range list {
				for _, bvExpr := range dc.jc.LHSExprs {
					if ctx.SemTable.EqualsExprWithDeps(ue.Name, bvExpr.Expr) {
						panic(vterrors.VT12001(
							fmt.Sprintf("'%s' column referenced in update expression '%s' is itself updated", sqlparser.String(ue.Name), sqlparser.String(dc.jc.Original))))
					}
				}
			}
		}
	}
}

func createUpdateOpWithTarget(ctx *plancontext.PlanningContext, updStmt *sqlparser.Update, target semantics.TableSet, uList updList) dmlOp {
	if len(uList) == 0 {
		panic(vterrors.VT13001("no update expression for the target"))
	}

	ti, err := ctx.SemTable.TableInfoFor(target)
	if err != nil {
		panic(vterrors.VT13001(err.Error()))
	}
	vTbl := ti.GetVindexTable()

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Restructure the UPDATE so the SET expressions do not read columns assigned in the same statement — compute the needed values first (e.g. into a temp table or derived table)
  2. Split the UPDATE into two sequential statements: one to materialize dependent values, one to apply them
  3. If the reference is unintentional, rename/qualify columns so the SET expression reads the pre-update source column from the other table only

Example fix

-- before (b is both read and written)
UPDATE t1 JOIN t2 ON t1.id = t2.id SET t1.a = t2.b, t2.b = t2.b + 1;
-- after (two statements)
UPDATE t1 JOIN t2 ON t1.id = t2.id SET t1.a = t2.b;
UPDATE t2 SET b = b + 1;
Defensive patterns

Strategy: validation

Validate before calling

-- verify no SET expression reads a column assigned in the same multi-table UPDATE
SELECT column_name FROM information_schema.columns
WHERE table_name = ? AND column_name IN (columns_read_by_set_expressions);

Try / catch

if err != nil && strings.Contains(err.Error(), "is itself updated") {
    return splitIntoSequentialUpdates(ctx, stmt)
}

Prevention

When it happens

Trigger: A multi-table UPDATE like UPDATE t1 JOIN t2 ... SET t1.a = t2.b WHERE ... where t2.b (the join/read column) is itself assigned elsewhere in the same UPDATE's Exprs; the check finds ue.Name equal-by-deps to a bvExpr in dc.jc.LHSExprs of an updated dependent column.

Common situations: Bulk updates written as self-referencing joins (SET t.x = t.y while t.y is also SET); migrations rewriting key columns that participate in the join condition; users porting application logic that assumed single-pass semantics.

Related errors


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