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