← Debugging Guides

阅读中文版本

GORM — record not found error on a lookup

Handle GORM ErrRecordNotFound by using Find for optional lookups and checking errors explicitly.

beginnerdebugginggogorm

Symptoms

  • record not found
  • First or Take returns gorm.ErrRecordNotFound
  • The error appears for valid but absent rows
  • Code treats a missing row as a failure

Possible causes

  • First or Take returns ErrRecordNotFound when no row matches
  • The query filters differ from the stored data
  • Soft-deleted rows are excluded by default
  • Callers mistake an empty result for an infrastructure error

Diagnosis steps

Confirm the query actually matches rows

db.ToSQL(func(tx *gorm.DB) *gorm.DB { return tx.Model(&User{}).Where("id = ?", id).First(&u) })

Interpretation

ToSQL prints the generated statement, revealing filter or table issues.

Use Find when absence is acceptable

db.Where("id = ?", id).Find(&users)

Interpretation

Find returns an empty slice rather than an error for no matches, which suits optional lookups.

Check soft delete behavior

grep -n "gorm.DeletedAt\|Unscoped" models/*.go

Interpretation

Soft-deleted rows are filtered unless you use Unscoped, which explains a missing row that exists in the table.

Root cause

The ORM reported ErrRecordNotFound because no row matched the query after defaults and soft-delete filtering, which the caller treated as an error rather than an expected empty result.

Fix

Use Find for lookups where absence is valid, check errors with errors.Is against gorm.ErrRecordNotFound, and use Unscoped when soft-deleted rows should be included.

Verify the fix

Run the lookup with a known-absent and a known-present value and confirm each path behaves as intended, and confirm soft-deleted rows appear only when Unscoped is used.

Symptom walkthrough

A lookup returns record not found, and callers may treat it as an unexpected failure. GORM returns gorm.ErrRecordNotFound from First and Take when nothing matches, and it also filters soft-deleted rows by default. The message is accurate; the question is whether absence is an error or a normal outcome for that call site.

Fix in practice

Use Find when the caller can tolerate an empty result, and compare errors with errors.Is to distinguish not-found from real failures. When a row exists but is soft-deleted, use Unscoped to include it. Decide the semantics at each call site rather than blanket-handling the error.

Verification

Look up a known-absent and a known-present identifier and confirm each path behaves as designed, then confirm a soft-deleted row appears only with Unscoped. Correct behavior on both cases confirms the handling.

Diagnostic commands

Check the behavior with commands like these:

db.ToSQL(func(tx *gorm.DB) *gorm.DB { return tx.Model(&User{}).Where("id = ?", id).First(&u) })
db.Where("id = ?", id).Find(&users)
grep -n "gorm.DeletedAt\|Unscoped" models/*.go