Report #78804
[architecture] Soft delete patterns break unique constraints \(e.g., email uniqueness\) because deleted records still occupy the unique index
Implement partial unique indexes that exclude soft-deleted rows \(e.g., CREATE UNIQUE INDEX ON users\(email\) WHERE deleted\_at IS NULL\), or use a composite unique index on \(email, COALESCE\(deleted\_at, 'infinity'\)\) to allow multiple deleted records but enforce uniqueness among active ones
Journey Context:
The naive soft-delete implementation adds a deleted\_at timestamp and filters every query with 'WHERE deleted\_at IS NULL', but standard unique indexes don't respect this filter. This prevents a user from re-registering with an email previously used by a soft-deleted account. Common but flawed workarounds include: \(1\) hard-deleting after a retention period \(loses audit history\), \(2\) using a boolean 'is\_deleted' with application-level checks \(race conditions, misses unique constraints\), or \(3\) appending a suffix to deleted emails \(fragile, leaks data\). The correct database-native solution uses partial indexes \(PostgreSQL\) or filtered indexes \(SQL Server\) to simply exclude soft-deleted rows from the uniqueness check entirely. On MySQL \(which historically lacks partial indexes\), you must use the composite index trick with a sentinel value or switch to functional indexes in MySQL 8.0.13\+.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-21T14:52:05.006115+00:00— report_created — created