vitessio/vitess · error · VitessError
VT09017
VT09017
Error message
VT09017: INSERT with a target destination is not allowed
What it means
VT09017 is raised by createInsertOperator in go/vt/vtgate/planbuilder/operators/insert.go:363 when an INSERT statement targets a explicitly-set destination (a targeted routing) instead of a keyspace/table resolved through normal routing. Vitess does not support combining a route target (e.g. from USE statements with @primary/@replica or vtgate target syntax) with INSERT planning, so it rejects the statement.
Source
Thrown at go/vt/vtgate/planbuilder/operators/insert.go:363
}
var def sqlparser.Expr
idx := ins.Columns.FindColumn(col.Name)
if idx == -1 {
def = findDefault(vTbl, col.Name)
// no default, replace it with null value.
if def == nil {
def = &sqlparser.NullVal{}
}
}
offsets = append(offsets, uComp{idx, def})
return false, nil
}, expr)
return offsets, false
}
func createInsertOperator(ctx *plancontext.PlanningContext, insStmt *sqlparser.Insert, vTbl *vindexes.BaseTable, routing Routing) (op Operator) {
if _, target := routing.(*TargetedRouting); target {
panic(vterrors.VT09017("INSERT with a target destination is not allowed"))
}
insOp := &Insert{
VTable: vTbl,
AST: insStmt,
}
route := &Route{
unaryOperator: newUnaryOp(insOp),
Routing: routing,
}
// Table column list is nil then add all the columns
// If the column list is empty then add only the auto-inc column and
// this happens on calling modifyForAutoinc
if insStmt.Columns == nil && valuesProvided(insStmt.Rows) {
if vTbl.ColumnListAuthoritative {
insStmt = populateInsertColumnlist(insStmt, vTbl)
} else {View on GitHub (pinned to 01a25a7d17)
Solutions
- Remove the explicit target: use `USE keyspace` (without @primary/@replica suffix) before the INSERT and let VTGate route the write
- Send the INSERT to a tablet directly (vttablet) instead of vtgate if you need tablet-level targeting
- Split the workload: use targeted sessions for reads and a normal session for DML
Example fix
// before USE commerce@replica; INSERT INTO products (id, name) VALUES (1, 'x'); // after USE commerce; INSERT INTO products (id, name) VALUES (1, 'x');
Defensive patterns
Strategy: validation
Validate before calling
// Reject targeted destinations before sending DML
if (/^[^\s]+@(primary|replica|rdonly)$/.test(currentUseTarget)) {
throw new Error('DML not allowed on targeted destination: ' + currentUseTarget);
} Type guard
function isTargetedDestination(target) {
return typeof target === 'string' && /@[a-z]+$/i.test(target);
} Try / catch
try {
await conn.query('INSERT INTO t VALUES (?)', [v]);
} catch (e) {
if (String(e).includes('VT09017')) {
await conn.query('USE commerce'); // drop the @tablet-type suffix and retry
} else throw e;
} Prevention
- Use `USE keyspace` without @primary/@replica suffixes for write sessions
- Keep read-targeting and write traffic on separate connections
- Send tablet-targeted DML to vttablet directly, not through vtgate
When it happens
Trigger: Executing an INSERT while the session has a targeted destination (routing is *TargetedRouting) — e.g. after `USE ks@primary` or sending the query with an explicit tablet-type/tablet-target destination to vtgate.
Common situations: Scripts that set a tablet target for reads and reuse the same session for writes; tools pinning traffic to a specific replica or tablet type; testing setups that redirect all statements to a chosen destination.
Related errors
- insert failed to generate an id
- no route variable found with name %s
- values %v for column %v does not map to keyspace ids
- UnicodeLooseMD5.Map: %v
- UnicodeLooseXXHash.Map: %v
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/bd5660d8fcaf0df4.
Report an issue: GitHub.