SQLInsight:一行依赖,自动追踪API背后的每一条SQL

起因

前几天看到一篇关于SQL监控的文章,功能挺实用:能追踪API到SQL的调用链,能看到完整的可执行SQL,还能检测N+1查询。我觉得不错,就点了个赞。

结果今天再去看,发现代码库没了。

这让我想起一个问题:开源到底意味着什么?

这些年学习和成长的过程中,我一直在学习各种开源项目的代码,从Spring Boot到MyBatis,从Dubbo到Seata。每次看到优秀的开源项目,都有一种"原来可以这样做"的豁然开朗。更重要的是,开源让我们能够和同行一起交流、一起进步。

所以我一直乐于分享自己的技术积累,最近的JobFlow任务调度项目也是这个想法的延续。帮助别人的过程中,也在充实自己。

既然别人的库没了,那我就自己实现一个,完全开源。

这就是SQLInsight项目的由来。


技术方案

在动手之前,我梳理了几种常见的实现思路。

为什么不用这些方案

方案一:注解 + AOP

这是最常见的想法:

java 复制代码
@SqlMonitor
public class OrderService {
    // ...
}

不采用的原因:

  • 需要在每个类/方法上加注解,侵入性强
  • 开发者容易忘记加
  • 维护成本高

方案二:JavaAgent字节码增强

这个方案技术上很强大,但现在的应用已经有太多Agent了:Jacoco做代码覆盖率、SkyWalking做链路追踪、Arthas做诊断。再加一个Agent,启动参数越来越长,维护越来越复杂。不想给大家增加负担。

方案三:IDEA插件

IDEA插件方案有几个问题:

  1. IDEA本来就慢,装的插件越多,越容易卡顿
  2. 现在AI插件已经很多了,IDE已经够拥挤
  3. 只能开发阶段用,生产环境出了问题无法排查

这个思路不适合生产环境的监控工具。

采用的方案

经过思考,我决定采用:动态代理 + StackTrace

  1. 动态代理:在最底层拦截

思路很简单:不管你用JPA、MyBatis、Hibernate还是JdbcTemplate,最终都要通过JDBC的Connection执行SQL。

所以我们直接在最底层做代理:

markdown 复制代码
应用启动
    ↓
自动拦截DataSource创建
    ↓  
动态代理:DataSource → Connection → PreparedStatement
    ↓
SQL执行时,自动记录

这个思路参考了Seata的实现。Seata也是通过动态代理DataSource来实现分布式事务的。

好处:

  • 一行依赖即可接入
  • 不需要改业务代码
  • 支持所有ORM框架
  • 支持所有DataSource(Druid/HikariCP/C3P0)
  1. StackTrace:自动追踪调用链

传统的API追踪需要在Controller层加AOP,但我们用StackTrace来实现:

java 复制代码
// SQL执行时
StackTraceElement[] stack = Thread.currentThread().getStackTrace();

// 向上查找,找到Controller
for (StackTraceElement element : stack) {
    Class<?> clazz = Class.forName(element.getClassName());
    if (clazz.isAnnotationPresent(RestController.class)) {
        // 找到了,解析API路径
        return parseApiInfo(clazz, element.getMethodName());
    }
}

这样就能自动知道:

scss 复制代码
GET /api/orders/123
    ↓
OrderController.getOrder()
    ↓
OrderService.getById()
    ↓
OrderMapper.selectById()
    ↓
SELECT * FROM orders WHERE id = 123 (15ms)

完整的调用链,自动构建,不需要写任何额外代码。

  1. SQL解析:JSQLParser

SQL解析用JSQLParser 4.9版本,这个库很成熟,PageHelper也在用它。可以用来:

  • 识别SQL类型(SELECT/INSERT/UPDATE/DELETE)
  • 提取表名,统计访问频率
  • SQL格式化
  1. 接入方式

动态代理 + StackTrace的组合,可以让接入变得非常简单。

开发者只需要:

