聊聊数据库查询处理中的两种模式(SSE & CSE)

服务端评估和客户端评估是数据库查询处理中的两个概念,它们与 “空间换时间”“时间换空间” 的优化策略有密切联系。

一、概念解释

1. 服务端评估(Server-side Evaluation)

  • 查询在数据库服务器上执行,只将最终结果传输到客户端。
  • 数据过滤、排序、聚合等操作都在服务端完成。
  • 优点:减少网络传输数据量,提高整体性能。
  • 缺点:可能增加服务端计算资源消耗。
联系:空间换时间
  • 将大量中间数据保留在服务端进行处理,避免将原始数据全部传到客户端再处理。
  • 用服务端的内存/计算资源(空间)换取了更快的响应时间。

2. 客户端评估(Client-side Evaluation)

  • 查询的部分逻辑由客户端(如应用程序)执行。
  • 数据从服务端拉取到客户端后,再进行过滤、计算等操作。
  • 优点:减轻服务端压力。
  • 缺点:可能导致大量数据在网络上传输,影响性能。
联系:时间换空间
  • 客户端使用自己的资源来处理数据,避免服务端占用过多资源。
  • 以客户端的处理时间和网络传输时间为代价,换取服务端资源的节省。

总结

模式执行位置特点优化策略
服务端评估数据库服务器减少网络流量,提升响应速度空间换时间
客户端评估应用程序端减轻服务端负载,灵活性高时间换空间

实际应用建议:

  • 对大数据集优先使用服务端评估。
  • 对小数据集或复杂业务逻辑可考虑客户端评估。
  • EF CoreORM 中,应尽量编写能被转换为 SQL 的查询语句,以触发服务端评估。

二、在 ORM 中两种模式的应用

Entity Framework Core (EF Core) 中,查询的执行方式分为两种模式:服务端评估(Server-side evaluation)客户端评估(Client-side evaluation)

EF Core 会尽可能将 LINQ 查询转换为 SQL 语句,在数据库端执行(服务端评估),以减少传输到客户端的数据量和提升性能。但在某些情况下,无法被翻译成 SQL 的表达式会被 EF Core 在客户端执行(客户端评估)。


✅ 1. 服务端评估(Server-side Evaluation)

定义:

查询逻辑由数据库服务器执行,仅将最终结果返回给客户端。

特点:

  • 查询被转换为 SQL 并在数据库中执行。
  • 只有满足条件的结果才会被发送到客户端。
  • 高效、适合处理大数据集。

示例:

var users = context.Users
    .Where(u => u.Age > 30)
    .ToList();

上面的 Where 条件会被翻译成 SQL,只返回年龄大于 30 的用户数据。

优点:

  • 减少网络传输数据量。
  • 利用数据库索引、优化器等能力。
  • 提高性能和可扩展性。

⚠️ 2. 客户端评估(Client-side Evaluation)

定义:

EF Core 无法将查询部分逻辑转换为 SQL 时,会在客户端(即应用程序中)继续执行这些逻辑。

特点:

  • 某些复杂逻辑(如自定义方法、非可翻译的 LINQ 方法)会导致客户端评估。
  • 数据先从数据库拉取到内存中,再进行过滤或计算。
  • 性能代价较高,应尽量避免对大数据集使用。

示例:

var users = context.Users
    .Where(u => MyCustomFilter(u.Name))
    .ToList();

如果 MyCustomFilter 是一个 C# 方法,EF Core 无法将其翻译为 SQL,就会把所有用户数据加载到内存中,然后在客户端执行该方法进行过滤。

常见触发场景:

  • 使用自定义 C# 方法。
  • 使用 ToString()DateTime.Now 等无法映射的函数。
  • 使用复杂的 LINQ 操作符组合。

注意事项:

  • EF Core 会在日志中输出警告(The LINQ expression could not be translated),提示发生了客户端评估。
  • 客户端评估可能导致性能问题,尤其是在大数据量下。

🛠️ 如何查看是否发生客户端评估?

  1. 启用 EF Core 日志输出:
   optionsBuilder.UseSqlServer("your_connection_string")
                 .LogTo(Console.WriteLine, LogLevel.Information);
  1. 查看日志中是否有如下内容:
   The LINQ expression '...' could not be translated and will be evaluated locally.

  • 🎯 推荐做法:
场景推荐模式说明
查询可被翻译为 SQL服务端评估更高效
查询包含复杂业务逻辑客户端评估谨慎使用,注意性能影响
大数据量处理尽量使用服务端评估避免全表加载
小数据 + 逻辑复杂可接受客户端评估减轻数据库负担

