跳到主要内容

群里又在问“这条慢SQL是谁写的”?教你一招,日志直达代码行

· 阅读需 6 分钟

前言: 只要你做过 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 循环里的验证逻辑?

你平时的常规操作是:

  1. 全局搜索 sys_user 或者方法名。
  2. 找出 10 个可疑的 Service 调用。
  3. 靠经验猜,或者一个个加 log.info() 重新发版。
  4. 运气好半小时找到,运气不好一整个下午全搭进去。

现有的工具也帮不上大忙:

  • 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';

img.png 在这个状态下,你只需要在 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 做了精准的过滤:

  1. 它会过滤掉所有的 JDK 代理类、Spring CGLIB 增强类、框架底层反编译类。
  2. 逐层向上剥离,精准抓取到第一个属于你业务包(比如 com.yourcompany.*)的那一行代码
  3. 将这行代码(类名 + 行号)附着在 SQL 日志中打印出来。

4. 灵魂拷问:生产环境抓堆栈,性能不会炸吗?

很多老司机看到这里,脑海里一定会飘过一个弹幕:“获取 StackTrace 极度消耗 CPU,这玩意儿能上生产?”

这个问题非常专业。我们做过详细的 Benchmark:

  1. 测试环境全开:在开发和测试环境,强烈建议全量开启 show-caller,IDE 点击跳转爽到飞起,损耗在局域网下完全无感(<1%)。
  2. 生产环境按需触发:生产环境不需要每条 SQL 都打行号,你可以配合 slow-sql-millis 参数,只对慢 SQL 获取堆栈。比如只对耗时 >1000ms 的 SQL 抓取 caller。既然 SQL 都已经慢成这样了,几毫秒的堆栈抓取耗时完全可以忽略不计,但为你保留了最珍贵的“案发现场证据”。

5. 除了行号,我们还需要怎样的 SQL 日志?

作为一个经常修福报的一线开发,我认为一个合格的持久层,日志应该做到三点,DLZ-DB 也全部默认实现了:

  1. 完整的执行 SQL:不要一堆问号 ?,把参数实打实地拼进去,方便我直接复制到 Navicat 里执行(省去了手工拼参数的痛苦)。
  2. 真实耗时统计:清晰标明 SQL 耗时,触发慢查询阈值自动标红(WARN 级别)。
  3. 精准的 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 神器?欢迎在评论区教我做事!