Tối ưu hiệu năng với Split Query trong EF Core

Công nghệ - 16/05/2025 01:45:03

🚀 Dùng .Include() trong EF Core liệu có đang âm thầm làm chậm hệ thống của bạn? Bài viết này sẽ hé lộ sự thật về Split Query vs Single Query qua các benchmark thực tế và mẹo tối ưu hiệu quả – đọc để khám phá ngay!

Xin chào các bạn, hôm nay, chúng ta sẽ cùng khám phá một tính năng mạnh mẽ của Entity Framework Core (EF Core): Split Query. Mình sẽ phân tích chi tiết cách Split Query hoạt động, so sánh tốc độ và hiệu năng với Single Query(truy vấn thường), đồng thời đưa ra các ví dụ thực tế và khuyến nghị để bạn áp dụng hiệu quả trong dự án.

Nếu bạn đang đau đầu với hiệu năng truy vấn dữ liệu liên kết, hi vọng bài viết này sẽ giúp bạn tìm ra giải pháp tối ưu hơn.


Split Query và Single Query: Chúng là gì?

Trước khi đi vào so sánh, hãy làm rõ hai khái niệm này:

  • Single Query (tạm gọi là Truy vấn đơn): Đây là cách mặc định của EF Core khi tải dữ liệu liên kết (related data) bằng các method như Include hoặc ThenInclude. EF Core tạo một câu lệnh SQL duy nhất với các lệnh JOIN để lấy toàn bộ dữ liệu từ các bảng liên quan. Ví dụ, nếu bạn truy vấn danh sách đơn hàng (Orders) kèm chi tiết đơn hàng (OrderDetails), EF Core sẽ dùng một truy vấn SQL với LEFT JOIN hoặc INNER JOIN.
  • Split Query (Truy vấn chia nhỏ): Được giới thiệu từ EF Core 5.0, Split Query chia truy vấn thành nhiều câu SQL nhỏ hơn, mỗi câu xử lý một phần dữ liệu (ví dụ: một câu cho bảng Orders, một câu cho bảng OrderDetails). Điều này giúp giảm lượng dữ liệu truyền tải và tránh các vấn đề hiệu năng khi làm việc với dữ liệu lớn hoặc nhiều mối quan hệ.

So sánh hiệu năng: Split Query vs Single Query

Để đánh giá tốc độ và hiệu năng, mình đã tham khảo các benchmark từ tài liệu chính thức của Microsoft và các bài phân tích từ cộng đồng lập trình viên. Dưới đây là những so sánh chi tiết qua các kịch bản thực tế.

1. Kịch bản 1: Dữ liệu nhỏ, ít mối quan hệ

Khi truy vấn một bảng với ít dữ liệu liên kết (ví dụ: danh sách công ty không kèm sản phẩm), Single Query thường nhanh hơn do chỉ cần một lần giao tiếp với database (round-trip).

Benchmark (theo Code Maze):

  • Single Query: 16.64 ms, tiêu thụ 4.04 MB bộ nhớ.
  • Split Query: 18.97 ms, tiêu thụ 4.25 MB bộ nhớ.

Phân tích:

  • Single Query nhanh hơn một chút (khoảng 2 ms) vì chỉ cần một truy vấn SQL, giảm độ trễ mạng.
  • Sự khác biệt về bộ nhớ không đáng kể (4.04 MB so với 4.25 MB).

Kết luận: Với dữ liệu nhỏ và ít mối quan hệ, Single Query là lựa chọn tốt nhờ đơn giản và nhanh chóng.

2. Kịch bản 2: Dữ liệu lớn, nhiều mối quan hệ

Khi truy vấn dữ liệu với nhiều mối quan hệ "một-nhiều" (ví dụ: danh sách công ty kèm sản phẩm), Split Query vượt trội nhờ tránh hiện tượng Cartesian Explosion – khi số hàng trả về tăng đột biến do các lệnh JOIN.

Benchmark (theo Code Maze):

  • Single Query: 200.60 ms, tiêu thụ 46.93 MB bộ nhớ.
  • Split Query: 35.62 ms, tiêu thụ 8.35 MB bộ nhớ.

