CodeGym /Các khóa học /SQL SELF /Cân bằng giữa chuẩn hóa và hiệu năng

Cân bằng giữa chuẩn hóa và hiệu năng

SQL SELF
Mức độ , Bài học
Có sẵn

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ải JOINCOUNT() 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_itemsproducts 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ột total_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.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION