dgraph-io/badger · error

ErrReadOnlyTxn

ErrReadOnlyTxn

Error message

No sets or deletes are allowed in a read-only transaction

What it means

ErrReadOnlyTxn is returned when a mutation (Set/SetEntry/Delete/modify) is attempted on a read-only transaction (txn.go:356). Read-only transactions come from db.View() or db.NewTransactionAt(ts, false) and cannot write.

Source

Thrown at errors.go:34

)

var (
	// ErrValueLogSize is returned when opt.ValueLogFileSize option is not within the valid
	// range.
	ErrValueLogSize = stderrors.New("Invalid ValueLogFileSize, must be in range [1MB, 2GB)")

	// ErrKeyNotFound is returned when key isn't found on a txn.Get.
	ErrKeyNotFound = stderrors.New("Key not found")

	// ErrTxnTooBig is returned if too many writes are fit into a single transaction.
	ErrTxnTooBig = stderrors.New("Txn is too big to fit into one request")

	// ErrConflict is returned when a transaction conflicts with another transaction. This can
	// happen if the read rows had been updated concurrently by another transaction.
	ErrConflict = stderrors.New("Transaction Conflict. Please retry")

	// ErrReadOnlyTxn is returned if an update function is called on a read-only transaction.
	ErrReadOnlyTxn = stderrors.New("No sets or deletes are allowed in a read-only transaction")

	// ErrDiscardedTxn is returned if a previously discarded transaction is reused.
	ErrDiscardedTxn = stderrors.New("This transaction has been discarded. Create a new one")

	// ErrEmptyKey is returned if an empty key is passed on an update function.
	ErrEmptyKey = stderrors.New("Key cannot be empty")

	// ErrInvalidKey is returned if the key has a special !badger! prefix,
	// reserved for internal usage.
	ErrInvalidKey = stderrors.New("Key is using a reserved !badger! prefix")

	// ErrBannedKey is returned if the read/write key belongs to any banned namespace.
	ErrBannedKey = stderrors.New("Key is using the banned prefix")

	// ErrThresholdZero is returned if threshold is set to zero, and value log GC is called.
	// In such a case, GC can't be run.
	ErrThresholdZero = stderrors.New(
		"Value log GC can't run because threshold is set to zero")

View on GitHub (pinned to 2a001d466f)

Solutions

  1. Use db.Update (or NewTransactionAt(ts, true)) when the code writes
  2. Split read-only and mutating logic into separate View/Update blocks
  3. Check txn.update before mutating in generic helpers

Example fix

// before
err := db.View(func(txn *Txn) error {
    return txn.Set([]byte("k"), []byte("v")) // ErrReadOnlyTxn
})
// after
err := db.Update(func(txn *Txn) error {
    return txn.Set([]byte("k"), []byte("v"))
})
Defensive patterns

Strategy: validation

Validate before calling

func mustBeWritable(txn *badger.Txn) error {
    if !txn.update { // unexported; instead track writability in your own flag
        return errors.New("read-only transaction")
    }
    return nil
}
// practical caller-side check: only pass txns from db.Update/NewTransaction(true) to mutators

Type guard

func isWritableTxn(txn *badger.Txn, writable bool) bool { return writable }

Try / catch

if err := txn.Set(k, v); err != nil {
    if errors.Is(err, badger.ErrReadOnlyTxn) {
        return fmt.Errorf("mutation attempted in View: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling txn.Set/SetEntry/Delete inside db.View(); passing a read-only txn into a mutating helper; creating a txn with NewTransactionAt(ts, false) then writing.

Common situations: Refactoring Update() to View() for read paths while leaving a Set inside; shared helpers that both read and write receiving a read-only txn.

Related errors


AI-assisted analysis of dgraph-io/badger@2a001d466f (2026-09-05). Data as JSON: /api/errors/769468265c9a7742. Report an issue: GitHub.