Phân tích:

  • Split Query nhanh hơn gấp 5-6 lần (35.62 ms so với 200.60 ms) vì nó chia nhỏ truy vấn, tránh việc lặp lại dữ liệu dư thừa từ các bảng liên kết.
  • Tiêu thụ bộ nhớ của Split Query thấp hơn đáng kể (8.35 MB so với 46.93 MB), giúp giảm áp lực lên hệ thống.

Kết luận: Với dữ liệu lớn và nhiều mối quan hệ, Split Query là lựa chọn tối ưu để cải thiện tốc độ và tiết kiệm tài nguyên.

3. Kịch bản 3: Cập nhật dữ liệu

Một bài viết trên Medium đã kiểm tra hiệu năng khi xử lý 7300 sản phẩm với các kịch bản cập nhật:

Cập nhật 3500 sản phẩm:

  • Split Query (lọc trong RAM): 3.15 giây.
  • Single Query (lọc trong RAM): 4.2 giây.

Cập nhật 10 sản phẩm:

  • Split Query (chỉ đọc để cập nhật): 1.20 giây.
  • Single Query (chỉ đọc để cập nhật): 0.6 giây.

Phân tích:

  • Split Query hiệu quả hơn khi đọc và lọc dữ liệu lớn trong RAM (3.15 giây so với 4.2 giây).
  • Single Query nhanh hơn trong các trường hợp cập nhật nhỏ lẻ (0.6 giây so với 1.20 giây) vì giảm số lần round-trip.

Kết luận: Split Query phù hợp cho các tác vụ xử lý dữ liệu lớn, trong khi Single Query tốt hơn cho các cập nhật nhỏ.

4. Phân tích từ Microsoft

Theo tài liệu trên Microsoft Learn, Single Query có thể gây ra hiệu năng kém khi dữ liệu lớn do Cartesian Explosion. Split Query, mặc dù tăng số lần round-trip, lại giảm đáng kể lượng dữ liệu truyền tải, giúp tiết kiệm băng thông và bộ nhớ.


Ví dụ thực tế: Truy vấn đơn hàng và chi tiết

Hãy xem một ví dụ cụ thể để minh họa sự khác biệt giữa hai phương pháp.

Với Single Query

var orders = dbContext.Orders
    .Include(o => o.OrderDetails)
    .ThenInclude(od => od.Product)
    .ToList();

SQL được tạo (giả định):

SELECT o.*, od.*, p.*
    FROM Orders o
    LEFT JOIN OrderDetails od ON o.Id = od.OrderId
    LEFT JOIN Products p ON od.ProductId = p.Id

Vấn đề tiềm ẩn: Nếu một đơn hàng có 10 chi tiết và mỗi chi tiết liên kết với một sản phẩm, số hàng trả về có thể rất lớn, dẫn đến tiêu tốn bộ nhớ và thời gian xử lý.

Với Split Query

var orders = dbContext.Orders

    .Include(o => o.OrderDetails)

    .ThenInclude(od => od.Product)

    .AsSplitQuery()

    .ToList();

SQL được tạo (giả định):

 -- Truy vấn 1: Lấy Orders
SELECT * FROM Orders

-- Truy vấn 2: Lấy OrderDetails
SELECT * FROM OrderDetails WHERE OrderId IN (...)

-- Truy vấn 3: Lấy Products
SELECT * FROM Products WHERE Id IN (...)

Lợi ích: Mỗi truy vấn chỉ lấy dữ liệu cần thiết, giảm số hàng trả về và tránh lặp lại dữ liệu dư thừa.


Khi nào nên dùng Single Query hay Split Query?

Dựa trên các phân tích và kinh nghiệm thực tế, mình đưa ra các khuyến nghị sau:

Sử dụng Single Query khi:

  • Dữ liệu nhỏ, ít mối quan hệ (ví dụ: truy vấn danh sách khách hàng không kèm đơn hàng).
  • Cần đảm bảo tính nhất quán dữ liệu, vì tất cả dữ liệu được lấy trong một lần truy vấn.
  • Độ trễ mạng là vấn đề (database nằm xa, mỗi round-trip tốn thời gian).

