跳到正文

MyBatis 生产检查清单 ​

适用 MyBatis 3.5.x / MyBatis-Spring-Boot-Starter。每条都可勾选,括号内是默认值或判断依据;版本差异以官方文档为准。 只列「不做会出事」的动作,机制解释见各篇正文。

上线前 ​

  • [ ] ${} 全量审计并有白名单:只允许表名、列名、ORDER BY 等无法预编译的结构使用,且值必须命中固定集合;用户输入拼进 ${} 等同于 SQL 注入。
  • [ ] 列表查询没有裸嵌套子查询:<collection select="..."> 在返回 N 行时会发 N+1 条 SQL,列表接口改为 JOIN 嵌套结果或 Service 层批量 IN;上线前用 1000 行数据数一次实际 SQL 条数。
  • [ ] 二级缓存默认不开,开则定责:确认 <cache/> 只加在低频写、单表为主的 namespace;readOnly=false(默认)时 POJO 必须实现 Serializable;多表关联用 <cache-ref> 指向同一 namespace;多节点部署不用原生二级缓存,改用 Redis。
  • [ ] 一级缓存与事务边界已核对:localCacheScope=SESSION(默认)下,同一事务内重复查询会命中旧值;关键「先查后改」查询加 flushCache="true",包含用户交互的流程不放在长事务里。
  • [ ] 延迟加载默认关闭(lazyLoadingEnabled=false);确需开启时用 DTO/VO 在 Service 内一次查完,不依赖 OpenSessionInViewFilter——它会把数据库连接持有时间拉长到整个 HTTP 请求。
  • [ ] 结果映射基线:大字段(TEXT/LONGTEXT)不进默认 SELECT *,需要时用 Cursor(MyBatis 3.5+)流式读取或单独查询;金额字段的实体类型用 BigDecimal,resultMap 显式写 jdbcType="DECIMAL" javaType="java.math.BigDecimal" 消除歧义(MyBatis 3.5.x 默认本就映射为 BigDecimal,问题多来自实体 double、显式 javaType 或自定义 TypeHandler)。

运行中 ​

  • [ ] N+1 信号:数据库 QPS 突增、平均 SQL 响应时间反而下降(大量简单 SQL 拉低均值)、连接池活跃数打满,三者同时出现先查嵌套查询。
  • [ ] 事务与连接池:长事务数量、锁等待时长、连接池等待数;事务里是否有 Feign/RPC 调用或用户交互等待。
  • [ ] 二级缓存命中率与一致性:命中率长期低于 30% 说明该表写多读少,缓存只增加序列化开销;出现「更新后仍读旧值」先查跨 namespace 写同一张表。
  • [ ] 堆内存构成:堆 dump 中 char[]/byte[] 占比持续上升,检查是否有大字段被整表加载或多条大字段记录同时映射。

故障时 ​

  • [ ] 接口返回 100 行却打了 101 条 SQL → 抓 SQL 日志看是否每行一次子查询 → 改 JOIN 嵌套结果或批量 IN,不要先加索引。
  • [ ] 数据已更新但页面仍显示旧值 → 确认查询与更新是否在同一事务、是否命中一级缓存 → 缩小事务范围或对关键查询设 flushCache="true"。
  • [ ] 序列化时报延迟加载异常 → 注意 LazyInitializationException 是 Hibernate/JPA 的异常名;MyBatis 侧常见 ExecutorException: Executor was closed.,或 PersistentObjectException/代理失效 → 看是否在事务提交后才访问懒加载属性 → 改为 DTO 投影或业务层提前加载,不要用 OpenSessionInView 掩盖。
  • [ ] 堆内存被 char[] 撑爆 → 查最近查询是否包含 TEXT/LONGTEXT 大字段 → 去掉大字段、分页或改 Cursor 流式处理。
  • [ ] 对账差几分钱 → 查金额字段的实体类型、ResultMap 的 javaType、自定义 TypeHandler 是否把值转成 double/Double(默认 BigDecimalTypeHandler 不会把 DECIMAL 变成 double)→ 统一为 BigDecimal 并回归历史数据,精度丢失不会抛异常。

把一门技术讲透到既懂也会用