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
- Co-locate parent and child tables on the same shard using appropriate vindexes (same sharding key) so FK relationships stay intra-shard
- Remove database-level foreign keys from sharded tables and enforce referential integrity in the application
- Keep FK-related tables unsharded (single-shard keyspace) if write patterns allow
- 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
- Use same-sharding-key vindexes so parent/child rows are co-located
- Enforce referential integrity in application code on sharded tables
- Keep FK relationships inside single-shard keyspaces
- Review vschema and FK configuration after every schema change
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
- VT12001
- malformed spec: MinKey/MaxKey cannot be in the middle of the
- malformed spec: shard limits should be in order: %q
- the shard count must be > 0: %v
- the index of the shard must be less than the total number of
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/014f6a869200044f.
Report an issue: GitHub.