常见坑:缓存脏读、驼峰映射、SQL 模式

实战中报错最少、排查最费时的往往是三件事:缓存读到旧数据、字段名对不上、数据库 SQL 模式把"看起来能跑"的 SQL 拒了。本节逐个拆解:现象、原因、对策。

坑一:缓存脏读

MyBatis 有两级缓存:

  • 一级缓存:SqlSession 级,默认开启,同一会话内重复查询直接命中。Spring 里 SqlSessionTemplate 每次新建 SqlSession,只有事务内共享会话,所以"查两次只打一条 SQL 日志"多发生在事务方法里。
  • 二级缓存:跨会话、namespace 级,需在 Mapper XML 显式加 <cache/> 才开启。

脏读来源:二级缓存按 namespace 隔离,两个 Mapper 操作同一张表、或别的服务/脚本直改库,缓存不会自动失效。对策:

<settings>
    <!-- 需要实时数据时:每条语句不共享一级缓存 -->
    <setting name="localCacheScope" value="STATEMENT"/>
</settings>

二级缓存只适合读多写少、无外部改库的表;同一张表尽量收敛到一个 Mapper,或多 Mapper 共享失效域用 <cache-ref>(缓存行为以官方文档为准)。

坑二:驼峰映射失败

数据库列 create_time 想自动映射成实体 createTime,默认不会成功,必须开全局开关:

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

Spring Boot 对应 mybatis.configuration.map-underscore-to-camel-case: true(MyBatis-Plus 默认开启)。没开时的症状:createTime 为 null 而 create_time 有值。相关点:自动映射受 autoMappingBehavior 控制(默认 PARTIAL,嵌套 resultMap 内不自动映射);多表 join 重名列互相覆盖,用别名区分:

SELECT u.id, u.name, o.amount AS orderAmount FROM ...

复杂映射直接写 resultMap,别依赖自动映射去猜。

坑三:MySQL SQL 模式(sql_mode)

sql_mode 是数据库的"行为开关",MySQL 5.7 起默认含 ONLY_FULL_GROUP_BY 严格模式,很多"以前能跑"的 SQL 就此报错(以 MySQL 官方文档为准):

-- 报错:name 不在 GROUP BY 中,也不在聚合函数内
-- Expression #2 of SELECT list is not in GROUP BY clause ...
SELECT user_id, name, COUNT(*) FROM orders GROUP BY user_id;

修复是改写 SQL,而不是关 sql_mode:

SELECT user_id, MAX(name) AS name, COUNT(*) FROM orders GROUP BY user_id;

严格模式还影响 INSERT:缺默认值、非法日期会直接报错而非静默改写。排查"我 SQL 没问题啊"时,先看会话 sql_mode 与表结构默认值;线上别为跑通代码放宽 sql_mode,应让 SQL 符合模式。

三个坑的共性规律:先怀疑框架默认值,再怀疑 SQL 是否符合运行环境约定。缓存讲作用域、驼峰靠显式开关、SQL 模式要求 SQL 自洽,按这三条排查能少熬几个夜。

笔记加载中…