Vấn Đề Với Cách Làm “Schema Tới Đâu, Làm Tới Đó”
Bạn bắt đầu một dự án mới. Bạn mở SQL client, tạo bảng users, thêm vài cột, rồi nhận ra mình cần bảng orders, tiếp theo là bảng products, rồi đến bảng junction — và ở đâu đó trong quá trình đó, các foreign key lại trỏ sai chiều còn tên cột thì không thống nhất giữa các bảng.
Đây là thứ tôi gọi là “schema tới đâu làm tới đó” — và hầu hết lập trình viên đều đã từng trải qua. Cách khắc phục không phải là kỷ luật hơn khi viết SQL. Cách khắc phục là dành 20 phút vẽ schema trực quan trước khi viết một dòng code nào.
Đó chính xác là mục đích dbdiagram.io được xây dựng. Đây là công cụ ERD chạy trên trình duyệt cho phép bạn phác thảo schema database bằng ngôn ngữ markup đơn giản gọi là DBML, sau đó tự động tạo SQL cho PostgreSQL, MySQL hoặc SQL Server chỉ với một cú click.
Khái Niệm Cơ Bản: ERD, Thực Thể và Quan Hệ
Sơ đồ thực thể-quan hệ (ERD) là bản đồ trực quan của database. Nó hiển thị các bảng (thực thể), các cột (thuộc tính) và cách chúng kết nối với nhau (quan hệ). Hãy nghĩ nó như bản vẽ thiết kế của kiến trúc sư — bạn sẽ không xây nhà mà không có bản vẽ đó.
Thực Thể và Thuộc Tính
Thực thể về cơ bản là một bảng. Mỗi thực thể có các thuộc tính — các cột với tên và kiểu dữ liệu. Một thực thể User có thể có các thuộc tính như id, email và created_at. Phác thảo những thứ này trước khi viết SQL buộc bạn phải suy nghĩ xem dữ liệu nào thực sự cần tồn tại, so với những gì bạn đang đoán mình sẽ cần.
Quan Hệ và Bản Số
Quan hệ mô tả cách các thực thể kết nối với nhau. Các loại chính bao gồm:
- Một-Một (1:1) — Một người dùng có một hồ sơ.
- Một-Nhiều (1:N) — Một người dùng có thể đặt nhiều đơn hàng.
- Nhiều-Nhiều (M:N) — Nhiều đơn hàng có thể chứa nhiều sản phẩm, được xử lý thông qua bảng junction.
Xác định đúng bản số ở giai đoạn thiết kế sẽ tiết kiệm hàng giờ refactoring về sau. Vẽ trực quan giúp phát hiện lỗi dễ dàng hơn nhiều trước khi chúng được đưa vào production migrations.
Cú Pháp DBML
DBML (Database Markup Language) là DSL được sử dụng bởi dbdiagram.io. Nó rõ ràng và dễ đọc — gần với việc viết tài liệu hơn là viết SQL thuần. Đây là ví dụ tối giản nhất:
Table users {
id integer [primary key, increment]
email varchar [not null, unique]
password_hash varchar [not null]
created_at timestamp [default: `now()`]
}
Mỗi block Table định nghĩa một bảng. Định nghĩa cột theo mẫu: column_name data_type [constraints]. Quan hệ sử dụng câu lệnh Ref: bên ngoài block bảng:
Ref: orders.user_id > users.id // Nhiều-một
Chiều mũi tên quan trọng: > nghĩa là nhiều-một, < nghĩa là một-nhiều và - nghĩa là một-một.
Thực Hành: Thiết Kế Schema Thương Mại Điện Tử
Truy cập dbdiagram.io và click Create your diagram. Không cần tài khoản để bắt đầu. Giao diện chia thành hai bảng: code editor ở bên trái, sơ đồ trực quan cập nhật trực tiếp ở bên phải. Khi bạn gõ, sơ đồ cập nhật ngay lập tức.
Chúng ta sẽ thiết kế schema cho một nền tảng thương mại điện tử đơn giản. Chúng ta cần users, products, orders và một cách xử lý nhiều sản phẩm trên mỗi đơn hàng.
Bước 1: Xác Định Thực Thể Trước
Trước khi viết bất kỳ DBML nào, hãy liệt kê những gì bạn cần lưu trữ — không phải cách bạn sẽ lưu. Với hệ thống thương mại điện tử này:
- Users — những người mua hàng
- Products — các mặt hàng để bán
- Orders — một sự kiện mua hàng gắn với người dùng
- Order Items — bảng junction liên kết orders và products
Bốn thực thể. Bây giờ hãy ghi nhanh các thuộc tính chính mà mỗi thực thể cần. Bước năm phút này thôi đã đủ để ngăn ngừa hầu hết các lỗi schema phổ biến.
Bước 2: Viết DBML
Dán nội dung sau vào editor của dbdiagram.io:
Table users {
id integer [primary key, increment]
email varchar [not null, unique]
full_name varchar
created_at timestamp [default: `now()`]
}
Table products {
id integer [primary key, increment]
name varchar [not null]
description text
price decimal(10,2) [not null]
stock_quantity integer [default: 0]
created_at timestamp [default: `now()`]
}
Table orders {
id integer [primary key, increment]
user_id integer [not null]
status varchar [default: 'pending'] // pending, paid, shipped, delivered
total_amount decimal(10,2)
created_at timestamp [default: `now()`]
}
Table order_items {
id integer [primary key, increment]
order_id integer [not null]
product_id integer [not null]
quantity integer [not null]
unit_price decimal(10,2) [not null] // giá tại thời điểm mua hàng
}
Khi bạn gõ, sơ đồ vẽ mỗi bảng như một hộp với các cột được liệt kê. Bạn sẽ ngay lập tức nhận ra các tên cột không thống nhất hoặc thiếu cột — những thứ dễ bị bỏ sót trong các file SQL thuần.
Bước 3: Thêm Quan Hệ
Bây giờ hãy kết nối các bảng. Thêm các câu lệnh Ref: này bên dưới phần định nghĩa bảng:
Ref: orders.user_id > users.id // Nhiều đơn hàng thuộc về một người dùng
Ref: order_items.order_id > orders.id // Nhiều items thuộc về một đơn hàng
Ref: order_items.product_id > products.id // Nhiều items tham chiếu đến một sản phẩm
Sơ đồ ngay lập tức vẽ các đường kết nối. Bạn sẽ thấy order_items nằm ở giữa, kết nối orders và products — đúng là mẫu nhiều-nhiều mà nó được thiết kế để xử lý.
Lưu ý tôi đã lưu unit_price trong order_items thay vì chỉ tham chiếu đến products.price. Đó là có chủ đích — giá sản phẩm thay đổi theo thời gian, nhưng các đơn hàng lịch sử phải phản ánh đúng số tiền khách hàng đã trả. Những quyết định thiết kế như vậy dễ phát hiện hơn nhiều khi nhìn vào ERD, thay vì khi chúng đã bị chôn vùi trong các file migration sáu tháng sau.
Bước 4: Xuất sang SQL
Khi sơ đồ trông ổn rồi, hãy click nút Export ở góc trên bên phải. Chọn database đích (PostgreSQL, MySQL hoặc MSSQL). dbdiagram.io sẽ tạo ra các câu lệnh CREATE TABLE đầy đủ, bao gồm cả ràng buộc foreign key:
CREATE TABLE "users" (
"id" integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
"email" varchar UNIQUE NOT NULL,
"full_name" varchar,
"created_at" timestamp DEFAULT (now())
);
CREATE TABLE "order_items" (
"id" integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
"order_id" integer NOT NULL,
"product_id" integer NOT NULL,
"quantity" integer NOT NULL,
"unit_price" decimal(10,2) NOT NULL
);
ALTER TABLE "orders" ADD FOREIGN KEY ("user_id") REFERENCES "users" ("id");
ALTER TABLE "order_items" ADD FOREIGN KEY ("order_id") REFERENCES "orders" ("id");
ALTER TABLE "order_items" ADD FOREIGN KEY ("product_id") REFERENCES "products" ("id");
Sao chép vào database client hoặc công cụ migration của bạn là xong. Không cần viết boilerplate, không lo lỗi đánh máy trong cú pháp ràng buộc.
Bonus: Import Schema Hiện Có
Nếu bạn đã có production database và muốn tài liệu hóa nó trực quan, hãy dump schema trước:
# PostgreSQL — chỉ schema, không có dữ liệu
pg_dump --schema-only -U your_user -d your_database > schema.sql
# MySQL
mysqldump --no-data -u your_user -p your_database > schema.sql
Sau đó trong dbdiagram.io: click Import → Import from PostgreSQL (hoặc MySQL) → dán SQL vào. Công cụ sẽ reverse-engineer ra DBML và vẽ sơ đồ cho bạn. Rất tiện lợi khi onboarding vào một codebase cũ.
Mẹo Để Thiết Kế Schema Gọn Gàng Hơn
Một vài điều tôi đúc kết được sau khi áp dụng quy trình này trên nhiều dự án:
- Đặt tên cột nhất quán. Nếu bạn dùng
created_attrong một bảng, hãy dùng nó ở mọi nơi — không phảicreated,date_createdhaycreation_datetùy theo từng bảng. - Luôn lưu snapshot giá và số lượng. Đừng bao giờ tra cứu trực tiếp từ
productscho dữ liệu đơn hàng lịch sử. Hãy lưuunit_pricevàquantitytại thời điểm giao dịch. - Thêm index cho foreign key. DBML hỗ trợ block
indexesbên trong định nghĩa bảng. Foreign key không có index sẽ gây full table scan khi join — điều này sẽ làm khổ bạn khi dữ liệu tăng lên. - Dùng
TableGroupcho schema lớn. Nếu bạn có hơn 20 bảng, hãy nhóm các bảng liên quan bằng blockTableGroupđể giữ sơ đồ dễ đọc.
Table order_items {
id integer [primary key, increment]
order_id integer [not null]
product_id integer [not null]
quantity integer [not null]
unit_price decimal(10,2) [not null]
indexes {
order_id
product_id
}
}
TableGroup ecommerce {
users
products
orders
order_items
}
Thêm một mẹo nữa về quy trình: khi seed database với dữ liệu ban đầu từ bảng tính, tôi thường cần chuyển đổi các file CSV trước khi import. Tôi dùng toolcraft.app/vi/tools/data/csv-to-json để chuyển đổi CSV sang JSON nhanh chóng — công cụ chạy hoàn toàn trên trình duyệt nên không có dữ liệu nào rời khỏi máy bạn, điều này quan trọng khi dữ liệu seed chứa thông tin nhạy cảm như hồ sơ khách hàng hay thông tin giá cả.
Tổng Kết
Dành 20 phút với dbdiagram.io trước migration đầu tiên sẽ tiết kiệm hàng giờ refactoring về sau. Cú pháp DBML nhanh để học, sơ đồ trực tiếp bắt lỗi quan hệ ngay lập tức, và tính năng xuất SQL giúp bạn bỏ qua hoàn toàn phần viết boilerplate CREATE TABLE.
Thói quen cần xây dựng là: bất cứ khi nào bắt đầu một tính năng liên quan đến database, hãy phác thảo các bảng trước. Ngay cả một ERD thô sơ với ba bốn bảng cũng buộc bạn phải suy nghĩ về quan hệ, cách đặt tên và kiểu dữ liệu — trước khi cam kết với một migration khó rollback.
Hãy lấy ví dụ thương mại điện tử trong bài này và thay thế bằng domain của riêng bạn — một blog CMS, hệ thống quản lý kho, hay mô hình đăng ký SaaS. Mẫu ERD-first vẫn giữ nguyên, và bản thân bạn trong tương lai sẽ biết ơn mỗi khi không phải reverse-engineer schema của chính mình.

