vitessio/vitess · error

failed query BEGIN, err: %s

Error message

failed query BEGIN, err: %s

What it means

Thrown by processTransaction when blp.dbClient.Begin() fails, meaning the player could not open a transaction on the target MySQL connection before replaying statements. The transaction is aborted before any statement is applied.

Source

Thrown at go/vt/binlog/binlogplayer/binlog_player.go:445

						log.Info(msg)
						if err := blp.setVReplicationState(binlogdatapb.VReplicationWorkflowState_Stopped, msg); err != nil {
							log.Error(fmt.Sprintf("Error writing stop state: %v", err))
						}
						return nil
					}
				}
				break
			}
			log.Info(fmt.Sprintf("Retrying txn in %v.", blp.deadlockRetry))
			time.Sleep(blp.deadlockRetry)
		}
	}
}

func (blp *BinlogPlayer) processTransaction(tx *binlogdatapb.BinlogTransaction) (ok bool, err error) {
	txnStartTime := time.Now()
	if err = blp.dbClient.Begin(); err != nil {
		return false, fmt.Errorf("failed query BEGIN, err: %s", err)
	}
	for i, stmt := range tx.Statements {
		// Make sure the statement is replayed in the proper charset.
		if dbClient, ok := blp.dbClient.(*dbClientImpl); ok {
			var stmtCharset *binlogdatapb.Charset
			if stmt.Charset != nil {
				stmtCharset = stmt.Charset
			} else {
				// Streamer sends a nil Charset for statements that use the
				// charset we specified in the request.
				stmtCharset = blp.defaultCharset
			}
			if !proto.Equal(blp.currentCharset, stmtCharset) {
				// In regular MySQL replication, the charset is silently adjusted as
				// needed during event playback. Here we also adjust so that playback
				// proceeds, but in Vitess-land this usually means a misconfigured
				// server or a misbehaving client, so we spam the logs with warnings.
				log.Warn(fmt.Sprintf("BinlogPlayer changing charset from %v to %v for statement %d in transaction %v", blp.currentCharset, stmtCharset, i, tx))

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Check target MySQL connectivity and `wait_timeout`/`interactive_timeout`; increase if gaps between vreplication transactions are long.
  2. Verify the target is not read-only (`SELECT @@read_only, @@super_read_only`).
  3. Restart the vreplication stream to get a fresh dbClient connection.
  4. Check target MySQL error log for aborted connections around the failure time.

Example fix

// before: connection expired during idle gap
//   if err = blp.dbClient.Begin(); err != nil { ... }
// after: validate/reconnect before beginning
//   if err := blp.dbClient.Ping(); err != nil {
//       if err := blp.dbClient.Reconnect(); err != nil { return false, err }
//   }
//   if err = blp.dbClient.Begin(); err != nil { ... }
Defensive patterns

Strategy: retry

Validate before calling

// check target is writable and reachable before streaming
var readOnly bool
if err := targetDB.QueryRow("SELECT @@read_only OR @@super_read_only").Scan(&readOnly); err != nil {
    return fmt.Errorf("cannot probe target: %w", err)
}
if readOnly {
    return errors.New("target MySQL is read-only; BEGIN will fail")
}

Try / catch

err := binlogplayer.ApplyBinlogEvents(ctx, blp)
if err != nil && strings.Contains(err.Error(), "failed query BEGIN") {
    log.Warn("BEGIN failed — likely dead target connection; restarting stream")
    return restartStream(ctx, blp) // fresh dbClient
}

Prevention

When it happens

Trigger: dbClient.Begin() errors: target MySQL connection is dead/closed (server gone away, wait_timeout), the dbConn was closed by charset-restore defer from a previous run, or the target server refuses new transactions (read-only, killing connections).

Common situations: Target MySQL restarted or idle connection expired during a long gap between transactions; super_read_only enabled on the target; connection pool exhaustion causing the player's conn to be reaped.

Related errors


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