目錄
含義
影響
避免方法
1.?立即加載(Eager Loading)
2.?顯式加載(Explicit Loading)
3.?投影(Projection)
4.?批處理查詢
5.?禁用延遲加載
含義
N+1 問題?是 ORM(對象關系映射)框架(如 Entity Framework)中常見的性能問題,指:
-
1 次查詢?獲取主對象列表(如獲取所有客戶)。
-
N 次額外查詢?為每個主對象單獨加載關聯數據(如為每個客戶查詢其訂單)。
總查詢次數 =?1(初始查詢) + N(關聯數據查詢)
foreach (var id in productIds)
{// 每次循環都執行異步查詢var product = await dbContext.Products.Where(p => p.Id == id).FirstOrDefaultAsync();if (product != null){Console.WriteLine($"找到產品: {product.Name}");}
}
上面的代碼都會產生N+1問題 每一次循環都會執行異步的查詢操作 會降低性能
// 1. 查詢所有客戶(1 次查詢)
var customers = dbContext.Customers.ToList();foreach (var customer in customers)
{// 2. 為每個客戶單獨查詢訂單(N 次查詢)var orders = customer.Orders.ToList();
}
若?customers
?有 100 條數據,將執行?101 次查詢(1 + 100)
影響
-
性能瓶頸:
-
大量數據庫往返(網絡延遲 + 查詢解析開銷)。
-
當 N 較大(如 1000+)時,響應時間顯著增加。
-
-
數據庫壓力:
-
高并發場景下可能導致數據庫連接池耗盡。
-
-
可伸縮性問題:
-
應用難以水平擴展(數據庫成為瓶頸)。
-
避免方法
1.?立即加載(Eager Loading)
使用?Include
?一次性加載關聯數據(生成?JOIN
?語句)。
var customers = dbContext.Customers.Include(c => c.Orders) // 一次性加載所有訂單.ToList();
執行過程:
-
生成單條 SQL:
SELECT * FROM Customers JOIN Orders ...
-
僅 1 次數據庫查詢。
2.?顯式加載(Explicit Loading)
在單次操作中批量加載關聯數據(避免循環內查詢)。
var customers = dbContext.Customers.ToList();// 批量加載所有客戶的訂單(1 次查詢)
dbContext.Entry(customers).Collection(c => c.Orders).Load();
3.?投影(Projection)
通過?Select
?僅查詢所需字段(自動處理關聯數據)。
var result = dbContext.Customers.Select(c => new {CustomerName = c.Name,Orders = c.Orders.Select(o => o.Amount).ToList()}).ToList();
優點:
-
生成高效 SQL(避免?
SELECT *
)。 -
無額外查詢。
4.?批處理查詢
手動合并查詢(如使用?WHERE IN
?語句):
// 異步獲取所有數據
var items = await dbContext.Products.Where(p => p.Price > 100).ToListAsync();// 同步遍歷已獲取的數據
foreach (var item in items)
{Console.WriteLine($"產品: {item.Name}, 價格: {item.Price}");
}
var customerIds = customers.Select(c => c.Id).ToList();
var allOrders = dbContext.Orders.Where(o => customerIds.Contains(o.CustomerId)).ToList();// 內存中關聯數據
foreach (var customer in customers)
{customer.Orders = allOrders.Where(o => o.CustomerId == customer.Id).ToList();
}
5.?禁用延遲加載
在 ORM 中關閉延遲加載,強制開發者主動處理關聯數據。
// Entity Framework Core 配置
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{optionsBuilder.UseLazyLoadingProxies(false); // 禁用延遲加載
}
-
代碼審查:
-
警惕循環內的?
DbContext
?查詢操作。
-
分頁處理:
-
當 N 極大時,分頁加載主數據(如?
Take(100)
)
關鍵原則:減少數據庫往返次數,用 1~2 次復雜查詢替代 N+1 次簡單查詢。