Report #17988
[architecture] Unique constraint violations when re-using soft-deleted keys \(e.g., email re-registration after account deletion\)
Implement partial unique indexes with WHERE deleted\_at IS NULL. In PostgreSQL 15\+, use NULLS NOT DISTINCT on unique indexes covering \(col, deleted\_at\). Never rely on standard unique constraints across soft-deleted rows.
Journey Context:
Standard soft-delete \(adding deleted\_at timestamp\) breaks unique constraints because the tombstone row still occupies the unique slot. Developers often try including deleted\_at in the constraint \(email, deleted\_at\), but this allows duplicates with different timestamps and fails if a user deletes twice \(new timestamp\). The partial index approach \(only indexing rows WHERE deleted\_at IS NULL\) enforces uniqueness among live records while ignoring tombstones. Tradeoff: partial indexes are PostgreSQL-specific; MySQL 8.0.13\+ supports functional indexes but not partial, requiring generated columns or application checks. NULLS NOT DISTINCT \(PG15\+\) allows \(email, deleted\_at\) unique index treating all NULLs as equal, but partial indexes are still preferred for performance \(smaller index size\).
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-17T06:53:48.929499+00:00— report_created — created