一、为什么需要优化数据库查询
想象一下,你正在开发一个电商网站,用户打开商品列表页面时,页面加载需要5秒。经过排查发现,问题出在数据库查询上——一个简单的商品列表查询竟然用了3秒!这种情况在实际开发中并不少见,特别是在数据量逐渐增大后,原本运行良好的查询可能突然变慢。
在.NET Core应用中,我们通常使用EF Core作为ORM框架,它确实能极大提升开发效率。但EF Core生成的SQL有时并不完美,比如:
- 可能查询了不需要的字段
- 复杂的LINQ操作被翻译成低效的SQL
- 没有合理利用索引
这时候就需要我们主动干预,通过调优查询或引入更轻量的Dapper来解决问题。
二、EF Core查询调优实战技巧
1. 只查询需要的字段
EF Core默认会查询所有字段,但很多时候我们只需要其中几个。比如用户表有20个字段,但列表页只需要显示用户名和头像:
// 技术栈:.NET Core + EF Core
// 不好的做法:查询所有字段
var users = dbContext.Users.ToList();
// 优化做法:只选择需要的字段
var users = dbContext.Users
.Select(u => new { u.UserName, u.AvatarUrl })
.ToList();
2. 警惕延迟加载的陷阱
EF Core的延迟加载看起来很美好,但容易导致"N+1查询问题"。比如查询10篇文章及其作者:
// 可能产生11条SQL查询(1次查文章+10次查作者)
var articles = dbContext.Articles.ToList();
foreach (var article in articles)
{
Console.WriteLine(article.Author.Name); // 每次访问都会触发查询
}
// 优化方案:使用Include预先加载
var articles = dbContext.Articles
.Include(a => a.Author)
.ToList();
3. 分页查询一定要加Skip/Take
大数据量查询必须分页,但要注意写法:
// 错误的顺序:先取全部数据再分页
var data = dbContext.Products
.ToList()
.Skip(10).Take(10); // 内存分页,性能灾难!
// 正确做法:在数据库层面分页
var data = dbContext.Products
.OrderBy(p => p.Id)
.Skip(10)
.Take(10)
.ToList();
三、什么时候该用Dapper
虽然EF Core很强大,但在以下场景Dapper更有优势:
- 超复杂SQL查询(如多表联合统计)
- 需要精确控制SQL执行
- 批量操作性能要求极高
Dapper基础用法示例
// 技术栈:.NET Core + Dapper
using var connection = new SqlConnection(connectionString);
// 简单查询
var products = connection.Query<Product>(
"SELECT Id, Name FROM Products WHERE Price > @minPrice",
new { minPrice = 100 });
// 多表查询
var sql = @"
SELECT o.*, u.UserName
FROM Orders o
JOIN Users u ON o.UserId = u.Id
WHERE o.CreateTime > @startDate";
var orders = connection.Query<Order, User, Order>(
sql,
(order, user) => {
order.User = user;
return order;
},
new { startDate = DateTime.Now.AddDays(-7) },
splitOn: "UserName");
四、EF Core与Dapper混合使用策略
最佳实践是:
- 80%的常规CRUD使用EF Core
- 20%复杂查询/性能关键路径使用Dapper
混合使用示例
// 在同一个服务中混用
public class ProductService
{
private readonly AppDbContext _db;
private readonly SqlConnection _connection;
public ProductService(AppDbContext db, SqlConnection connection)
{
_db = db;
_connection = connection;
}
// 使用EF Core添加产品
public async Task AddProduct(Product product)
{
_db.Products.Add(product);
await _db.SaveChangesAsync();
}
// 使用Dapper查询热销商品
public async Task<List<Product>> GetHotProducts()
{
var sql = @"
SELECT TOP 10 p.*
FROM Products p
JOIN OrderItems oi ON p.Id = oi.ProductId
GROUP BY p.Id, p.Name, p.Price
ORDER BY SUM(oi.Quantity) DESC";
return (await _connection.QueryAsync<Product>(sql)).ToList();
}
}
五、性能优化进阶技巧
- 批量操作优化
EF Core的批量更新可能生成多条SQL,使用Dapper能大幅提升性能:
// 使用Dapper执行批量更新
var sql = "UPDATE Products SET Price = Price * 1.1 WHERE CategoryId = @categoryId";
await connection.ExecuteAsync(sql, new { categoryId = 5 });
- 读写分离
将查询操作路由到从库,减轻主库压力:
// 在Startup中配置多数据库连接
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MasterDb")));
services.AddSingleton(new SqlConnection(
Configuration.GetConnectionString("ReadOnlyDb")));
六、应用场景与选型建议
适合使用EF Core的场景:
- 快速原型开发
- 简单的CRUD操作
- 需要数据库迁移支持的项目
适合使用Dapper的场景:
- 报表类复杂查询
- 批量数据处理
- 对性能要求极高的接口
注意事项:
- 不要过早优化,先确保功能正确
- 混合使用时注意事务处理
- Dapper需要手动处理SQL注入风险
七、总结
数据库查询优化是个持续的过程,没有银弹。EF Core提供了开发效率,Dapper提供了执行效率。明智的做法是根据具体场景选择合适的工具,甚至混合使用。记住:
- 先分析慢查询的原因
- 从简单的EF Core调优开始
- 必要时引入Dapper处理复杂场景
- 始终关注实际性能指标
通过合理的策略组合,完全可以让.NET Core应用的数据库访问既保持开发效率,又拥有出色的运行时性能。
评论