vitessio/vitess · error

failed query COMMIT, err: %s

Error message

failed query COMMIT, err: %s

What it means

Thrown by processTransaction when blp.dbClient.Commit() fails after all statements and the recovery-position write succeeded within the transaction. Because recovery info is written inside the same txn, a failed COMMIT means nothing (including the position) was persisted, so the whole transaction is retried from the stream.

Source

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

		if sqlErr, ok := err.(*sqlerror.SQLError); ok && sqlErr.Number() == sqlerror.ERLockDeadlock {
			// Deadlock: ask for retry
			log.Info(fmt.Sprintf("Deadlock: %v", err))
			if err = blp.dbClient.Rollback(); err != nil {
				return false, err
			}
			return false, nil
		}
		_ = blp.dbClient.Rollback()
		return false, err
	}
	// Update recovery position after successful replay.
	// This also updates the blp's internal position.
	if err = blp.writeRecoveryPosition(tx); err != nil {
		_ = blp.dbClient.Rollback()
		return false, err
	}
	if err = blp.dbClient.Commit(); err != nil {
		return false, fmt.Errorf("failed query COMMIT, err: %s", err)
	}
	blp.blplStats.Timings.Record(BlplTransaction, txnStartTime)
	return true, nil
}

func (blp *BinlogPlayer) exec(sql string) (*sqltypes.Result, error) {
	queryStartTime := time.Now()
	qr, err := blp.dbClient.ExecuteFetch(sql, 0)
	blp.blplStats.Timings.Record(BlplQuery, queryStartTime)
	if d := time.Since(queryStartTime); d > SlowQueryThreshold {
		log.Info(fmt.Sprintf("SLOW QUERY (took %.2fs) '%s'", d.Seconds(), sql))
	}
	return qr, err
}

// writeRecoveryPosition writes the current GTID as the recovery position
// for the next transaction.
// It also tries to get the timestamp for the transaction. Two cases:

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Check the inner error: lock timeout → reduce competing writers or raise innodb_lock_wait_timeout; disk full → free space.
  2. Verify the last committed position in _vt.vreplication; nothing was committed for this txn, so a retry is safe.
  3. Restart the vreplication stream; it re-dials and re-applies from the stored position.
  4. Inspect target MySQL error log for deadlock/aborted transaction entries at the failure time.

Example fix

// before: lock wait timeout during commit
//   SET GLOBAL innodb_lock_wait_timeout = 60; -- or
// after: remove competing writer on the target tables
//   (quiesce manual writes to replicated tables during vreplication)
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight: ensure target is writable and has disk headroom
var readOnly bool
targetDB.QueryRow("SELECT @@read_only").Scan(&readOnly)
if readOnly {
    return errors.New("target read-only; commits will fail")
}
// monitor df / MySQL free-space metrics on the target host

Try / catch

err := binlogplayer.ApplyBinlogEvents(ctx, blp)
if err != nil && strings.Contains(err.Error(), "failed query COMMIT") {
    // recovery info is in the same txn: nothing committed; retry is safe
    log.Warn("COMMIT failed; transaction fully rolled back — retrying from stored position")
    return retryWithBackoff(ctx, func() error {
        return binlogplayer.ApplyBinlogEvents(ctx, blp)
    })
}

Prevention

When it happens

Trigger: dbClient.Commit() errors: connection dropped between last statement and COMMIT, MySQL killed the transaction (lock wait timeout, deadlock victim), target went read-only, disk full on target, or server crash during commit.

Common situations: Long transactions hitting innodb_lock_wait_timeout while other writers hold row locks; disk-full on the target host; network flap exactly at commit time; MySQL crash mid-commit causing recovery rollback.

Related errors


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