vitessio/vitess · error
cannot build a row streamer plan for the %s table as a lastp
Error message
cannot build a row streamer plan for the %s table as a lastpk value was provided (%v) and the number of primary key values within it (%d) does not match the number of primary key columns in the table (%d)
What it means
When resuming a copy phase, the saved lastpk must have exactly one value per primary key column of the table. buildSelect rejects the plan when the counts differ, because the WHERE clause (col1 = ? and col2 > ?) or (col1 > ?) cannot be constructed correctly.
Source
Thrown at go/vt/vttablet/tabletserver/vstreamer/rowstreamer.go:293
// If we know the index name that we should be using then tell MySQL
// to use it if possible. This helps to ensure that we are able to
// leverage the ordering from the index itself and avoid having to
// do a FILESORT of all the results. This index should contain all
// of the PK columns which are used in the ORDER BY clause below.
var indexHint string
// If we're pushing down any expressions, we need to let the optimizer
// choose the best index to use.
if st.PKIndexName != "" && len(rs.plan.whereExprsToPushDown) == 0 {
escapedPKIndexName, err := sqlescape.EnsureEscaped(st.PKIndexName)
if err != nil {
return "", err
}
indexHint = fmt.Sprintf(" force index (%s)", escapedPKIndexName)
}
buf.Myprintf(" from %v%s", sqlparser.NewIdentifierCS(rs.plan.Table.Name), indexHint)
if len(rs.lastpk) != 0 { // We're in the Nth copy phase cycle and need to resume
if len(rs.lastpk) != len(rs.pkColumns) {
return "", fmt.Errorf("cannot build a row streamer plan for the %s table as a lastpk value was provided (%v) and the number of primary key values within it (%d) does not match the number of primary key columns in the table (%d)",
st.Name, rs.lastpk, len(rs.lastpk), len(rs.pkColumns))
}
buf.WriteString(" where ")
// This closure handles the case for composite PKs. For example,
// if lastpk was (1,2), the where clause would be:
// (col1 = 1 and col2 > 2) or (col1 > 1).
// A tuple inequality like (col1,col2) > (1,2) ends up
// being a full table scan for MySQL.
addLastPKExpressions := func() {
prefix := ""
for lastcol, pkCol := range slices.Backward(rs.pkColumns) {
buf.Myprintf("%s(", prefix)
prefix = " or "
for i, pk := range rs.pkColumns[:lastcol] {
buf.Myprintf("%v = ", sqlparser.NewIdentifierCI(rs.plan.Table.Fields[pk].Name))
rs.lastpk[i].EncodeSQL(buf)
buf.Myprintf(" and ")
}View on GitHub (pinned to 01a25a7d17)
Solutions
- Check the table's current PK column count (SHOW INDEX/SHOW CREATE TABLE) and compare with the checkpointed lastpk in the workflow's vgtid.
- If the PK changed, reset/restart the workflow's copy phase (or correct the TableLastPK) so lastpk is regenerated against the current schema.
- Do not hand-edit checkpoints; use vtctld workflow restart/copy-phase reset so lastpk is rebuilt from live schema.
Defensive patterns
Strategy: validation
Validate before calling
pkCols := getPrimaryKeyColumns(tableName)
lastpk := checkpoint.TableLastPK[tableName].Lastpk
if lastpk != nil && len(lastpk.Rows) > 0 && len(lastpk.Rows[0]) != len(pkCols) {
return fmt.Errorf("stale lastpk for %s: %d values vs %d pk cols; reset copy phase", tableName, len(lastpk.Rows[0]), len(pkCols))
} Try / catch
err := stream(ctx, filter, tablePKs)
if err != nil && strings.Contains(err.Error(), "does not match the number of primary key columns") {
// drop the stale checkpoint and restart the copy phase
restartWorkflowCopyPhase(ctx, workflowID)
} Prevention
- Never hand-edit VReplication checkpoints (TableLastPK/vgtid)
- Re-plan workflows after any ALTER TABLE touching the primary key
- Validate lastpk shape against current schema before resuming streams
- Use vtctld workflow reset instead of manual state surgery
When it happens
Trigger: Calling Stream/VStreamRows with a TableLastPK whose lastpk values count differs from the table's PK column count — e.g. lastpk saved against an older schema whose PK had a different number of columns.
Common situations: Workflow checkpoint (vgtid TableLastPK) written before an ALTER TABLE changed the primary key; copying lastpk state between tables with different PKs; manual editing of VReplication state.
Related errors
- primary key %d refers to non-existent column
- 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/f9c3daedd4e0229a.
Report an issue: GitHub.