Java 程序员成长记(二):菜鸟入职之 MyBatis XML「陷阱」

技术知识点

MyBatis 用法、EXPLAIN执行计划分析、索引优化原则、批量查询最佳实践

一、批量查询的「自信陷阱」

周五下午的阳光带着周末的慵懒,小彬盯着Jira上的新任务------「批量查询任务列表」。有了上周Maven依赖的经验,他自觉对Spring Boot多了几分底气,决定在MyBatis里大展身手。

"批量查询用标签就行吧。"他自言自语,熟练地在TaskMapper.xml里写下SQL:

sql 复制代码
<select id="listTasksByIds" resultType="com.xxtech.task.Task">
    SELECT * FROM task 
    <where>
        task_id IN
        <foreach collection="ids" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </where>
</select>

测试时传入2000个任务ID,小彬满怀期待地点击调用按钮。然而页面转圈圈的动画持续了8秒,浏览器才慢吞吞吐出数据。他感觉后背发凉------昨天红姐刚在群里强调"接口响应时间超过1秒就算缺陷",这要是被她测到...

"小彬,脸色这么差?"贝贝哥的声音从身后传来。这位代码洁癖患者永远穿着浆洗笔挺的格子衬衫,工位上的"代码即文档"标语被擦得反光。他探身看了眼控制台日志:"哟,批量查询用了8秒?让我看看你的SQL写得有多'优雅'。"

二、贝贝哥的「执行计划课」

贝贝哥拖过椅子,在IDEA里打开Mapper接口,右键点击"Execute SQL"。小彬这才发现,原来MyBatis自带的SQL执行工具能直接生成真实语句。

"先别急着跑,写SQL不看执行计划,相当于蒙眼开车。"贝贝哥在控制台输入EXPLAIN SELECT * FROM task WHERE task_id IN (...),敲下回车的瞬间,两人盯着结果愣住了:

sql 复制代码
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1  | SIMPLE      | task  | ALL  | NULL          | NULL | NULL    | NULL | 10W  | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+

"type=ALL,全表扫描!"贝贝哥的手指重重敲在显示器上,"你这SQL相当于让数据库把10万条数据全翻一遍,能不慢吗?"小彬盯着"rows=10W"的字样,突然想起老王说过"索引是数据库的高速公路"。

"先给task_id加索引。"贝贝哥递来键盘,"记住,主键虽然自带索引,但如果查询条件用的是其他字段,得单独加。"小彬颤抖着输入:

sql 复制代码
ALTER TABLE task ADD INDEX idx_task_id (task_id);

再次执行EXPLAIN,type变成了"range",rows骤降至2000。但贝贝哥仍皱着眉头:"用拼IN子句没问题,但MyBatis默认会把集合转成逗号分隔的字符串,万一ids是空列表呢?"他在XML里加了个判断:

bash 复制代码
<select id="listTasksByIds" resultType="com.xxtech.task.Task">
    SELECT * FROM task 
    <where>
        <if test="ids != null and ids.size() > 0">
            task_id IN
            <foreach collection="ids" item="id" open="(" separator="," close=")">
                #{id}
            </foreach>
        </if>
    </where>
</select>

"代码洁癖第一条:永远假设输入是脏的。"贝贝哥摸出酒精湿巾擦了擦键盘,"对了,返回值别用SELECT *,按需查询字段能减少IO消耗。"

三、工位上的「索引哲学」

修复完SQL,小彬迫不及待地再次测试------接口响应时间从8秒降到了300ms。他正想欢呼,贝贝哥突然指着控制台:"慢着,看看日志里的SQL参数。"

只见打印出的SQL里,IN子句后面跟着一长串数字,像条扭曲的蛇。"2000个参数全塞在一条SQL里?"贝贝哥摇摇头,"虽然现在数据库能处理,但万一传到10万个ID呢?内存溢出警告在向你招手。"

他翻开《MyBatis从入门到精通》,指着"批量操作最佳实践"章节:"超过1000个参数就该分批处理,用PageHelper或者自己写循环。"说着在Service层演示:

ini 复制代码
public List<Task> listTasksByIds(List<Long> ids) {
    int pageSize = 1000;
    List<Task> result = new ArrayList<>();
    for (int i = 0; i < ids.size(); i += pageSize) {
        int end = Math.min(i + pageSize, ids.size());
        result.addAll(taskMapper.listTasksByIds(ids.subList(i, end)));
    }
    return result;
}

"代码洁癖第二条:永远为未来的你留条后路。"贝贝哥从抽屉里摸出张键盘贴纸,上面写着"EXPLAIN = SQL的CT扫描","贴在键盘F键上,每次写SQL前按三遍------这是我的保命习惯。"

四、茶水间的「性能八卦」

午休时,小彬在茶水间遇到架构师老王。格子衬衫大叔正在研究新到的枸杞品种,闻言笑道:"听说你被贝贝哥抓去上执行计划课了?这小子当年写过更离谱的SQL------把like '%xxx%'写在索引字段前,直接让索引失效,气得DBA在他工位贴了'SQL毁灭者'标语。"

