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

  1. 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.
  2. 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.
  3. 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

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


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