java 复制代码
// 1. 添加依赖
<dependency>
    <groupId>com.github</groupId>
    <artifactId>sqlinsight-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

// 2. 启动类加个注解(如果需要的话)
@SpringBootApplication
@EnableSqlInsight  // 这一行
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

如果采用Spring Boot自动配置,甚至连注解都可以省略。


关于监控系统集成

很多人可能会问:为什么不直接集成Prometheus?

我的想法是:建议业务系统自己接入Prometheus,但SQLInsight作为一个工具,不应该渗透到业务系统的监控体系中。

原因:

  1. 每个公司的监控体系不一样,有的用Prometheus,有的用其他方案
  2. 作为一个通用工具,不应该强制用户使用某种监控方案
  3. 保持工具的纯粹性和简洁性

不过,SQLInsight会提供标准的输出接口:

  • 日志输出(可以对接ELK)
  • 数据库持久化(可以自己做可视化)
  • 事件回调(可以自己集成任何监控系统)

用户可以根据自己的需求,灵活接入自己的监控体系。


数据持久化

SQLInsight支持可选的持久化功能:

yaml 复制代码
sqlinsight:
  persistence:
    enabled: true
    type: mysql  # 或者 postgresql

持久化内容:

  • API路径和方法
  • SQL语句和参数
  • 执行时间
  • 调用链路
  • 时间戳

持久化的好处:

  • 可以做历史查询
  • 可以做趋势分析
  • 可以做慢SQL统计
  • 后续可以做Web页面查看

核心功能

1. API到SQL的完整追踪

sql 复制代码
输入:/api/orders/123
输出:
  └─ OrderController.getOrder()
      └─ OrderService.getById()
          └─ SQL: SELECT * FROM orders WHERE id = 123 (15ms)

2. SQL执行统计

每个API执行了多少条SQL:

sql 复制代码
GET /api/orders/123
├─ SELECT * FROM orders WHERE id = ? (15ms)
├─ SELECT * FROM order_items WHERE order_id = ? (8ms)  
└─ SELECT * FROM users WHERE id = ? (5ms)
总计: 3条SQL,28ms

3. SQL执行时间

sql 复制代码
慢SQL检测:
[警告] SELECT * FROM orders WHERE status = 'PAID' ORDER BY created_at DESC
        执行时间: 3500ms
        API: GET /api/orders/list
        建议: 添加索引 (status, created_at)

4. 完整的可执行SQL

不再是带问号的SQL,而是可以直接复制到数据库执行的完整SQL:

sql 复制代码
原始: SELECT * FROM orders WHERE id = ? AND user_id = ?
完整: SELECT * FROM orders WHERE id = 123 AND user_id = 456

这对于排查问题非常有用,可以直接拿到数据库执行,看看结果是否符合预期。

5. N+1查询检测

自动检测循环查询问题:

sql 复制代码
[警告] 检测到N+1查询
       API: GET /api/orders/list
       SQL数量: 23条SELECT
       建议: 使用JOIN或批量查询优化

开发计划

第一阶段:核心功能

  • 动态代理DataSource
  • 动态代理Connection
  • 动态代理PreparedStatement
  • StackTrace解析
  • API信息提取
  • SQL参数绑定
  • 控制台输出
  • 日志输出

第二阶段:增强功能

  • 数据库持久化
  • 慢SQL检测
  • N+1查询检测
  • 性能优化(缓存)
  • 配置项支持

性能考虑

很多人会担心:这样监控,会不会影响性能?

影响可以忽略不计。

性能优化策略:

  1. 缓存Controller信息
java 复制代码
// 解析一次后缓存
Map<String, ControllerInfo> cache = new ConcurrentHashMap<>();

首次解析:约50微秒 缓存命中:<1微秒

  1. 异步记录
java 复制代码
// SQL信息异步写入
executor.submit(() -> {
    persistence.save(sqlExecution);
});
  1. 限制栈深度
