{"record":{"id":"57141f96b2f40e9e","repo":"vitessio/vitess","slug":"vt12001-57141f","errorCode":"VT12001","errorMessage":"multi shard UPDATE with LIMIT","messagePattern":"multi shard UPDATE with LIMIT","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vtgate/planbuilder/operators/route_planning.go","lineNumber":108,"sourceCode":"\tvindexTable := tableInfo.GetVindexTable()\n\tif tableInfo.GetVindexTable().Type == vindexes.TypeReference && vindexTable.Source != nil {\n\t\tsourceTable, _, _, _, _, err := ctx.VSchema.FindTableOrVindex(vindexTable.Source.TableName)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tvindexTable = sourceTable\n\t\trefTbl := sqlparser.NewAliasedTableExpr(vindexTable.GetTableName(), \"\")\n\t\tins.Table.Expr = refTbl.Expr\n\t\t// We don't need to process the alias because you cannot define aliases for inserts.\n\t}\n\n\tif !vindexTable.Keyspace.Sharded {\n\t\treturn vindexTable, &AnyShardRouting{keyspace: vindexTable.Keyspace}\n\t}\n\n\ttblName, ok := table.Alias.Expr.(sqlparser.TableName)\n\tif !ok {\n\t\tpanic(vterrors.VT12001(\"multi shard UPDATE with LIMIT\"))\n\t}\n\n\t_, _, _, typ, dest, err := ctx.VSchema.FindTableOrVindex(tblName)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif dest == nil {\n\t\trouting := &ShardedRouting{\n\t\t\tkeyspace:    vindexTable.Keyspace,\n\t\t\tRouteOpCode: engine.Scatter,\n\t\t}\n\t\treturn vindexTable, routing\n\t}\n\n\tif typ != topodatapb.TabletType_PRIMARY {\n\t\tpanic(vterrors.VT09002(dmlType))\n\t}\n","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtgate/planbuilder/operators/route_planning.go#L90-L126","documentation":"VT12001 signals an unsupported multi-shard operation. When building the vindex table for a DML with LIMIT (e.g. `UPDATE ... LIMIT n` or `DELETE ... LIMIT n`), the planner requires the table expression to be a simple table name; a non-table expression (subquery/derived table) combined with LIMIT would spread across shards, which is unsupported.","triggerScenarios":"Running `UPDATE t ... LIMIT n` (or DELETE with LIMIT) on a sharded keyspace where the FROM/UPDATE target is not a plain TableName — e.g. `UPDATE (SELECT ...) AS x ... LIMIT n`.","commonSituations":"Applications ported from single-MySQL setups using UPDATE/DELETE with LIMIT on sharded tables; ORM-generated DML with LIMIT clauses.","solutions":["Rewrite the DML to select the primary keys first, then update/delete by key without LIMIT","Remove the LIMIT clause if it is not essential","Move the statement to an unsharded keyspace or execute it directly against the shard via a targeted workflow"],"exampleFix":"// before\nUPDATE t SET a = 1 WHERE b = 2 LIMIT 10; -- VT12001\n// after\nUPDATE t SET a = 1 WHERE id IN (SELECT id FROM t WHERE b = 2 LIMIT 10) -- executed appropriately, or select ids first then update by id","handlingStrategy":"validation","validationCode":"// detect DML-with-LIMIT on sharded keyspaces before executing\nq := strings.ToLower(statement)\nif strings.HasPrefix(q, \"update\") || strings.HasPrefix(q, \"delete\") {\n    if strings.Contains(q, \" limit \") && keyspaceIsSharded {\n        // rewrite to key-based DML before sending\n    }\n}","typeGuard":null,"tryCatchPattern":"_, err := conn.Execute(\"UPDATE t SET a=1 WHERE b=2 LIMIT 10\", nil)\nif err != nil && strings.Contains(err.Error(), \"VT12001\") {\n    // fall back: select ids first, then update by id without LIMIT\n}","preventionTips":["Avoid UPDATE/DELETE ... LIMIT on sharded keyspaces","Select matching primary keys first, then modify by key","Keep the target table a simple table name for DML (no derived tables)"],"tags":["vtgate","dml","limit","sharded","unsupported"],"backgroundTag":"multi-shard-dml-limit","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}