🔗 参考文档:

三、客户端评估的优化技巧

优化 客户端评估(Client-side Evaluation) 的核心目标是:尽量避免在客户端处理大量数据,减少性能损耗。下面是一些具体的优化技巧和实践建议:


✅ 1. 优先使用可翻译的 LINQ 方法

EF Core 能将部分 LINQ 操作符自动转换为 SQL,而有些则不能。

  • ✔ 推荐:
var users = context.Users
    .Where(u => u.Age > 30)
    .Select(u => new { u.Id, u.Name })
    .ToList();
  • ❌ 避免:
var users = context.Users
    .Where(u => MyCustomFilter(u.Name)) // 自定义方法无法翻译
    .ToList();

✅ 2. 将复杂逻辑下推到数据库

如果查询中包含复杂逻辑或函数,考虑将其封装为数据库端函数(如 SQL 函数 ),并通过 EF Core 映射调用。

示例:

  • 创建 SQL 函数 dbo.IsPreferredUser(@userId)
  • EF Core 中映射并使用
modelBuilder.HasDbFunction(() => SqlFunctions.IsPreferredUser(default(int)))
    .HasName("IsPreferredUser")
    .HasSchema("dbo");

然后可以在查询中使用:

var users = context.Users
    .Where(u => SqlFunctions.IsPreferredUser(u.Id))
    .ToList();

✅ 3. 预加载必要数据后在服务端处理

避免先加载整个表再在客户端做后续过滤,应尽量在服务端完成筛选。

  • ❌ 不推荐:
var allUsers = context.Users.ToList(); // 加载全部用户
var filtered = allUsers.Where(u => IsPreferred(u)).ToList(); // 客户端评估
  • ✔ 推荐:
var preferredUsers = context.Users
    .FromSqlRaw("SELECT * FROM Users WHERE IsPreferred = 1")
    .ToList();

✅ 4. 使用 FromSqlRaw / ExecuteSqlRaw 手动控制查询

对于 EF Core 无法翻译的逻辑,可以使用原始 SQL 查询来替代客户端评估。

var result = context.Users
    .FromSqlRaw("SELECT * FROM Users WHERE Name LIKE '%{0}%'", searchText)
    .ToList();

✅ 5. 对大数据量避免使用 ToList() 前的 Where / Select

如果你看到如下代码结构,说明可能发生了客户端评估:

var data = context.Users.ToList().Where(u => u.IsActive); // ❌ 客户端评估

应该改为:

var data = context.Users.Where(u => u.IsActive).ToList(); // ✅ 服务端执行

✅ 6. 启用日志监控客户端评估发生情况

通过启用 EF Core 日志,可以发现哪些查询触发了客户端评估。

optionsBuilder.UseSqlServer("your_connection_string")
              .LogTo(Console.WriteLine, LogLevel.Information);

查找日志中的警告信息:

The LINQ expression '...' could not be translated and will be evaluated locally.

✅ 7. 拆分查询逻辑,分阶段处理

如果某些业务逻辑确实无法完全在服务端执行,可以考虑分阶段处理:

  1. 先在服务端获取较小的数据集;
  2. 再在客户端进行进一步处理。
var smallDataSet = context.Users
    .Where(u => u.Age > 30 && u.IsActive)
    .Take(100)
    .ToList();

var finalResult = smallDataSet.Where(u => ComplexFilter(u)).ToList();

✅ 8. 使用投影(Projection)减少传输数据量

即使部分逻辑必须客户端执行,也可以先通过投影只拉取必要字段。

var data = context.Users
    .Select(u => new { u.Id, u.Name, u.Email }) // 只取需要字段
    .ToList()
    .Where(u => SomeComplexCheck(u));

✅ 9. 升级 EF Core 版本以获得更好的翻译支持

新版本 EF Core 支持更多表达式的翻译能力。例如:

  • EF Core 5+ 引入了更多的 SQL 翻译器支持。
  • EF Core 6/7 进一步扩展了可翻译的 LINQ 表达式范围。

✅ 10. 使用 Memory-based Provider 做本地测试

开发时可以使用 InMemoryDatabase 来模拟客户端评估行为,帮助你识别潜在性能问题。


📌 总结

优化技巧说明
使用可翻译的 LINQ 方法避免自定义 C# 方法
将逻辑下推至数据库SQL 函数、存储过程
使用 FromSqlRaw 执行原生 SQL替代复杂客户端逻辑
分阶段查询 + 投影减少客户端处理数据量
启用日志监控客户端评估快速定位性能瓶颈
升级 EF Core 版本获取更强的翻译能力
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ChaITSimpleLove

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值