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「罢工」》

相关推荐
葫芦和十三13 小时前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp14 小时前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑14 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯15 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan17 小时前
多Agent之间的区别
后端
青石路19 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充19 小时前
1.面向对象设计思想
后端
IT_陈寒19 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro20 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗20 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端