java 复制代码
// 只扫描前50个栈帧
for (int i = 0; i < Math.min(stack.length, 50); i++) {
    // ...
}

预期性能影响:微秒级,可忽略。


使用示例

控制台输出

bash 复制代码
2026-01-05 10:30:45.123 [SQL] GET /api/orders/123
  └─ OrderController.getOrder() 
      └─ SELECT * FROM orders WHERE id = 123 (15ms)
      
2026-01-05 10:30:45.145 [SQL] GET /api/orders/123
  └─ OrderController.getOrder()
      └─ SELECT * FROM order_items WHERE order_id = 123 (8ms)

2026-01-05 10:30:45.850 [SLOW-SQL] GET /api/reports/summary
  └─ ReportController.getSummary()
      └─ SELECT COUNT(*), SUM(amount) FROM orders 
         WHERE created_at > '2025-01-01' GROUP BY user_id (3500ms)

日志输出(JSON格式,ELK友好)

json 复制代码
{
  "timestamp": "2026-01-05T10:30:45.123Z",
  "api": {
    "path": "/api/orders/123",
    "method": "GET",
    "controller": "OrderController.getOrder()"
  },
  "sql": {
    "statement": "SELECT * FROM orders WHERE id = ?",
    "parameters": [123],
    "executableSql": "SELECT * FROM orders WHERE id = 123",
    "duration": 15,
    "type": "SELECT"
  },
  "callStack": "OrderController.getOrder() → OrderService.getById() → OrderMapper.selectById()"
}

Web页面查询(计划中)

vbnet 复制代码
┌─────────────────────────────────────────────────────┐
│ SQL监控查询                                           │
├─────────────────────────────────────────────────────┤
│ 查询条件:                                            │
│   API路径: /api/orders/*                             │
│   时间范围: 最近1小时                                 │
│   执行时间: > 1000ms                                 │
│                                    [查询]             │
├─────────────────────────────────────────────────────┤
│ 结果:                                                │
│                                                      │
│ GET /api/orders/list                     3500ms     │
│   SELECT * FROM orders WHERE status = 'PAID'...     │
│   执行时间: 3500ms                                   │
│   调用链: OrderController → OrderService             │
│                                                      │
│ GET /api/orders/export                   2800ms     │
│   SELECT * FROM orders WHERE...                     │
│   执行时间: 2800ms                                   │
│   调用链: OrderController → ExportService            │
└─────────────────────────────────────────────────────┘

写在最后

做SQLInsight这个项目,不是为了跟谁竞争,也不是为了证明什么。只是觉得技术应该开放共享,而不是闭门造车。

如果这个工具能帮助到一些同行,让大家少踩一些坑,少加一些班,那就值了。

项目采用MIT协议,可以自由使用、修改、商用和分发。

相关推荐
为自己_带盐10 小时前
从零开始玩转 Microsoft Agent Framework:我的 MAF 实践之旅-第二篇
后端·microsoft·ai·.net
乌日尼乐11 小时前
【Java基础整理】java数组详解
java·后端
百***787511 小时前
GLM-4.7开源大模型实测与API接入指南:编码/办公全场景适配
开源
想用offer打牌11 小时前
一站式讲清IO多路复用(轻松愉悦版)
后端·面试·操作系统
嘻哈baby11 小时前
网络延迟与丢包问题排查实战
后端
东百牧码人11 小时前
PostgreSQL 的得力助手:psql.exe 使用指南
后端
东百牧码人11 小时前
C# TimeOfDay TimeOnly如何比较
后端
开心就好202512 小时前
如何保护 iOS IPA 文件中的资源与文件安全
后端
上进小菜猪12 小时前
基于 YOLOv8 的学生课堂行为检测(举手、看书、写作业、玩手机)-完整项目源码
后端
涡能增压发动积12 小时前
英雄联盟证书过期上热搜?吃透 HTTPS 核心:证书、TLS、HTTP3/QUIC 故障复盘全解析
后端