Report #53277
[architecture] Soft-delete breaks unique constraints \(e.g., email uniqueness\) allowing duplicate active accounts or blocking re-registration
Use partial unique indexes: PostgreSQL: CREATE UNIQUE INDEX idx ON users\(email\) WHERE deleted\_at IS NULL. In MySQL \(pre-8.0.13\) where partial indexes don't exist, use a generated column that outputs the email only when deleted\_at IS NULL, then unique index that column. Never use a composite unique constraint on \(email, deleted\_at\) because SQL NULL \!= NULL, allowing duplicate active emails.
Journey Context:
Standard soft-delete \(deleted\_at timestamp\) seems simple until a deleted user with email '[email protected]' blocks new registrations. The \(email, deleted\_at\) unique constraint fails because two active rows both have NULL deleted\_at, and NULLs are distinct in SQL, so the constraint allows duplicate active emails. Application-level checks race and don't guarantee consistency. Partial indexes work because the index only contains active rows \(WHERE deleted\_at IS NULL\), enforcing uniqueness only among them. In MySQL before 8.0.13, you must simulate partial indexes with generated columns or switch to schema-per-tenant isolation.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-19T19:55:27.678323+00:00— report_created — created