vitessio/vitess · error · VitessError

VT12002

VT12002

Error message

VT12002: unsupported: cross-shard foreign keys between table '%s' and '%s'

What it means

VT12002: the insert plan detected foreign keys spanning shards. When the SemTable reports parent (or child) foreign keys for the target table, the planner cannot produce a correct multi-shard insert plan, because parent and child rows may live on different shards. It panics with the target table and the first conflicting parent table in the message.

Source

Thrown at go/vt/vtgate/planbuilder/operators/insert.go:165

	return &Sequential{Sources: []Operator{delOp, insOp}}
}

func checkAndCreateInsertOperator(ctx *plancontext.PlanningContext, ins *sqlparser.Insert, vTbl *vindexes.BaseTable, routing Routing) Operator {
	insOp := createInsertOperator(ctx, ins, vTbl, routing)

	// Find the foreign key mode and for unmanaged foreign-key-mode, we don't need to do anything.
	ksMode, err := ctx.VSchema.ForeignKeyMode(vTbl.Keyspace.Name)
	if err != nil {
		panic(err)
	}
	if ksMode != vschemapb.Keyspace_managed {
		return insOp
	}

	parentFKs := ctx.SemTable.GetParentForeignKeysList()
	childFks := ctx.SemTable.GetChildForeignKeysList()
	if len(parentFKs) > 0 {
		panic(vterrors.VT12002(vTbl.String(), parentFKs[0].Table.String()))
	}
	if len(childFks) > 0 {
		if ins.Action == sqlparser.ReplaceAct {
			panic(vterrors.VT12001("REPLACE INTO with foreign keys"))
		}
		if len(ins.OnDup) > 0 {
			rows := getRowsOrError(ins)
			return createUpsertOperator(ctx, ins, insOp, rows, vTbl)
		}
	}
	return insOp
}

func getRowsOrError(ins *sqlparser.Insert) sqlparser.Values {
	if rows, ok := ins.Rows.(sqlparser.Values); ok {
		return rows
	}
	panic(vterrors.VT12001("ON DUPLICATE KEY UPDATE with foreign keys with select statement"))

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Co-locate parent and child tables on the same shard using appropriate vindexes (same sharding key) so FK relationships stay intra-shard
  2. Remove database-level foreign keys from sharded tables and enforce referential integrity in the application
  3. Keep FK-related tables unsharded (single-shard keyspace) if write patterns allow
  4. Check `Show`/vschema configuration to confirm which FKs the SemTable sees and adjust the schema

Example fix

-- before: parent in another keyspace/shard with FK enforced
CREATE TABLE child (id INT, pid INT, FOREIGN KEY (pid) REFERENCES parent(id));
-- after: enforce in application, no FK in sharded schema
CREATE TABLE child (id INT, pid INT);
-- validate pid exists in app code before insert
Defensive patterns

Strategy: validation

Validate before calling

// Before deploying, verify FK tables share the same sharding key/vindex
// or drop FKs from sharded tables entirely:
SELECT TABLE_NAME, REFERENCED_TABLE_NAME FROM information_schema.KEY_COLUMN_USAGE
 WHERE REFERENCED_TABLE_NAME IS NOT NULL;

Try / catch

_, err := db.ExecContext(ctx, insertSQL)
if err != nil && strings.Contains(err.Error(), "VT12002") {
  // foreign key spans shards: remove FK or co-locate tables
}

Prevention

When it happens

Trigger: An INSERT (or REPLACE) into a table that has foreign-key relationships (GetParentForeignKeysList non-empty, or child FKs in later checks) where related tables are in different shards/keyspaces, during checkAndCreateInsertOperator.

Common situations: Enabling managed foreign keys in a sharded Vitess keyspace without co-locating parent/child tables via Vindexes; application schemas with FKs migrated as-is into sharded deployments.

Related errors


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