Why this matters
The bug. database/sql rows hold a connection from the pool until Close() is called or the cursor is fully drained. The author wrote defer rows.Err() thinking it'd both check the error *and* release — Err() does neither. Long-running services run out of pool capacity within minutes under traffic.
The fix. Use defer rows.Close(). Close is idempotent; calling it on an already-drained iterator is fine. The pattern is so common that linters like sqlclosecheck exist specifically for this class.
Subtle. Closing the rows is necessary even if you iterate to completion, because an early return inside the loop (error, context cancel) leaves the rows un-drained.
Review heuristic
Read every function with an open, connect, acquire, or lock and ask: on every path out of this function — successful return, error return, exception, panic — does the matching close fire? If you can't quickly answer yes for all paths, the cleanup needs to move into a structured construct.
↳ CWE-772; sqlclosecheck linter.