Sử dụng Split Query khi:

  • Làm việc với dữ liệu lớn, nhiều mối quan hệ "một-nhiều" (ví dụ: danh sách đơn hàng kèm chi tiết và sản phẩm).
  • Muốn tiết kiệm bộ nhớ và giảm lượng dữ liệu truyền tải.
  • Tính nhất quán dữ liệu không quá quan trọng, hoặc có thể dùng transaction để kiểm soát.

Phương án triển khai Split Query trong EF Core

1. Áp dụng cho từng truy vấn

Sử dụng phương thức AsSplitQuery() để bật Split Query cho một truy vấn cụ thể:

var orders = dbContext.Orders
    .Include(o => o.OrderDetails)
    .ThenInclude(od => od.Product)
    .AsSplitQuery()
    .ToList();

2. Cấu hình toàn cục

Nếu muốn áp dụng Split Query cho tất cả truy vấn, cấu hình trong DbContext:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseSqlServer(connectionString, 
        o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
}

3. Single Query khi cần

Nếu đã bật Split Query toàn cục, bạn có thể dùng AsSingleQuery() cho một truy vấn cụ thể:

var orders = dbContext.Orders
    .Include(o => o.OrderDetails)
    .AsSingleQuery()
    .ToList();

Thực hành tốt nhất và lưu ý

  • Tránh Cartesian Explosion với Single Query: Luôn kiểm tra số lượng dữ liệu trả về khi dùng Single Query. Nếu thấy hiệu năng giảm, hãy thử chuyển sang Split Query.
  • Đánh giá độ trễ mạng với Split Query: Split Query tăng số lần round-trip, có thể gây chậm nếu database nằm xa. Hãy đo lường hiệu năng bằng các công cụ như SQL Server Profiler.
  • Dùng transaction khi cần nhất quán dữ liệu: Với Split Query, nếu dữ liệu thay đổi giữa các truy vấn, bạn có thể gặp vấn đề nhất quán. Dùng transaction để kiểm soát, nhưng cẩn thận với khóa bảng hoặc deadlock.
  • Benchmark trong môi trường thực tế: Hiệu năng phụ thuộc vào schema database, lượng dữ liệu, và cấu hình hệ thống. Sử dụng BenchmarkDotNet hoặc MiniProfiler để đo lường chính xác.

Kết hợp với các kỹ thuật tối ưu khác:

  • Dùng AsNoTracking() cho các truy vấn chỉ đọc để giảm overhead.
  • Xem xét phân trang (Skip/Take) để giới hạn dữ liệu trả về.

Kết luận

Split Query Single Query là hai công cụ mạnh mẽ trong Entity Framework Core, mỗi cái phù hợp với các tình huống khác nhau. Split Query tỏ ra vượt trội khi xử lý dữ liệu lớn với nhiều mối quan hệ, giúp giảm thời gian thực thi (nhanh gấp 5-6 lần trong một số trường hợp) và tiết kiệm bộ nhớ. Ngược lại, Single Query phù hợp hơn với dữ liệu nhỏ, ít mối quan hệ, hoặc khi cần giảm độ trễ mạng.

Là một senior .NET developer, mình khuyên bạn nên thử nghiệm cả hai phương pháp trong dự án thực tế, sử dụng các công cụ đo lường để so sánh hiệu năng, và chọn giải pháp phù hợp với yêu cầu cụ thể.

Nếu bạn có mẹo hay kinh nghiệm khi dùng Entity Framework Core, hãy chia sẻ ở phần bình luận để chúng ta cùng học hỏi nhé! Cảm ơn các bạn đã đọc bài viết. Hãy theo dõi blog của mình để cập nhật thêm nhiều bài viết công nghệ tâm huyết khác!

 

Tài liệu tham khảo:

 

 

/Son Do - I share real-world lessons, team building & developer growth.

 

#1percentbetter #EntityFrameworkCore #SplitQuery #SingleQuery #DotNet #NETDevelopment #PerformanceOptimization #DatabaseQuery #CSharp #SQLPerformance #TechBlog #SoftwareEngineering #BackendDevelopment

Công nghệ

Xem tất cả