主题
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并回归历史数据,精度丢失不会抛异常。