小彬差点喷出水来。老王压低声音:"其实索引也不是万能的。上周有个业务场景,需要按创建时间和状态联合查询,我让贝贝哥加了复合索引,结果他写成了(state, create_time)------顺序错了,导致create_time部分用不上索引,又被DBA教育了一顿。"

正说着,前端小浩晃着滑板进来:"你们后端写SQL能不能考虑下前端感受?上次我调一个批量接口,等了半分钟没响应,以为程序卡死了,结果发现后端在循环里逐条查数据库------那场面,就像用勺子挖游泳池的水。"

小浩从卫衣口袋里掏出颗糖扔给小彬:"红姐今晚要测你的接口,记得先自己写个压力测试。她上次把一个慢接口的测试用例写成了'持续并发100用户,持续1小时',直接把那哥们儿的夜宵都吓出来了。"

五、下班后的「索引攻坚战」

夜幕降临,小彬留在办公室研究索引优化。他打开MySQL官方文档,重点标注"最左匹配原则",又在本地数据库建了测试表,反复用EXPLAIN验证不同索引的效果。当他终于搞懂联合索引的顺序奥秘时,抬头发现贝贝哥居然还在工位上。

"在看索引?"贝贝哥端来两杯咖啡,"给你看个好玩的。"他打开一个GitHub仓库,里面全是团队自定义的MyBatis插件,其中一个叫"IndexAdvisor"的工具能自动分析SQL并建议索引。

"这是老王牵头写的,专治SQL性能癌。"贝贝哥演示着插件功能,"不过记住,工具只是辅助,真正的优化要靠理解原理。就像写代码,IDE能帮你补全,但逻辑是否优雅,还得看自己。"

小彬突然注意到贝贝哥的鼠标垫上印着"代码即文档",旁边用马克笔添了行小字:"SQL即架构的镜子"。这句话在深夜的屏幕光下显得格外醒目。

六、第二天的「红姐验收」

次日上午,红姐抱着测试用例来找小彬。这位短发御姐的白大褂口袋里永远插着三支不同颜色的笔,此刻正用红色笔圈着测试点:"批量查询2000条数据,响应时间≤500ms,测三次。"

第一次测试:289ms;第二次:295ms;第三次:281ms。红姐的嘴角难得扬起一丝弧度:"勉强及格。不过..."她翻到下一页,"如果传入空列表呢?如果有重复ID呢?"

小彬胸有成竹地点击测试按钮------接口友好地返回空列表,没有报错。贝贝哥不知何时站在身后,冲他比了个OK的手势,袖口露出半截"代码洁癖协会"的臂章。

"记住,性能优化是场持久战。"红姐收拾起测试文档,"就像你们后端写SQL,今天解决了全表扫描,明天可能又有锁竞争。但至少..."她瞥了眼贝贝哥的键盘贴纸,"现在你知道先看执行计划了。"

尾声:键盘上的「CT扫描」

下班前,小彬郑重地把"EXPLAIN = SQL的CT扫描"贴纸贴在键盘F8键上。贝贝哥路过时满意地点头,往他的工位上放了本《高性能MySQL》,扉页写着:"送给未来的SQL优化师------代码洁癖留"。

手机突然震动,小浩发来消息:"你的接口文档已同步Swagger,看在响应速度还行的份上,给你颁个'SQL急救小能手'勋章。"附带的图片里,勋章图案是一把手术刀插在SQL语句上,配文"精准切除性能肿瘤"。

小彬摸着键盘上的贴纸,突然明白为什么团队强调"实战导向"------所有的技术知识点,只有在生产环境的枪林弹雨中,才能真正变成自己的肌肉记忆。窗外的晚霞染透写字楼玻璃,他在笔记本上写下:"写SQL就像雕刻,先看骨架(索引),再磨细节(执行计划),最后还要经得起敲打(测试)。"

【下章预告】

《Java 程序员成长记(三):菜鸟入职之@Transactional「罢工」》

相关推荐
gadiaola14 分钟前
【JVM】Java虚拟机(二)——垃圾回收
java·jvm
coderSong25683 小时前
Java高级 |【实验八】springboot 使用Websocket
java·spring boot·后端·websocket
Mr_Air_Boy4 小时前
SpringBoot使用dynamic配置多数据源时使用@Transactional事务在非primary的数据源上遇到的问题
java·spring boot·后端
豆沙沙包?4 小时前
2025年- H77-Lc185--45.跳跃游戏II(贪心)--Java版
java·开发语言·游戏
年老体衰按不动键盘5 小时前
快速部署和启动Vue3项目
java·javascript·vue
灵感__idea5 小时前
JavaScript高级程序设计(第5版):无处不在的集合
前端·javascript·程序员
咖啡啡不加糖5 小时前
Redis大key产生、排查与优化实践
java·数据库·redis·后端·缓存
liuyang-neu5 小时前
java内存模型JMM
java·开发语言
大鸡腿同学5 小时前
纳瓦尔宝典
后端
UFIT5 小时前
NoSQL之redis哨兵
java·前端·算法