H2H2Vì sao nên dùng soft delete?
Tránh “bay màu” dữ liệu ngoài ý muốn: Hard delete lỡ tay là đi luôn. Soft delete cho bạn một lớp an toàn.
Compliance và nghiệp vụ: Nhiều ngành yêu cầu lưu vết giao dịch, lịch sử. Soft delete giúp “xóa với người dùng” nhưng vẫn giữ dữ liệu để kiểm toán.
Không làm vỡ quan hệ dữ liệu: Bản ghi bị xóa mềm vẫn tồn tại nên foreign key, báo cáo lịch sử… không bị gãy.
Dễ khôi phục: Un-delete nhanh gọn khi cần.
H2H2Khi nào KHÔNG nên dùng?
Dữ liệu thật sự nhạy cảm cần xóa vĩnh viễn ngay (privacy by design).
Bảng log quá lớn, giá trị khôi phục thấp, chi phí lưu trữ cao.
Trường hợp có yêu cầu pháp lý “phải xóa hoàn toàn” trong thời hạn.
H2H2Các kỹ thuật triển khai phổ biến
H3H31) Cột đánh dấu
deleted_at
(datetime) – phổ biến nhất. Null = còn sống. Có timestamp = đã xóa.is_deleted
(boolean) – true/false. Đơn giản nhưng kém giàu thông tin hơndeleted_at
.
Ví dụ SQL:
-- Dùng deleted_at
SELECT *
FROM users
WHERE deleted_at IS NULL;
-- Dùng is_deleted
SELECT *
FROM users
WHERE is_deleted = FALSE;
H3H32) Global query filter / scope
Thiết lập một lớp filter mặc định để mọi truy vấn đều loại bản ghi đã xóa. Có thể thêm method để “bỏ qua” filter khi cần audit hoặc khôi phục.
H3H33) Interceptor/Repository/Service layer
Đóng gói logic xóa mềm tại tầng repository/service để code đồng nhất, dễ test, dễ thay đổi chiến lược sau này.
H3H34) Archiving (lưu trữ)
Sau một thời gian, chuyển dữ liệu đã xóa mềm sang bảng archive để gọn bảng chính và tối ưu chỉ mục.
H2H2Triển khai trong các framework phổ biến
H3H3Laravel
Bật soft delete với trait
SoftDeletes
trong Eloquent Model.Migration thêm cột
deleted_at
bằngsoftDeletes()
.
// migration
Schema::table('posts', function (Blueprint $table) {
$table->softDeletes(); // tạo cột deleted_at
});
// model
use Illuminate\\Database\\Eloquent\\SoftDeletes;
class Post extends Model
{
use SoftDeletes; // tự động filter deleted_at IS NULL
}
// xóa mềm
Post::find($id)->delete();
// truy vấn bao gồm cả bản ghi đã xóa
Post::withTrashed()->get();
// chỉ các bản ghi đã xóa
Post::onlyTrashed()->get();
// khôi phục
Post::withTrashed()->find($id)->restore();
// xóa vĩnh viễn (bỏ qua soft delete)
Post::withTrashed()->find($id)->forceDelete();
Tài liệu: Laravel Docs – Eloquent: Soft Deleting.
H3H3Django
Sử dụng package như
django-safedelete
để có manager mặc định lọc record đã xóa, hỗ trợ restore và hard delete.
Repo: django-safedelete trên GitHub.
H3H3Ruby on Rails
Dùng gem
discard
hoặcparanoia
để códiscarded_at
và scope mặc định.
Gems: discard trên RubyGems • paranoia trên GitHub.
H3H3Entity Framework Core (.NET)
Tạo cột
IsDeleted
hoặcDeletedAt
, sau đó cấu hình Global Query Filter trongOnModelCreating
.
modelBuilder.Entity<Post>().HasQueryFilter(p => p.DeletedAt == null);
Tài liệu: Entity Framework Core Docs.
H2H2Ưu và nhược điểm
H3H3Ưu điểm
Khôi phục dữ liệu dễ dàng.
Không phá vỡ quan hệ khóa ngoại.
Phục vụ audit, báo cáo lịch sử, compliance.
Trải nghiệm người dùng “xóa nhưng còn cứu”.
H3H3Nhược điểm
Tăng dung lượng bảng.
Truy vấn phức tạp hơn nếu quên filter.
Hiệu năng giảm nếu chỉ mục chưa tối ưu cho cột xóa mềm.
Uniqueness constraint có thể rắc rối khi khôi phục bản ghi cũ.
H3H3Cách khắc phục
Lập lịch purge xóa vĩnh viễn sau N ngày hoặc chuyển sang bảng archive.
Tạo index cho
deleted_at
hoặcis_deleted
.Dùng Global Scope/Filter để tránh quên điều kiện.
Với uniqueness, cân nhắc unique có điều kiện, thêm suffix khi khôi phục, hoặc kiểm tra va chạm trước khi restore.
H2H2Best practices “đỡ đau đầu”
Nhất quán: đã chọn soft delete thì áp dụng xuyên suốt domain liên quan.
Minh bạch: giải thích rõ với người dùng về cơ chế xóa.
Phân quyền: chỉ vai trò phù hợp mới được xem và khôi phục bản ghi đã xóa.
Audit log: ghi lại ai xóa, lúc nào, lý do.
Observability: metric số lượng bản ghi đã xóa, thời gian khôi phục, kích thước bảng.
Test kỹ: xóa, restore, hard delete, cascade, uniqueness…
H2H2Một số pattern thường gặp
Soft delete + Versioning: theo dõi lịch sử thay đổi và cả trạng thái xóa.
Soft delete + Audit logging: full trace cho compliance.
Soft delete + Archiving: giữ bảng chính gọn nhẹ, nhanh hơn.
H2H2FAQ
Soft delete có an toàn không?
An toàn hơn hard delete trong đa số nghiệp vụ vì có thể khôi phục. Nhưng đừng coi là “bất tử” – vẫn cần backup, audit, và quy trình purge.
Có nên dùng cả is_deleted
và deleted_at
?
Không bắt buộc. deleted_at
đã đủ linh hoạt. Chỉ dùng boolean nếu yêu cầu cực đơn giản.
Làm sao để không quên filter?
Thiết lập global scope/filter hoặc repository mặc định lọc. Chỉ “bỏ qua” filter khi có chủ đích.
H2H2Checklist triển khai nhanh
[ ] Thêm cột
deleted_at
và index.[ ] Bật global scope/filter.
[ ] Viết service
delete
,restore
,forceDelete
.[ ] Lên lịch purge, hoặc chuyển archive.
[ ] Thêm audit log.
[ ] Viết test cho xóa, restore, uniqueness, cascade.
H2H2Tài liệu tham khảo
Laravel Docs – Eloquent: Soft Deleting: Laravel Docs
Django Safe Delete: GitHub – django-safedelete
Rails gem discard: RubyGems – discard
Rails gem paranoia: GitHub – paranoia
Entity Framework Core Docs: Learn Microsoft