Khi tụi mình chuẩn hóa dữ liệu một cách hoàn hảo, mỗi bảng sẽ cực kỳ gọn gàng, và thông tin trong đó chỉ tuân theo một nguyên tắc duy nhất. Nhưng để chạy các truy vấn thực tế (ví dụ như "Những sinh viên nào đã đăng ký khóa SQL?") thì có thể phải join cả đống bảng lại với nhau. Càng nhiều bảng, truy vấn càng phức tạp, hệ thống càng phải "cày cuốc" nhiều hơn.
Chắc là bạn đã quen với JOIN từ các bài trước rồi nhỉ. Đây là ví dụ truy vấn mà bạn có thể cần khi làm việc với một database được thiết kế chuẩn hóa:
SELECT students.name, courses.title
FROM students
JOIN enrollments ON students.id = enrollments.student_id
JOIN courses ON enrollments.course_id = courses.id
WHERE courses.title = 'SQL';
Nghe thì đơn giản, nhưng bên dưới thì server phải làm việc cực nhọc: đọc từng bảng, ghép dữ liệu, lọc... Nếu bảng cực kỳ to thì sao? Rõ ràng hiệu năng sẽ tụt dốc.
Cuộc chiến: chuẩn hóa vs tốc độ
May mắn (hay xui xẻo?), ngoài đời thực database luôn là sự thỏa hiệp. Chuẩn hóa hoàn toàn giúp dữ liệu nhất quán, nhưng lại làm chậm các truy vấn phức tạp. Nếu database dùng cho phân tích và báo cáo, đôi khi denormalization lại có lợi hơn. Nó giống như thay vì 10 hộp nhỏ thì gom hết vào một cái rương to: lấy dữ liệu nhanh hơn, nhưng sắp xếp lại thì mệt hơn.
Khi nào nên "thở phào" với chuẩn hóa?
Có những trường hợp denormalization lại hợp lý hơn:
Các aggregate hay dùng
Ví dụ, tưởng tượng hệ thống mỗi ngày đều phải đếm số sinh viên của từng khóa học. Nếu chuẩn hóa, bạn sẽ phảiJOIN và
COUNT() liên tục. Thay vào đó, có thể thêm một cột
student_count vào bảng "Courses", tự động cập nhật mỗi khi có thêm/xóa sinh viên.
-- Cột denormalized
UPDATE courses
SET student_count = (
SELECT COUNT(*)
FROM enrollments
WHERE enrollments.course_id = courses.id
);
Các báo cáo chạy thường xuyên
Nếu khách hàng của bạn ngày nào cũng hỏi "Ai, ở đâu, khi nào đã mua gì?", thì tốt nhất là lưu sẵn một bảng denormalized kiểu "Tên khách, sản phẩm, ngày". Bảng sẽ to hơn, nhưng lấy dữ liệu thì siêu nhanh.
Đọc nhiều, ghi ít
Khi database chủ yếu để đọc (kiểu phân tích), thì hy sinh chuẩn hóa để lấy tốc độ cũng đáng.
Giảm số lần join khi quan hệ phức tạp
Nếu giữa các bảng có quan hệ lồng nhau (nested), vàJOIN thành ác mộng, thì bỏ bớt vài lớp chuẩn hóa đi cho nhẹ đầu.
Ví dụ: denormalization tăng tốc thế nào?
Có các bảng chuẩn hóa của một shop online:
Bảng products |
Bảng orders |
Bảng order_items |
|---|---|---|
| id | id | id |
| name | date | order_id |
| price | customer_id | product_id |
| quantity |
Mỗi đơn hàng (orders) gồm nhiều dòng (order_items). Tính xem shop kiếm được bao nhiêu tiền:
SELECT SUM(order_items.quantity * products.price) AS total_revenue
FROM order_items
JOIN products ON order_items.product_id = products.id;
Việc join order_items và products sẽ làm truy vấn chậm nếu dữ liệu lớn.
Cấu trúc denormalized
Giờ thử tưởng tượng trong bảng order_items có thêm cột total_price (denormalization):
Bảng order_items |
|---|
| id |
| order_id |
| product_id |
| quantity |
| total_price |
Giờ truy vấn cực kỳ đơn giản:
SELECT SUM(total_price) AS total_revenue
FROM order_items;
Không cần JOIN nữa, tốc độ tăng hẳn.
Bài tập thực hành: tối ưu database "Bán hàng"
Cho: các bảng chuẩn hóa
Bảng products |
Bảng sales |
|---|---|
| id | id |
| name | product_id |
| price | date |
| quantity |
Bài toán: tăng tốc truy vấn kiểu "Tổng doanh thu từng sản phẩm là bao nhiêu?".
Bước 1: thêm cột total_price vào bảng sales:
ALTER TABLE sales ADD COLUMN total_price NUMERIC;
Bước 2: cập nhật dữ liệu cho cột này với dữ liệu hiện có:
UPDATE sales
SET total_price = quantity * (
SELECT price
FROM products
WHERE products.id = sales.product_id
);
Bước 3: truy vấn nhanh hơn hẳn:
SELECT product_id, SUM(total_price) AS total_revenue
FROM sales
GROUP BY product_id;
Nhưng! Denormalization cũng có giá của nó
Bạn hiểu mà, "nhanh hơn" không phải lúc nào cũng "tốt hơn". Khi denormalization, sẽ có vài vấn đề:
Tốn dung lượng lưu trữ
Cộttotal_price là bản sao dữ liệu, chiếm thêm chỗ.
Khó cập nhật
Nếu giá sản phẩm thay đổi trong bảng products, bạn phải tự tay cập nhật lại cột total_price. Dễ bị lệch dữ liệu lắm.
Lỗi khi thêm, sửa, xóa
Thông tin rất dễ bị "lệch pha" nếu quên cập nhật dữ liệu denormalized. Ví dụ, giá sản phẩm đổi mà không tự động cập nhật.
Cân bằng: làm sao tìm điểm vàng?
Chọn ưu tiên: tốc độ hay cấu trúc? Nếu database chủ yếu để đọc, hãy tối ưu cho truy vấn.
Denormalization có chọn lọc. Chỉ dùng cho các số liệu, báo cáo quan trọng.
Tự động hóa cập nhật dữ liệu denormalized. Dùng trigger hoặc job để tránh lệch dữ liệu.
GO TO FULL VERSION