本文将详细讲解EF Core与MySQL的查询优化,包括使用AsNoTracking提高查询性能,使用Include和ThenInclude进行贪婪加载,使用Select进行投影查询、原始SQL查询,使用索引优化查询,其他优化技巧如分页、批量操作和查询编译,性能监控和诊断工具的使用。
1. 使用 AsNoTracking 提高查询性能
基本用法
// 常规查询(会跟踪实体变更) var products = context.Products .Where(p => p.Price > 100) .ToList(); // 使用 AsNoTracking(不跟踪实体变更,性能更好) var products = context.Products .AsNoTracking() .Where(p => p.Price > 100) .ToList();
应用场景
-
只读查询操作
-
数据展示场景
-
报表生成
-
大数据量查询
全局配置
// 在DbContext中配置全局不跟踪 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)) .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); } // 或者针对特定查询启用跟踪 var products = context.Products .AsTracking() // 显式启用跟踪 .Where(p => p.Price > 100) .ToList();
2. 使用 Include 和 ThenInclude 进行贪婪加载
基本用法
// 加载单个关联实体 var blogs = context.Blogs .Include(b => b.Posts) // 加载Posts集合 .ToList(); // 加载多层关联实体 var blogs = context.Blogs .Include(b => b.Posts) .ThenInclude(p => p.Comments) // 加载Posts下的Comments .Include(b => b.Author) // 加载单个Author .ToList(); // 加载多个关联实体 var blogs = context.Blogs .Include(b => b.Posts) .Include(b => b.Tags) .ToList();
过滤包含的关联数据
// 只加载符合条件的关联数据(EF Core 5.0+) var blogs = context.Blogs .Include(b => b.Posts.Where(p => p.IsPublished)) .Include(b => b.Tags.OrderBy(t => t.Name).Take(5)) .ToList(); // 使用字符串方式包含(动态查询场景) var blogs = context.Blogs .Include("Posts.Comments") .ToList();
性能考虑
// 避免过度包含(N+1查询问题) // 错误方式:会产生N+1查询 var blogs = context.Blogs.ToList(); foreach (var blog in blogs) { var posts = context.Posts.Where(p => p.BlogId == blog.Id).ToList(); // 处理posts... } // 正确方式:使用Include一次性加载所有关联数据 var blogs = context.Blogs .Include(b => b.Posts) .ToList(); foreach (var blog in blogs) { // 直接访问blog.Posts,不会产生额外查询 }
3. 使用 Select 进行投影查询
基本投影
// 只选择需要的字段 var productInfo = context.Products .Where(p => p.Price > 100) .Select(p => new { p.Id, p.Name, p.Price, CategoryName = p.Category.Name // 关联实体字段 }) .ToList(); // 转换为DTO对象 var productDtos = context.Products .Select(p => new ProductDto { Id = p.Id, Name = p.Name, Price = p.Price, CategoryName = p.Category.Name }) .ToList();
条件投影
var products = context.Products .Select(p => new { p.Id, p.Name, PriceCategory = p.Price > 100 ? "Expensive" : "Affordable", HasStock = p.Stock > 0 }) .ToList();
集合投影
var blogSummaries = context.Blogs .Select(b => new { b.Id, b.Title, PostCount = b.Posts.Count(), LatestPost = b.Posts .OrderByDescending(p => p.CreatedDate) .Select(p => new { p.Title, p.CreatedDate }) .FirstOrDefault() }) .ToList();
4. 原始 SQL 查询
基本查询
// 使用FromSqlRaw执行原始SQL查询 var products = context.Products .FromSqlRaw("SELECT * FROM Products WHERE Price > {0} AND Stock > {1}", 100, 0) .ToList(); // 使用参数化查询防止SQL注入 var minPrice = 100; var minStock = 0; var products = context.Products .FromSqlInterpolated($"SELECT * FROM Products WHERE Price > {minPrice} AND Stock > {minStock}") .ToList();
与LINQ结合使用
// 原始SQL查询后继续使用LINQ var expensiveProducts = context.Products .FromSqlRaw("SELECT * FROM Products WHERE Price > 100") .Where(p => p.Stock > 0) .OrderByDescending(p => p.Price) .ToList();
执行非查询SQL
// 执行更新、删除等操作 var affectedRows = context.Database.ExecuteSqlRaw( "UPDATE Products SET Price = Price * 1.1 WHERE CategoryId = {0}", categoryId); // 使用存储过程 var products = context.Products .FromSqlRaw("EXEC GetExpensiveProducts @minPrice = {0}", 100) .ToList();
5. 使用索引优化查询
在模型中定义索引
protected override void OnModelCreating(ModelBuilder modelBuilder) { // 创建单列索引 modelBuilder.Entity<Product>() .HasIndex(p => p.Name); // 创建唯一索引 modelBuilder.Entity<Product>() .HasIndex(p => p.Sku) .IsUnique(); // 创建复合索引 modelBuilder.Entity<Product>() .HasIndex(p => new { p.CategoryId, p.Price }); // 创建筛选索引(MySQL 8.0+) modelBuilder.Entity<Product>() .HasIndex(p => p.Price) .HasFilter("[Price] > 100"); }
在迁移中创建索引
// 创建迁移后,可以自定义索引 migrationBuilder.CreateIndex( name: "IX_Products_CategoryId_Price", table: "Products", columns: new[] { "CategoryId", "Price" }, filter: "Price > 100");
使用索引提示(MySQL 8.0+)
// 强制使用特定索引 var products = context.Products .FromSqlRaw("SELECT * FROM Products USE INDEX (IX_Products_Price) WHERE Price > 100") .ToList();
监控查询性能
// 启用MySQL慢查询日志 // 在my.cnf或my.ini中添加: // slow_query_log = 1 // slow_query_log_file = /var/log/mysql/mysql-slow.log // long_query_time = 2 // 使用EXPLAIN分析查询 var explainResult = context.Database.ExecuteSqlRaw( "EXPLAIN SELECT * FROM Products WHERE Price > 100");
6. 其他优化技巧
分页优化
// 使用Keyset分页(基于值的分页) var lastPrice = 100; var lastId = 50; var products = context.Products .Where(p => p.Price > lastPrice || (p.Price == lastPrice && p.Id > lastId)) .OrderBy(p => p.Price) .ThenBy(p => p.Id) .Take(20) .ToList(); // 传统分页(适用于小数据集) var pageNumber = 2; var pageSize = 20; var products = context.Products .OrderBy(p => p.Name) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToList();
批量操作优化
// 使用AddRange批量添加 var products = new List<Product>(); // 添加多个产品到列表 context.Products.AddRange(products); context.SaveChanges(); // 使用ExecuteUpdate批量更新(EF Core 7.0+) context.Products .Where(p => p.CategoryId == 1) .ExecuteUpdate(p => p.SetProperty(x => x.Price, x => x.Price * 1.1m)); // 使用ExecuteDelete批量删除(EF Core 7.0+) context.Products .Where(p => p.Stock == 0) .ExecuteDelete();
查询编译优化
// 使用编译查询(适用于频繁执行的查询) private static readonly Func<ApplicationDbContext, int, IEnumerable<Product>> GetProductsByCategory = EF.CompileQuery((ApplicationDbContext context, int categoryId) => context.Products.Where(p => p.CategoryId == categoryId)); // 使用编译查询 var products = GetProductsByCategory(context, 1).ToList();
连接池优化
// 在连接字符串中配置连接池 var connectionString = "server=localhost;database=efcoredb;user=root;password=yourpassword;Pooling=true;MinimumPoolSize=5;MaximumPoolSize=100;ConnectionTimeout=30;";
7. 性能监控和诊断
启用EF Core日志
// 在DbContext配置中启用敏感数据日志记录和详细错误 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)) .EnableSensitiveDataLogging() // 仅开发环境 .EnableDetailedErrors() // 仅开发环境 .LogTo(Console.WriteLine, LogLevel.Information); // 记录SQL查询 }
使用MiniProfiler监控性能
// 安装MiniProfiler.EntityFrameworkCore services.AddMiniProfiler(options => { options.RouteBasePath = "/profiler"; options.ColorScheme = StackExchange.Profiling.ColorScheme.Auto; }).AddEntityFramework();
分析查询性能
// 使用MySQL的EXPLAIN分析查询 var query = context.Products.Where(p => p.Price > 100); var sql = query.ToQueryString(); // 获取生成的SQL Console.WriteLine(sql); // 或者在数据库直接执行EXPLAIN var explainResult = context.Database.ExecuteSqlRaw( "EXPLAIN SELECT * FROM Products WHERE Price > 100");
总结
本教程详细介绍了EF Core与MySQL的查询优化技巧,包括:
-
使用AsNoTracking提高只读查询性能
-
使用Include和ThenInclude正确加载关联数据,避免N+1查询问题
-
使用Select投影查询减少数据传输量
-
使用原始SQL查询处理复杂场景
-
使用索引优化查询性能
-
其他优化技巧如分页、批量操作和查询编译
-
性能监控和诊断工具的使用
优化查询性能是一个持续的过程,需要结合实际应用场景和数据库特性进行调整。建议定期分析慢查询日志,使用EXPLAIN分析查询计划,并根据结果调整索引和查询方式。
记住,最好的优化往往是基于实际性能分析而不是盲目猜测。在生产环境中,始终使用性能监控工具来识别和解决瓶颈问题。在这个系列的最后,会单独详细的写一篇EF Core与MySQL的日志和调试详解。