- Bài viết tổng hợp các pattern thực tiễn giúp sử dụng Postgres hiệu quả và an toàn hơn
- Mỗi pattern tuy nhỏ nhưng khi tích lũy lại sẽ tạo ra khác biệt lớn
Sử dụng khóa chính UUID
- UUID có tính ngẫu nhiên nên có nhược điểm về sắp xếp và hiệu năng chỉ mục
- Chiếm nhiều không gian hơn ID số
- Nhưng có các ưu điểm sau
- Có thể tạo UUID mà không cần kết nối tới DB
- Có thể công khai ra bên ngoài một cách an toàn
- Có thể dùng
gen_random_uuid() để tự động tạo UUID làm khóa chính
Luôn thêm trường created_at và updated_at
- Khi debug, việc biết thời điểm bản ghi được tạo và thay đổi là cực kỳ hữu ích
- Có thể cấu hình để
updated_at được tự động cập nhật thông qua trigger
- Hàm chỉ cần tạo một lần, còn trigger thì phải áp dụng cho từng bảng
Đặt on update/delete restrict cho khóa ngoại
- Khi thiết lập ràng buộc khóa ngoại, nên luôn dùng
on update restrict on delete restrict
- Giúp ngăn việc xóa dây chuyền xảy ra ngoài ý muốn khi xóa dữ liệu
- Lưu trữ thì rẻ nhưng khôi phục dữ liệu rất khó, nên xử lý theo hướng thận trọng
Khuyến nghị dùng schema
- Schema mặc định là
public, nhưng khi ứng dụng lớn dần thì nên tách sang schema riêng
- Schema hoạt động như namespace, và vẫn có thể join giữa các schema khác nhau
- Càng có nhiều bảng thì việc tận dụng schema càng có lợi cho tính dễ đọc và bảo trì
Dùng pattern bảng enum
- Thay vì dùng enum type hoặc check constraint của PostgreSQL, cách dùng bảng enum linh hoạt hơn
- Khi quản lý giá trị enum bằng bảng riêng, có thể thêm metadata hoặc mở rộng giá trị enum dễ dàng
- Dùng khóa ngoại để tham chiếu giá trị trong bảng enum và giữ ràng buộc
Đặt tên bảng ở dạng số ít
- Tên bảng nên để ở dạng số ít thay vì số nhiều
- Khi viết query, dạng số ít rõ ràng hơn, còn số nhiều có thể gây nhầm lẫn về ngữ nghĩa hoặc sở hữu cách
Đặt tên bảng join theo kiểu cơ học
- Với bảng join cho quan hệ nhiều-nhiều, cách an toàn và rõ ràng là ghép tên của hai bảng lại với nhau
- Ví dụ:
person_pet
- Thêm unique index cho tổ hợp này để tránh trùng lặp
Dùng soft delete thay vì xóa hẳn
- Thay vì xóa dữ liệu thật sự, nên dùng trường timestamp như
revoked_at để biểu thị thời điểm bị xóa
- Không chỉ biết có bị xóa hay không, mà còn theo dõi được xóa khi nào
- Timestamp cung cấp nhiều thông tin hơn giá trị Boolean
Biểu diễn trạng thái (Status) bằng bảng log
- Thay vì biểu diễn trạng thái bằng một cột duy nhất, hãy lưu lịch sử thay đổi trạng thái vào bảng riêng
- Thời điểm trạng thái phát sinh được ghi rõ bằng cột
valid_at
- Thiết lập cờ
latest cùng unique index + trigger để truy vấn trạng thái mới nhất nhanh chóng
- Cách này hữu ích trong xử lý sự kiện bất đồng bộ hoặc khi thứ tự có thể bị xáo trộn
Thêm system_id cho các hàng đặc biệt
- Ngoài bảng enum, đôi khi còn cần một số "hàng hệ thống" cụ thể
- Thêm trường text
system_id dưới dạng nullable và đặt unique index
- Có thể truy vấn rõ ràng hàng cụ thể thông qua
system_id
Chỉ dùng View ở mức tối thiểu
- View hữu ích để trừu tượng hóa các query phức tạp nhưng khó bảo trì
- Khi bỏ cột thì cần tạo lại view
- Nếu tạo view chồng lên view sẽ phát sinh vấn đề về hiệu năng và độ dễ đọc
- Chỉ nên dùng cẩn trọng trong phạm vi thực sự cần thiết
Tận dụng mạnh query JSON
- Postgres không chỉ mạnh ở lưu trữ JSON mà còn rất mạnh ở các query trả về JSON
- Có thể trả về các quan hệ lồng nhau dưới dạng JSON chỉ với một query
- Có thể lấy toàn bộ dữ liệu cần thiết trong một lần mà không gặp vấn đề N+1
- Nhược điểm: mất thông tin kiểu dữ liệu, và phải nạp toàn bộ dữ liệu vào bộ nhớ cùng lúc
- Nhưng lợi thế về hiệu năng hoặc cấu trúc thường lớn hơn
4 bình luận
> Bảng join nên được đặt tên một cách máy móc
Mình thấy việc bản thân có một quy tắc đặt tên như thế này đã là điều hay rồi~
Nếu xét đến UUID7 thì chẳng phải vẫn có thể sắp xếp theo thứ tự thời gian sao?
Có lẽ cũng đáng tham khảo bài viết về việc sử dụng UUID làm khóa chính trong PostgreSQL.
Cách thêm timestamp khi soft delete khá hay. Nếu dùng UUID làm khóa chính thì sẽ không sắp xếp theo thời gian được, nên có lẽ dùng snowflake id hoặc ulid cũng là một lựa chọn tốt. Tuy nhiên trong trường hợp này, mỗi server sẽ phải giữ sequence number.