ASP.NET Core 问答 05:EF Core、部署、性能与生产故障排查
081 DbContext 为什么通常按请求注册为 Scoped?
难度: 基础
查看参考答案
结论: 它适合作为短生命周期工作单元,跟踪一次业务操作中的实体变化
原因: 长时间持有会积累跟踪状态且不支持并发操作
边界: 一个请求也可能需要多个工作单元,后台任务必须自建作用域
最小示例: AddDbContext 默认 scoped
082 为什么同一个 DbContext 不能并行查询?
难度: 基础
查看参考答案
结论: DbContext 不支持并发操作,每个异步调用应立即 await
原因: ChangeTracker、连接和内部状态不是并发会话
边界: 需要并行时创建独立 Context,并评估事务和连接压力
最小示例: 不要在同一 context 上 Task.WhenAll
083 跟踪查询与 AsNoTracking 如何选择?
难度: 基础
查看参考答案
结论: 需要修改并 SaveChanges 时使用跟踪,只读查询优先投影或无跟踪
原因: 跟踪提供身份解析和变更检测,但有内存与 CPU 成本
边界: 无跟踪仍可能返回实体对象,只是不加入当前 Context 跟踪
最小示例: 列表页使用 AsNoTracking
084 如何消除 N+1 查询?
难度: 基础
查看参考答案
结论: 用投影、Include 或批量查询一次取得需要的数据
原因: 循环中的懒加载会随行数增加数据库往返
边界: 一次巨大 Include 也可能过度加载,需按结果形状测量
最小示例: Select 直接投影订单及客户名
085 单查询与 Split Query 如何权衡?
难度: 基础
查看参考答案
结论: 单查询减少往返但可能笛卡尔膨胀,拆分查询降低重复行但增加往返和一致性边界
原因: 多个集合 Include 时数据乘积可能远大于实体数量
边界: 拆分查询期间并发修改可能让各结果集不是同一快照
最小示例: 用 AsSplitQuery 后测量 SQL 与数据量
086 为什么投影常是 EF 查询优化首选?
难度: 基础
查看参考答案
结论: Select 只取需要列和形状,减少传输、物化与跟踪
原因: 直接加载完整实体会读取无用列和导航
边界: 投影中包含实体时仍可能跟踪,应检查结果类型
最小示例: Select new OrderDto
087 如何处理乐观并发冲突?
难度: 进阶
查看参考答案
结论: 配置并发令牌,捕获 DbUpdateConcurrencyException 后按业务选择重试、合并或提示冲突
原因: 更新语句会把原令牌放入条件,受影响行数为零表示状态已变化
边界: 盲目重试可能覆盖他人修改,必须重新读取并决定
最小示例: 使用 rowversion
088 SaveChanges 是否自动使用事务?
难度: 进阶
查看参考答案
结论: 单次 SaveChanges 在支持的提供程序上通常以事务保证该批更改原子性
原因: 跨多次 SaveChanges 或外部系统需要显式事务、执行策略或补偿
边界: 数据库事务不能覆盖消息队列和 HTTP 调用
最小示例: Outbox 将消息与业务数据同库提交
089 迁移应如何进入生产?
难度: 进阶
查看参考答案
结论: 在发布流程审查并生成可回滚或幂等脚本,由受控身份执行
原因: 应用启动时所有实例同时迁移可能竞争并放大故障
边界: 大表变更要分阶段兼容旧新版本并评估锁
最小示例: 先加可空列,再回填,最后收紧约束
090 Compiled Query 何时值得使用?
难度: 进阶
查看参考答案
结论: 仅在高频、稳定形状查询且分析确认编译开销显著时使用
原因: EF 已缓存普通查询形状,数据库往返通常更昂贵
边界: 参数和模型必须符合编译委托契约,不能替代索引优化
最小示例: 用基准比较 EF.CompileAsyncQuery
091 如何发现慢 EF 查询?
难度: 进阶
查看参考答案
结论: 记录命令耗时、查看生成 SQL 和执行计划,并关联请求 trace
原因: 只看 LINQ 源码无法判断翻译结果、索引和实际基数
边界: 生产日志要避免泄露参数敏感数据
最小示例: TagWith 标记关键查询
092 容器中的 ASP.NET Core 应监听什么地址?
难度: 进阶
查看参考答案
结论: 按平台注入端口并监听容器可访问接口,而不是只绑定 localhost
原因: 容器端口映射不改变进程内部监听地址
边界: 生产通常由编排平台和反向代理提供外部入口
最小示例: ASPNETCORE_HTTP_PORTS 配置端口
093 反向代理后为什么要处理 Forwarded Headers?
难度: 进阶
查看参考答案
结论: 应用需要可信地恢复原始 scheme、host 和客户端地址
原因: 否则 HTTPS 重定向、链接生成和安全策略可能判断错误
边界: 只能信任已知代理并限制转发数量,不能接受任意客户端伪造头
最小示例: 配置 KnownProxies 或 KnownNetworks
094 滚动发布为什么需要向后兼容?
难度: 进阶
查看参考答案
结论: 新旧实例会短暂共存,数据库与消息契约必须同时被两版理解
原因: 一次性破坏字段或协议会让部分流量失败
边界: 使用扩展再收缩策略,先发布兼容读写再清理旧字段
最小示例: 消息新增可选字段
095 如何设计就绪探针?
难度: 实战
查看参考答案
结论: 检查实例接流量所需的关键本地状态,并设置快速超时
原因: 把所有远程依赖都做深度查询会放大故障并拖垮依赖
边界: 存活探针应更轻,避免依赖抖动触发重启循环
最小示例: 启动完成后 readiness 成功
096 ASP.NET Core 热路径应先优化什么?
难度: 实战
查看参考答案
结论: 先用指标和 profiler 找到 CPU、分配、锁、I/O 或数据库瓶颈
原因: 凭感觉改 Span 或缓存可能增加复杂度却不改善端到端延迟
边界: 在 Release 和代表性负载下测量,关注吞吐与尾延迟
最小示例: dotnet-counters 后再采集 trace
097 线程池饥饿有哪些常见原因?
难度: 实战
查看参考答案
结论: 请求路径阻塞异步 I/O、长同步等待或大量突发工作会耗尽工作线程
原因: 线程增长有延迟,队列会导致延迟阶梯上升
边界: 不要用 Task.Run 包装所有 I/O,应该端到端 async
最小示例: 避免 .Result 和同步网络调用
098 如何诊断内存持续增长?
难度: 实战
查看参考答案
结论: 区分泄漏、缓存增长、高分配率和 GC 暂时堆扩张,再用计数器、dump 和引用链定位
原因: 只看进程工作集不能证明托管泄漏
边界: 采集 dump 可能暂停或增加成本,要在合适环境操作
最小示例: 观察 heap size、allocation rate 与 Gen2
099 为什么重试、超时和熔断要协同设计?
难度: 实战
查看参考答案
结论: 超时限制单次等待,重试处理瞬时失败,熔断在持续故障时快速失败
原因: 策略叠加会扩大总时长和下游负载,必须共享总体截止时间并加抖动
边界: 非幂等请求不能无条件重试,熔断也不能替代容量规划
最小示例: HttpClient 弹性管道限制最大尝试次数
100 生产故障排查的第一步是什么?
难度: 实战
查看参考答案
结论: 先确认影响范围、时间线、近期变更和关键 SLI,再保全证据并止损
原因: 没有共同事实就直接重启或改配置可能销毁证据并扩大问题
边界: 紧急止损与根因分析可以并行,但变更要可回滚并记录
最小示例: 对比发布前后错误率和 P99