vitessio/vitess · error
primary key %d refers to non-existent column
Error message
primary key %d refers to non-existent column
What it means
buildPKColumns validates that every primary key column index stored in the table's statement/plan metadata actually exists within the decoded field list. If a PK index exceeds the number of fields, the schema information is inconsistent and the row streamer cannot build a plan.
Source
Thrown at go/vt/vttablet/tabletserver/vstreamer/rowstreamer.go:239
}
pkColumns := make([]int, 0)
if len(st.PKColumns) == 0 {
// Use a PK equivalent if one exists.
pkColumns, err := rs.vse.mapPKEquivalentCols(rs.ctx, rs.cp, st)
if err == nil && len(pkColumns) != 0 {
return pkColumns, nil
}
// Fall back to using every column in the table if there's no PK or PKE.
pkColumns = make([]int, len(st.Fields))
for i := range st.Fields {
pkColumns[i] = i
}
return pkColumns, nil
}
for _, pk := range st.PKColumns {
if pk >= int64(len(st.Fields)) {
return nil, fmt.Errorf("primary key %d refers to non-existent column", pk)
}
pkColumns = append(pkColumns, int(pk))
}
st.PKIndexName = "PRIMARY"
return pkColumns, nil
}
func (rs *rowStreamer) buildSelect(st *binlogdatapb.MinimalTable) (string, error) {
buf := sqlparser.NewTrackedBuffer(nil)
// We could have used select *, but being explicit is more predictable.
buf.Myprintf("select ")
if rs.options == nil || !rs.options.NoTimeouts { // We don't e.g. want to add the timeout for a VDiff query
buf.Myprintf("%s", GetVReplicationMaxExecutionTimeQueryHint(rs.config.CopyPhaseDuration))
}
prefix := ""
for _, col := range rs.plan.Table.Fields {
if rs.plan.isConvertColumnUsingUTF8(col.Name) {
buf.Myprintf("%sconvert(%v using utf8mb4) as %v", prefix, sqlparser.NewIdentifierCI(col.Name), sqlparser.NewIdentifierCI(col.Name))View on GitHub (pinned to 01a25a7d17)
Solutions
- Compare the table's SHOW CREATE TABLE with the fields the streamer decoded; if the schema changed, refresh schema on the tablet (ReloadSchema) and restart the workflow.
- Verify the source table actually has a PRIMARY KEY matching the metadata; if the PK was dropped/altered mid-stream, re-plan the VStream from scratch.
- If it persists with an unchanged schema, capture the table name and plan and file a bug with reproduction details — this indicates internal metadata inconsistency.
Defensive patterns
Strategy: validation
Validate before calling
// before streaming, confirm PK metadata matches live schema
fields := getTableFields(tableName) // from schema
pkCols := getPrimaryKeyColumns(tableName) // SHOW INDEX / information_schema
if len(pkCols) == 0 || len(pkCols) > len(fields) {
return fmt.Errorf("table %s PK metadata inconsistent: %d pk cols vs %d fields", tableName, len(pkCols), len(fields))
} Try / catch
if err := buildPlan(...); err != nil {
if strings.Contains(err.Error(), "refers to non-existent column") {
// refresh schema and re-plan
tablet.ReloadSchema(ctx)
plan, err = buildPlan(...) // retry once
}
} Prevention
- Avoid ALTER TABLE on PKs while VReplication streams are active
- Reload tablet schema after DDL and before starting workflows
- Ensure source tables have well-defined PRIMARY KEYs
- Pin schema changes to maintenance windows for replicated tables
When it happens
Trigger: st.PKColumns contains an index >= len(st.Fields) when building the row streamer plan — i.e. schema metadata claims more/positioned PK columns than the field set returned for the table.
Common situations: Table schema changed (column dropped / PK altered) between schema load and field decoding, corrupted or stale table metadata, or a parser/analyzer bug producing mismatched PK column positions.
Related errors
- cannot build a row streamer plan for the %s table as a lastp
- row stream ended: %w
- row stream send error: %w
- VStreamer is not open
- only integer literals are supported
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/a7e4e39771fc38ae.
Report an issue: GitHub.