Soft delete là "xóa giả": thay vì xóa thật, thêm cột deleted_at TIMESTAMPTZ để đánh dấu thời điểm xóa (tốt hơn is_deleted BOOLEAN vì biết thêm khi nào). Bản ghi vẫn nằm trong bảng.
Cách dùng: mọi query phải thêm WHERE deleted_at IS NULL. Tối ưu bằng partial index để chỉ quét bản ghi còn sống:
CREATE INDEX idx_users_active ON users(email) WHERE deleted_at IS NULL;Ưu điểm: khôi phục được, có audit trail (ai xóa, khi nào), FK không bị gãy.
Nhược điểm:
- Mọi query phải nhớ lọc deleted_at IS NULL — quên là lộ dữ liệu.
- Bảng phình dần theo thời gian.
- UNIQUE vẫn áp lên bản ghi đã xóa → email UNIQUE không cho đăng ký lại sau khi soft-delete. Khắc phục bằng partial unique index ... WHERE deleted_at IS NULL.
Vẫn cần hard delete cho: yêu cầu xóa theo GDPR, dữ liệu nhạy cảm (PII), và log/audit cũ quá thời hạn lưu trữ.
Soft delete is a "fake delete": instead of actually removing a row, you add a deleted_at TIMESTAMPTZ column marking when it was deleted (better than is_deleted BOOLEAN since it also records the time). The row stays in the table.
How to use: every query must add WHERE deleted_at IS NULL. Optimize with a partial index so you only scan live rows:
CREATE INDEX idx_users_active ON users(email) WHERE deleted_at IS NULL;Pros: recoverable, gives an audit trail (who deleted, when), FKs don't break.
Cons:
- Every query must remember to filter deleted_at IS NULL — forget it and you leak data.
- The table grows over time.
- UNIQUE still applies to deleted rows → email UNIQUE blocks re-registration after a soft-delete. Fix with a partial unique index ... WHERE deleted_at IS NULL.
You still need hard delete for: GDPR erasure requests, sensitive data (PII), and old log/audit records past their retention period.