Chuyển đến nội dung chính
Mô-đun 10 · Bài học 4 trong tổng số 440/57 trong khóa học~12 min
Bài học theo mô-đun (4/4)

Vấn đề N+1

Trong quá trình phát triển phụ trợ, một trong những lỗi thường gặp nhất làm ảnh hưởng nghiêm trọng đến hiệu suất API là vấn đề khét tiếng "N + 1 Truy vấn".

Nhà phát triển đang sử dụng các tóm tắt thư viện như "ORM" (Trình ánh xạ quan hệ đối tượng: ví dụ: Prisma hoặc Sequelize) thường không biết đến để nói chuyện với cơ sở dữ liệu từ Node.js.

Giải phẫu của thảm họa

Hãy tưởng tượng một ứng dụng muốn trả về toàn bộ danh sách trang chủ thương mại điện tử: Tất cả các sản phẩm cùng với chuỗi tên danh mục của từng sản phẩm. Nhìn từ phía Node.js, một lập trình viên thiếu kinh nghiệm sẽ viết vòng lặp lặp:

  1. products = await db.query("SELECT * FROM products"); (Một thao tác lớn, cồng kềnh 1)
  2. Nhà phát triển lặp lại (for..of) trên mỗi product trong mảng 100 được tìm thấy
  3. Ở mỗi bước, trong Node.js, chúng chờ đợi một truy vấn phụ lặp lại categoryName = await db.query("SELECT name FROM categories WHERE id = " + product.category_id); và nhúng nó vào kết quả (N lần lặp thứ cấp của các truy vấn nhỏ, chậm!)

Tổng số yêu cầu tới daemon Postgres: 1 + 100 lần lặp cục bộ = 101 các chuyến đi khứ hồi mạng vi mô giữa Node.js và DB. 101 chuyến đi khứ hồi gây ra chi phí mạng khủng khiếp và độ trễ xấu. Ở quy mô thực, N có thể không phải là 100 mà là 10.000 hàng hóa đơn để khám phá! API sẽ không quay trở lại trước khi đạt thời gian chờ 5xx.

Câu trả lời gốc thực sự: THAM GIA hoặc Tập hợp trong một truy vấn

Bằng cách sử dụng thành thạo các bản tóm tắt DBMS được viết bằng tay, thay vì các lệnh ORM tầm thường trong Nút, kết hợp các lần thực thi lại với nhau trong một phần nghìn giây trong một truy vấn duy nhất, không thể đánh bại được để giải quyết tình trạng lộn xộn "N+1":

SQL
SELECT
  p.id,
  p.name,
  c.name AS category_name
FROM products p
JOIN categories c ON p.category_id = c.id;

Tổng số chuyến đi khứ hồi API/DB: 1 truy vấn duy nhất, được hợp nhất, tối ưu hóa về mặt toán học bằng các chỉ mục riêng của Postgres, được xử lý hoàn toàn với độ trễ mạng bổ sung là 0 mili giây.

Thay thế tổng hợp JSON

Các phiên bản Postgres hiện đại thậm chí còn cho phép bạn trả về toàn bộ liên kết phức tạp được lồng dưới dạng JSON với json_agg, cho phép JavaScript của bạn nhận mảng products bao gồm cả mảng phụ gồm các bài đánh giá được định dạng mà không cần bất kỳ tập lệnh cục bộ nào! (Trong các lộ trình và thử thách nâng cao, bạn sẽ nghiên cứu mệnh đề JSONb của Postgres.)

Đến lượt bạn

tập thể dục#sql.m10.l4.e1
Nỗ lực: 0Đang tải…

Hãy giải quyết một vấn đề N+1 thực sự đang được thực hiện.\nNode sẽ chạy một lệnh Chọn toàn cục và sau đó lặp lại từng khách hàng để tìm tên của họ nếu họ muốn tóm tắt các đơn đặt hàng đã được giao sáng nay.\nHãy giải quyết vấn đề đó bằng cách in cho tôi một ảnh gốc duy nhất một THAM GIA giữa 'khách hàng' (sử dụng bí danh văn bản ban đầu là 'c') và 'đơn đặt hàng' (bí danh 'o').\nTrích xuất 2 trường tầm thường (bắt buộc tiền tố bí danh chính thức): 'c.first_name' (từ khách hàng) và 'o.status' (từ đơn hàng đang chờ xử lý).

Đang tải trình chỉnh sửa…
Hiển thị gợi ý

Cú pháp: SELECT c.first_name, o.status TỪ khách hàng c THAM GIA đơn hàng o ON c.id = o.customer_id;

Giải pháp khả dụng sau 3 lần thử

Khám phá hiệu quả của GROUP BY

tập thể dục#sql.m10.l4.e2
Nỗ lực: 0Đang tải…

Nếu Group-By hợp lý không được sử dụng bên trong cơ sở dữ liệu để lấy tổng số lượng trên mỗi hàng và danh mục, Node.js sẽ gọi DB không ngừng, tăng dần thành N+1.\nViết một truy vấn chọn trường 'thành phố' từ 'khách hàng' và số lượng Postgres tiêu chuẩn 'COUNT(id)' bằng cách sử dụng từ khóa đầu cuối để tổng hợp chúng một cách duy nhất theo tên.

Đang tải trình chỉnh sửa…
Hiển thị gợi ý

Một điều tầm thường: CHỌN thành phố, COUNT(id) TỪ khách hàng NHÓM THEO thành phố;

Giải pháp khả dụng sau 3 lần thử