群里又在问“这条慢SQL是谁写的”?教你一招,日志直达代码行
前言: 只要你做过 Java 后端,绝对在工作群里经历过这种让人窒息的时刻: DBA 或者运维甩出一截张牙舞爪的慢 SQL:“这 SQL 谁写的?把库跑挂了!” 全组人陷入死寂,你默默打开 IDEA,开始了痛苦的“案发现场排查”。
1. 案发现场:排查慢 SQL,为什么这么痛苦?
很多时候,排查慢 SQL 最难的不是怎么优化索引,而是**“我根本不知道这破 SQL 是哪段代码触发的”**。
比如 DBA 发来这样一条 SQL:
SELECT * FROM sys_user WHERE status = 1 AND type = 'ADMIN';
你一看,表名 sys_user,整个系统有 30 多个地方在查这张表:
- 是定时任务扫表?
- 是后台管理系统的导出报表?
- 还是某个新来的实习生写在
for循环里的验证逻辑?
你平时的常规操作是:
- 全局搜索
sys_user或者方法名。 - 找出 10 个可疑的 Service 调用。
- 靠经验猜,或者一个个加
log.info()重新发版。 - 运气好半小时找到,运气不好一整个下午全搭进去。
现有的工具也帮不上大忙:
- MyBatis 日志:最多告诉你
UserMapper.selectList,但如果有 20 个 Service 调用了这个 Mapper 呢?线索全断了。 - P6Spy / 各种 SQL 插件:能把问号
?替换成真实参数,但这并不能告诉你调用的源头是谁。 - APM(SkyWalking 等):确实能看链路,但太重了,很多小项目或测试环境根本没装。而且,开发者最习惯的还是直接看控制台日志。
2. 破局:如果日志里直接长出“代码行号”呢?
每次经历这种折磨,我都在想:为什么日志不能直接告诉我,这条 SQL 是在哪个类的第几行执行的?
如果线上打印的 SQL 日志长这样,是不是瞬间清爽了:
[SLOW SQL] 耗时: 1250ms | 来源: UserServiceImpl.java:42
执行 SQL: SELECT * FROM sys_user WHERE status = 1 AND type = 'ADMIN';
在这个状态下,你只需要在 IDEA 控制台轻轻一点 DsPluginImplMysql.java:113,瞬间穿越到案发现场。
没有任何猜测,不用全局搜索,1 秒钟直接破案。
这就是我梦寐以求的开发体验。
3. 我是怎么实现它的?(DLZ-DB 的解法)
为了彻底解决这个痛点,我在开发轻量级 Java 数据访问框架 DLZ-DB 时,把这个功能做成了“一等公民”。
不用改写任何业务代码,只要在配置里加一行:
dlz:
db:
# 开启调用方追踪
show-caller: true
# 可选:只针对执行超过 500ms 的慢 SQL 打印堆栈追踪
slow-sql-millis: 500
它的工作原理其实很巧妙: 当 SQL 执行时,框架会在底层获取当前线程的调用栈(Stack Trace)。但是,获取完整堆栈是非常耗性能的。为了兼顾“可观测性”和“性能”,DLZ-DB 做了精准的过滤:
- 它会过滤掉所有的 JDK 代理类、Spring CGLIB 增强类、框架底层反编译类。
- 逐层向上剥离,精准抓取到第一个属于你业务包(比如
com.yourcompany.*)的那一行代码。 - 将这行代码(类名 + 行号)附着在 SQL 日志中打印出来。
4. 灵魂拷问:生产环境抓堆栈,性能不会炸吗?
很多老司机看到这里,脑海里一定会飘过一个弹幕:“获取 StackTrace 极度消耗 CPU,这玩意儿能上生产?”
这个问题非常专业。我们做过详细的 Benchmark:
- 测试环境全开:在开发和测试环境,强烈建议全量开启
show-caller,IDE 点击跳转爽到飞起,损耗在局域网下完全无感(<1%)。 - 生产环境按需触发:生产环境不需要每条 SQL 都打行号,你可以配合
slow-sql-millis参数,只对慢 SQL 获取堆栈。比如只对耗时 >1000ms 的 SQL 抓取 caller。既然 SQL 都已经慢成这样了,几毫秒的堆栈抓取耗时完全可以忽略不计,但为你保留了最珍贵的“案发现场证据”。
5. 除了行号,我们还需要怎样的 SQL 日志?
作为一个经常修福报的一线开发,我认为一个合格的持久层,日志应该做到三点,DLZ-DB 也全部默认实现了:
- 完整的执行 SQL:不要一堆问号
?,把参数实打实地拼进去,方便我直接复制到 Navicat 里执行(省去了手工拼参数的痛苦)。 - 真实耗时统计:清晰标明 SQL 耗时,触发慢查询阈值自动标红(WARN 级别)。
- 精准的 Caller 定位:一行直达业务代码。
6. 写在最后:试试这个轻量级方案
我们日常开发中,习惯了 MyBatis 庞大的体系,有时候甚至麻木了它带来的一些痛点。
如果你正在开发一个中小型项目、内部工具、多租户 SaaS,或者你想用 AI 快速辅助生成无样板的 Java 代码,极其推荐你试试我开源的 DLZ-DB。它不到 7000 行代码,去掉了 Mapper 和 XML,极度轻量。
引入它非常简单:
<dependency>
<groupId>top.dlzio</groupId>
<artifactId>dlz-db-spring-boot-starter</artifactId>
<version>7.0.1-4</version> <!-- 稳如老狗的最新版 -->
</dependency>
代码里直接就能查(天然多数据源,不需要注解放哪的问题):
List<User> users = DB.Pojo.select(User.class).where("status", 1).list();
项目开源地址(求个 Star 鼓励一下日常掉头发的开源作者): 👉 https://github.com/dingkui/dlz-db
如果你对“如何用几行代码搞定动态数据源”、“为什么不用 MyBatis-Plus”感兴趣,欢迎关注我,下一篇给你看点更硬核的代码魔法。
评论区互动: 你们团队平时是怎么排查慢 SQL 源头的?全靠
grep还是上了什么 APM 神器?欢迎在评论区教我做事!