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
- Use db.Update (or NewTransactionAt(ts, true)) when the code writes
- Split read-only and mutating logic into separate View/Update blocks
- 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
- Do all mutations in db.Update, all pure reads in db.View
- Never pass read-only txns into mutating helper functions
- Create txns with update=true where writes are possible
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.