vitessio/vitess · error

SBR mode unsupported for streaming: %s

Error message

SBR mode unsupported for streaming: %s

What it means

During keyrange-filtered binlog streaming, an INSERT/UPDATE/DELETE statement has no KeyspaceID attached, meaning it came from statement-based replication (SBR) or lacked row metadata. Keyrange filtering requires row-based replication so each row's keyspace ID can be compared against the requested keyrange.

Source

Thrown at go/vt/binlog/keyrange_filter.go:50

// passed into the Streamer: bls.Stream(file, pos, sendTransaction) ->
// bls.Stream(file, pos, keyRangeFilterFunc(keyrange, sendTransaction))
func keyRangeFilterFunc(keyrange *topodatapb.KeyRange, callback func(*binlogdatapb.BinlogTransaction) error) sendTransactionFunc {
	return func(eventToken *querypb.EventToken, statements []FullBinlogStatement) error {
		matched := false
		filtered := make([]*binlogdatapb.BinlogTransaction_Statement, 0, len(statements))
		for _, statement := range statements {
			switch statement.Statement.Category {
			case binlogdatapb.BinlogTransaction_Statement_BL_SET:
				filtered = append(filtered, statement.Statement)
			case binlogdatapb.BinlogTransaction_Statement_BL_DDL:
				log.Warn(fmt.Sprintf("Not forwarding DDL: %s", statement.Statement.Sql))
				continue
			case binlogdatapb.BinlogTransaction_Statement_BL_INSERT,
				binlogdatapb.BinlogTransaction_Statement_BL_UPDATE,
				binlogdatapb.BinlogTransaction_Statement_BL_DELETE:
				if statement.KeyspaceID == nil {
					updateStreamErrors.Add("KeyRangeStream", 1)
					return fmt.Errorf("SBR mode unsupported for streaming: %s", statement.Statement.Sql)
				}
				if !key.KeyRangeContains(keyrange, statement.KeyspaceID) {
					continue
				}
				filtered = append(filtered, statement.Statement)
				matched = true
			case binlogdatapb.BinlogTransaction_Statement_BL_UNRECOGNIZED:
				updateStreamErrors.Add("KeyRangeStream", 1)
				log.Error(fmt.Sprintf("Error parsing keyspace id: %s", statement.Statement.Sql))
				continue
			}
		}
		trans := &binlogdatapb.BinlogTransaction{
			EventToken: eventToken,
		}
		if matched {
			trans.Statements = filtered
		}

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Set binlog_format=ROW on the source MySQL server and restart/repick the streamer.
  2. Identify and rewrite the offending statement (shown in the error) so it replicates row-based.
  3. If the statement is intentionally SBR, do not request keyrange-filtered streaming for that workload.

Example fix

-- before
SET GLOBAL binlog_format = 'STATEMENT';
-- after
SET GLOBAL binlog_format = 'ROW';
Defensive patterns

Strategy: validation

Validate before calling

// require RBR before keyrange streaming
out, _ := exec.Command("mysql", "-e", "SELECT @@binlog_format").Output()
if !strings.Contains(string(out), "ROW") {
	return errors.New("binlog_format must be ROW for keyrange streaming")
}

Try / catch

if err := bls.StreamKeyRange(ctx, ...); err != nil {
	if strings.Contains(err.Error(), "SBR mode unsupported") {
		return fmt.Errorf("switch source to binlog_format=ROW: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: Streaming a binlog to a KeyRange while the source MySQL uses binlog_format=STATEMENT, or a DML statement carries no KeyspaceID in the BinlogTransaction statement.

Common situations: Mixed RBR/SBR deployments where some statements fall back to SBR (e.g. statements that MySQL can't replicate row-based, or binlog_format not set to ROW on the source).

Related errors


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