本文将详细讲解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的查询优化技巧,包括:

  1. 使用AsNoTracking提高只读查询性能

  2. 使用Include和ThenInclude正确加载关联数据,避免N+1查询问题

  3. 使用Select投影查询减少数据传输量

  4. 使用原始SQL查询处理复杂场景

  5. 使用索引优化查询性能

  6. 其他优化技巧如分页、批量操作和查询编译

  7. 性能监控和诊断工具的使用

优化查询性能是一个持续的过程,需要结合实际应用场景和数据库特性进行调整。建议定期分析慢查询日志,使用EXPLAIN分析查询计划,并根据结果调整索引和查询方式。

记住,最好的优化往往是基于实际性能分析而不是盲目猜测。在生产环境中,始终使用性能监控工具来识别和解决瓶颈问题。在这个系列的最后,会单独详细的写一篇EF Core与MySQL的日志和调试详解。

EF Core 与 MySQL:查询优化详解的更多相关文章

  1. MySQL状态变量详解

    MySQL状态变量详解 mysql的状态变量(status variables)记录的mysql服务器的运行状态信息.查看语法如下: SHOW [GLOBAL | SESSION] STATUS; S ...

  2. (转)mysql explain详解

    原文:http://www.cnblogs.com/xuanzhi201111/p/4175635.html http://yutonger.com/18.html http://www.jiansh ...

  3. MySQL 数据类型 详解

    MySQL 数据类型 详解 MySQL 的数值数据类型可以大致划分为两个类别,一个是整数,另一个是浮点数或小数.许多不同的子类型对这些类别中的每一个都是可用的,每个子类型支持不同大小的数据,并且 My ...

  4. mysql存储过程详解

    mysql存储过程详解 1.      存储过程简介   我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的S ...

  5. mysql 存储过程详解 存储过程

    mysql存储过程详解 1.      存储过程简介         我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成 ...

  6. MySQL配置文件详解

    MYSQL 配置文件详解 “全局缓存”.“线程缓存”,全局缓存是所有线程共享,线程缓存是每个线程连接上数据时创建一个线程(如果没有设置线程池),假如有200连接.那就是200个线程,如果参数设定值是1 ...

  7. MySQL存储过程详解 mysql 存储过程

    原文地址:MySQL存储过程详解  mysql 存储过程作者:王者佳暮 mysql存储过程详解 1.     存储过程简介 我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储 ...

  8. Mysql Explain 详解

    Mysql Explain 详解[强烈推荐] Mysql Explain 详解一.语法explain < table_name >例如: explain select * from t3 ...

  9. MySQL存储过程详解 mysql 存储过程(二)

    mysql存储过程详解 1.      存储过程简介 我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的SQL ...

  10. MySQL权限详解

    MySQL权限级别介绍 MySQL权限级别 全局性的管理权限,作用于整个MySQL实例级别 数据库级别的权限,作用于某个指定的数据库上或者所有的数据库上 数据库对象级别的权限,作用于指定的数据库对象上 ...

随机推荐

  1. 利于puppeteer获取网络资源的直链

    背景 比如我想使用curl 或者 页面按钮点击直接下载个网盘资源,那就会出现问题. 因为目前各大网盘给的分享链接都是一个页面,而且大部分还都做了防盗机制,你无法简单的获取真实下载连接! 但是我们可以利 ...

  2. Claude Code与MCP:让AI拥有超能力的完全指南

    前言:什么是MCP?为什么你需要它? 想象一下,如果Claude是一个超级聪明的助手,那么MCP(Model Context Protocol)就是给它装上了各种神奇的"义肢".就 ...

  3. 决策树模型(5)Cart剪枝

    CART 剪枝 损失函数构建 在前面的章节中,我们了解到可以通过平方误差最小化准则和基尼指数最小化准则生成一颗回归树和分类树 \(T\).但是通常在实现过程中,我们会对CART树进行剪枝以达到简化模型 ...

  4. ETL工具观察:ETLCloud与MDM是什么关系?

    一.什么是ETLCloud ETLCloud数据中台是一款高时效的数据集成平台,专注于解决大数据量和高合规要求环境下的数据集成需求. 工具特点 1.离线与实时集成:支持离线数据集成(ETL.ELT)和 ...

  5. ETL怎么实现文件处理

    在现代企业及各类组织的日常运作中,数据作为一种关键的信息资源,其管理和分析能力直接影响到决策效率与准确性.文件作为数据的主要载体,承载着从运营报告.客户记录.交易明细等各种类型的数据信息.这些海量且多 ...

  6. 『OpenCV-Python』加载网络图片

    点赞 + 关注 + 收藏 = 学会了 前面介绍过在 OpenCV 里可以通过 cv2.imread 读取本地图片,但这个方法无法读取网络图片. 读取网络图片:cv2.imdecode 在 OpenCV ...

  7. 使用VMware 16 安装中标麒麟 7 --九五小庞

    1.下载中标麒麟7百度网盘:百度网盘 请输入提取码百度网盘为您提供文件的网络备份.同步和分享服务.空间大.速度快.安全稳固,支持教育网加速,支持手机端.注册使用百度网盘即可享受免费存储空间https: ...

  8. win11正式版如何关闭快速启动的问题

    有不少雨林木风官网的小伙伴,都可能不知道什么是快速启动.其实,它是windows11正式版里面的快速启动功能,是让计算机不真正关闭并保存某些数据,以便用户下次启动时能够快速关闭计算机系统.那么有小伙伴 ...

  9. Windows11正式版如何设置电脑自动开机的问题

    有很多雨林木风系统的用户都不知道如何设置电脑自动启动吧?其实,这个问题很容易解决的,本文中,我们雨林木风小编就来分享Windows 11正式版设置电脑自动开机的方法.让我们看看吧. 在 Win11 电 ...

  10. UNR 6. D2T2 神隐

    \(\mathbf{Part. -1}\) 这是一道交互题. hehe 蚤决定花费几天时间,游览下山市的最著名的旅游景点 -- 吓山. 吓山,以高低纵横,崔巍秀丽,错综复杂的地形闻名.据说无论用什么地 ...