SpringBoot 自研运行时 SQL 调用树,3 分钟定位慢 SQL!

在复杂的业务系统中,一个接口往往会执行多条SQL,如何直观地看到这些SQL的调用关系和执行情况?

本文将使用SpringBoot + MyBatis拦截器构建一个SQL调用树可视化系统。

图片

图片

项目背景

在日常开发中,我们经常遇到这样的场景:

复杂查询链路:一个用户详情接口可能涉及用户基本信息、订单列表、订单详情等多个查询性能问题排查:系统响应慢,需要快速定位是哪个SQL影响了性能开发调试需求:希望能直观地看到SQL的执行顺序和层次关系

基于这些需求,实现了一个基于SpringBoot + MyBatis的SQL调用树可视化系统。

系统功能特性

该系统具有以下核心功能:

核心功能

MyBatis拦截器:通过拦截器机制捕获SQL执行过程,无需修改业务代码调用树构建:自动构建SQL调用的层次关系可视化展示:使用D3.js实现树形结构的可视化展示性能监控:记录SQL执行时间,自动标识慢SQL统计分析:提供SQL执行统计信息和性能分析数据管理:支持数据的查询、清理和导出

技术实现

后端技术:Spring Boot 3.4.5 + MyBatis 3.0.3 + H2数据库前端技术:HTML5 + Tailwind CSS + D3.js v7配置管理:支持动态配置慢SQL阈值等参数

项目结构

技术栈

后端技术栈

Spring Boot 3.4.5:应用框架MyBatis 3.0.3:数据访问层和拦截器H2 Database:内存数据库(演示用)Lombok:简化代码编写Jackson:JSON序列化

前端技术栈

HTML5 + Tailwind CSS:页面结构和样式D3.js v7:数据可视化Font Awesome:图标库原生JavaScript:前端交互逻辑

项目目录结构
springboot-sql-tree/
├── src/main/java/com/example/sqltree/
│   ├── SqlTreeApplication.java          ## 启动类
│   ├── SqlInterceptor.java              ## MyBatis拦截器
│   ├── SqlCallTreeContext.java          ## 调用树上下文管理
│   ├── SqlNode.java                     ## SQL节点数据模型
│   ├── SqlTreeController.java           ## REST API控制器
│   ├── DemoController.java              ## 演示API
│   ├── UserService.java                 ## 用户服务(演示用)
│   ├── UserMapper.java                  ## 用户数据访问
│   └── OrderMapper.java                 ## 订单数据访问
├── src/main/resources/
│   ├── application.yml                  ## 应用配置
│   ├── schema.sql                       ## 数据库表结构
│   ├── data.sql                         ## 示例数据
│   └── static/
│       ├── index.html                   ## 前端页面
│       └── sql-tree.js                  ## 前端JavaScript
└── pom.xml                              ## Maven配置

这或许是一个对你有用的开源项目,mall项目是一套基于 SpringBoot3 + Vue 的电商系统(Github标星60K),后端支持多模块和 2024最新微服务架构 ,采用Docker和K8S部署。包括前台商城项目和后台管理系统,能支持完整的订单流程!涵盖商品、订单、购物车、权限、优惠券、会员、支付等功能!

  • Boot项目:https://github.com/macrozheng/mall

  • Cloud项目:https://github.com/macrozheng/mall-swarm

  • 教程网站:https://www.macrozheng.com

项目演示:

图片

核心实现详解

1. MyBatis拦截器:零侵入的核心

这是整个系统的核心组件,通过MyBatis的插件机制实现SQL执行的无感知拦截:

@Component
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class SqlInterceptor implements Interceptor {@Autowiredprivate SqlCallTreeContext sqlCallTreeContext;@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 检查是否启用追踪if (!sqlCallTreeContext.isTraceEnabled()) {return invocation.proceed();}long startTime = System.currentTimeMillis();Object[] args = invocation.getArgs();MappedStatement mappedStatement = (MappedStatement) args[0];Object parameter = args[1];// 获取SQL信息BoundSql boundSql = mappedStatement.getBoundSql(parameter);String sql = boundSql.getSql();String sqlType = mappedStatement.getSqlCommandType().name();// 获取调用栈信息StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();String serviceName = extractServiceName(stackTrace);String methodName = extractMethodName(stackTrace);// 创建SQL节点SqlNode sqlNode = SqlNode.builder().nodeId(UUID.randomUUID().toString()).sql(formatSql(sql)).sqlType(sqlType).threadName(Thread.currentThread().getName()).serviceName(serviceName).methodName(methodName).startTime(LocalDateTime.now()).parameters(extractParameters(boundSql, parameter)).depth(sqlCallTreeContext.getCurrentDepth() + 1).build();// 进入SQL调用sqlCallTreeContext.enter(sqlNode);try {// 执行SQLObject result = invocation.proceed();// 记录执行结果long executionTime = System.currentTimeMillis() - startTime;int affectedRows = calculateAffectedRows(result, sqlType);sqlCallTreeContext.exit(sqlNode, affectedRows, null);return result;} catch (Exception e) {// 记录异常信息sqlCallTreeContext.exit(sqlNode, 0, e.getMessage());throw e;}}private String extractServiceName(StackTraceElement[] stackTrace) {for (StackTraceElement element : stackTrace) {String className = element.getClassName();if (className.contains("Service") && !className.contains("$")) {return className.substring(className.lastIndexOf('.') + 1);}}return"Unknown";}private String extractMethodName(StackTraceElement[] stackTrace) {for (StackTraceElement element : stackTrace) {if (element.getClassName().contains("Service")) {return element.getMethodName();}}return"unknown";}private int calculateAffectedRows(Object result, String sqlType) {if ("SELECT".equals(sqlType) && result instanceof List) {return ((List<?>) result).size();} elseif (result instanceof Integer) {return (Integer) result;}return0;}
}

关键特性

🎯 精准拦截:同时拦截查询和更新操作 🔒 异常安全:确保业务逻辑不受监控影响 📊 丰富信息:自动提取Service调用信息和执行统计

2. 调用树上下文管理器:线程安全的数据管理

SqlCallTreeContext负责管理SQL调用树的构建和存储,采用线程安全的设计:

@Component
publicclass SqlCallTreeContext {// 线程本地存储privatefinal ThreadLocal<Stack<SqlNode>> callStack = new ThreadLocal<Stack<SqlNode>>() {@Overrideprotected Stack<SqlNode> initialValue() {returnnew Stack<>();}};privatefinal ThreadLocal<List<SqlNode>> rootNodes = new ThreadLocal<List<SqlNode>>() {@Overrideprotected List<SqlNode> initialValue() {returnnew ArrayList<>();}};// 全局会话存储privatefinal Map<String, List<SqlNode>> globalSessions = new ConcurrentHashMap<>();// 统计信息privatefinal AtomicLong totalSqlCount = new AtomicLong(0);privatefinal AtomicLong slowSqlCount = new AtomicLong(0);privatefinal AtomicLong errorSqlCount = new AtomicLong(0);privatefinal AtomicLong totalExecutionTime = new AtomicLong(0);// 配置参数privatevolatilelong slowSqlThreshold = 1000; // 慢SQL阈值(毫秒)privatevolatileboolean traceEnabled = true; // 追踪开关/*** 进入SQL调用*/public SqlNode enter(SqlNode sqlNode) {if (!traceEnabled) {return sqlNode;}Stack<SqlNode> stack = callStack.get();// 设置深度sqlNode.setDepth(stack.size() + 1);// 建立父子关系if (!stack.isEmpty()) {SqlNode parent = stack.peek();parent.addChild(sqlNode);sqlNode.setParentId(parent.getNodeId());} else {// 根节点rootNodes.get().add(sqlNode);}// 压入栈stack.push(sqlNode);return sqlNode;}/*** 退出SQL调用*/public void exit(SqlNode sqlNode, int affectedRows, String errorMessage) {if (!traceEnabled) {return;}// 设置结束时间和结果sqlNode.setEndTime(LocalDateTime.now());sqlNode.setAffectedRows(affectedRows);sqlNode.setErrorMessage(errorMessage);// 计算执行时间long executionTime = Duration.between(sqlNode.getStartTime(), sqlNode.getEndTime()).toMillis();sqlNode.setExecutionTime(executionTime);// 标记慢SQLif (executionTime > slowSqlThreshold) {sqlNode.setSlowSql(true);slowSqlCount.incrementAndGet();}// 标记错误SQLif (errorMessage != null) {errorSqlCount.incrementAndGet();}// 更新统计totalSqlCount.incrementAndGet();totalExecutionTime.addAndGet(executionTime);// 弹出栈Stack<SqlNode> stack = callStack.get();if (!stack.isEmpty()) {stack.pop();// 如果栈为空,说明调用树完成,保存到全局会话if (stack.isEmpty()) {String sessionKey = generateSessionKey();globalSessions.put(sessionKey, new ArrayList<>(rootNodes.get()));rootNodes.get().clear();}}}/*** 获取当前调用深度*/public int getCurrentDepth() {return callStack.get().size();}/*** 获取当前线程的根节点*/public List<SqlNode> getRootNodes() {returnnew ArrayList<>(rootNodes.get());}/*** 获取所有会话*/public Map<String, List<SqlNode>> getAllSessions() {returnnew HashMap<>(globalSessions);}/*** 清理会话数据*/public void clearSessions() {globalSessions.clear();rootNodes.get().clear();callStack.get().clear();}/*** 生成会话键*/private String generateSessionKey() {return Thread.currentThread().getName() + "_" + System.currentTimeMillis();}/*** 获取统计信息*/public SqlStatistics getStatistics() {return SqlStatistics.builder().totalSqlCount(totalSqlCount.get()).slowSqlCount(slowSqlCount.get()).errorSqlCount(errorSqlCount.get()).averageExecutionTime(totalSqlCount.get() > 0 ? totalExecutionTime.get() / totalSqlCount.get() : 0).build();}// Getter和Setter方法public boolean isTraceEnabled() {return traceEnabled;}public void setTraceEnabled(boolean traceEnabled) {this.traceEnabled = traceEnabled;}public long getSlowSqlThreshold() {return slowSqlThreshold;}public void setSlowSqlThreshold(long slowSqlThreshold) {this.slowSqlThreshold = slowSqlThreshold;}
}

线程安全:使用ThreadLocal确保多线程环境下的数据隔离智能建树:自动识别父子关系,构建完整调用树实时统计:同步更新性能统计信息

3. 数据模型:完整的SQL节点信息
@Data
publicclass SqlNode {private String nodeId;              // 节点唯一标识private String sql;                 // SQL语句private String formattedSql;        // 格式化后的SQLprivate String sqlType;             // SQL类型privateint depth;                  // 调用深度private String threadName;          // 线程名称private String serviceName;         // Service类名private String methodName;          // Service方法名private LocalDateTime startTime;    // 开始时间private LocalDateTime endTime;      // 结束时间privatelong executionTime;         // 执行耗时privateboolean slowSql;            // 是否为慢SQLprivateint affectedRows;           // 影响行数private String errorMessage;        // 错误信息private List<Object> parameters;    // SQL参数private List<SqlNode> children;     // 子节点// 智能分析方法public boolean isSlowSql(long threshold) {return executionTime > threshold;}public int getTotalNodeCount() {return1 + children.stream().mapToInt(SqlNode::getTotalNodeCount).sum();}public int getMaxDepth() {return children.isEmpty() ? depth : children.stream().mapToInt(SqlNode::getMaxDepth).max().orElse(depth);}
}
4. RESTful API:完整的数据接口

SqlTreeController提供完整的REST API接口,支持数据查询、配置管理和系统监控:

@RestController
@RequestMapping("/api/sql-tree")
publicclass SqlTreeController {@Autowiredprivate SqlCallTreeContext sqlCallTreeContext;/*** 获取当前线程的SQL调用树*/@GetMapping("/current")public ResponseEntity<List<SqlNode>> getCurrentTree() {List<SqlNode> rootNodes = sqlCallTreeContext.getRootNodes();return ResponseEntity.ok(rootNodes);}/*** 获取所有会话的SQL调用树*/@GetMapping("/sessions")public ResponseEntity<Map<String, List<SqlNode>>> getAllSessions() {Map<String, List<SqlNode>> sessions = sqlCallTreeContext.getAllSessions();return ResponseEntity.ok(sessions);}/*** 获取指定会话的SQL调用树*/@GetMapping("/session/{sessionKey}")public ResponseEntity<List<SqlNode>> getSessionTree(@PathVariable String sessionKey) {Map<String, List<SqlNode>> sessions = sqlCallTreeContext.getAllSessions();List<SqlNode> sessionTree = sessions.get(sessionKey);if (sessionTree != null) {return ResponseEntity.ok(sessionTree);} else {return ResponseEntity.notFound().build();}}/*** 清理所有调用树数据*/@DeleteMapping("/clear")public ResponseEntity<Map<String, Object>> clearAllTrees() {sqlCallTreeContext.clearSessions();Map<String, Object> response = new HashMap<>();response.put("success", true);response.put("message", "All SQL trees cleared successfully");response.put("timestamp", LocalDateTime.now());return ResponseEntity.ok(response);}/*** 获取统计信息*/@GetMapping("/statistics")public ResponseEntity<Map<String, Object>> getStatistics() {SqlStatistics stats = sqlCallTreeContext.getStatistics();Map<String, Object> response = new HashMap<>();response.put("totalSqlCount", stats.getTotalSqlCount());response.put("slowSqlCount", stats.getSlowSqlCount());response.put("errorSqlCount", stats.getErrorSqlCount());response.put("averageExecutionTime", stats.getAverageExecutionTime());response.put("slowSqlThreshold", sqlCallTreeContext.getSlowSqlThreshold());response.put("traceEnabled", sqlCallTreeContext.isTraceEnabled());return ResponseEntity.ok(response);}/*** 配置追踪参数*/@PostMapping("/config")public ResponseEntity<Map<String, Object>> updateConfig(@RequestBody Map<String, Object> config) {Map<String, Object> response = new HashMap<>();if (config.containsKey("slowSqlThreshold")) {long threshold = ((Number) config.get("slowSqlThreshold")).longValue();sqlCallTreeContext.setSlowSqlThreshold(threshold);response.put("slowSqlThreshold", threshold);}if (config.containsKey("traceEnabled")) {boolean enabled = (Boolean) config.get("traceEnabled");sqlCallTreeContext.setTraceEnabled(enabled);response.put("traceEnabled", enabled);}response.put("success", true);response.put("message", "Configuration updated successfully");return ResponseEntity.ok(response);}/*** 分析慢SQL*/@GetMapping("/analysis/slow-sql")public ResponseEntity<List<SqlNode>> getSlowSqlAnalysis() {Map<String, List<SqlNode>> sessions = sqlCallTreeContext.getAllSessions();List<SqlNode> slowSqlNodes = new ArrayList<>();for (List<SqlNode> sessionNodes : sessions.values()) {collectSlowSqlNodes(sessionNodes, slowSqlNodes);}// 按执行时间降序排序slowSqlNodes.sort((a, b) -> Long.compare(b.getExecutionTime(), a.getExecutionTime()));return ResponseEntity.ok(slowSqlNodes);}/*** 导出数据*/@GetMapping("/export")public ResponseEntity<Map<String, Object>> exportData() {Map<String, Object> exportData = new HashMap<>();exportData.put("sessions", sqlCallTreeContext.getAllSessions());exportData.put("statistics", sqlCallTreeContext.getStatistics());exportData.put("exportTime", LocalDateTime.now());exportData.put("version", "1.0");return ResponseEntity.ok(exportData);}/*** 系统状态检查*/@GetMapping("/health")public ResponseEntity<Map<String, Object>> healthCheck() {Map<String, Object> health = new HashMap<>();health.put("status", "UP");health.put("traceEnabled", sqlCallTreeContext.isTraceEnabled());health.put("slowSqlThreshold", sqlCallTreeContext.getSlowSqlThreshold());health.put("timestamp", LocalDateTime.now());return ResponseEntity.ok(health);}/*** 递归收集慢SQL节点*/private void collectSlowSqlNodes(List<SqlNode> nodes, List<SqlNode> slowSqlNodes) {for (SqlNode node : nodes) {if (node.isSlowSql()) {slowSqlNodes.add(node);}if (node.getChildren() != null && !node.getChildren().isEmpty()) {collectSlowSqlNodes(node.getChildren(), slowSqlNodes);}}}
}
5. 前端可视化实现

前端使用D3.js实现交互式的SQL调用树可视化,主要包含以下功能:

// sql-tree.js - 主要的可视化逻辑
class SqlTreeVisualizer {constructor() {this.width = 1200;this.height = 800;this.margin = { transform: translateY( 50, right: 150, bottom: 50, left: 150 };// 初始化SVG容器this.svg = d3.select('#tree-container').append('svg').attr('width', this.width).attr('height', this.height);this.g = this.svg.append('g').attr('transform', `translate(${this.margin.left},${this.margin.top})`);// 配置树布局this.tree = d3.tree().size([this.height - this.margin.top - this.margin.bottom, this.width - this.margin.left - this.margin.right]);// 初始化工具提示this.tooltip = d3.select('body').append('div').attr('class', 'tooltip').style('opacity', 0);}/*** 渲染SQL调用树*/render(sessions) {this.g.selectAll('*').rem)ove();if (!sessions || Object.keys(sessions).length === 0) {this.showEmptyState();return;}// 选择第一个会话进行展示const sessionKey = Object.keys(sessions)[0];const rootNodes = sessions[sessionKey];if (rootNodes && rootNodes.length > 0) {this.renderTree(rootNodes[0]);}}/*** 渲染单个调用树*/renderTree(rootNode) {// 构建D3层次结构const root = d3.hierarchy(rootNode, d => d.children);// 计算节点位置this.tree(root);// 绘制连接线const links = this.g.selectAll('.link').data(root.links()).enter().append('path').attr('class', 'link').attr('d', d3.linkHorizontal().x(d => d.y).y(d => d.x)).style('fill', 'none').style('stroke', '#94a3b8').style('stroke-width', '2px').style('stroke-opacity', 0.6);// 绘制节点组const nodes = this.g.selectAll('.node').data(root.descendants()).enter().append('g').attr('class', 'node').attr('transform', d => `translate(${d.y},${d.x})`);// 绘制节点圆圈nodes.append('circle').attr('r', 10).style('fill', d => this.getNodeColor(d.data)).style('stroke', '#1e293b').style('stroke-width', '2px').style('cursor', 'pointer');// 添加节点文本nodes.append('text').attr('dy', '.35em').attr('x', d => d.children ? -15 : 15).style('text-anchor', d => d.children ? 'end' : 'start').style('font-size', '12px').style('font-weight', '500').style('fill', '#1e293b').text(d =>this.getNodeLabel(d.data));// 添加交互事件nodes.on('mouseover', (event, d) => this.showTooltip(event, d.data)).on('mouseout', () => this.hideTooltip()).on('click', (event, d) => this.showNodeDetails(d.data));}/*** 获取节点颜色*/getNodeColor(data) {if (data.errorMessage) {return'#ef4444'; // 错误:红色}if (data.slowSql) {return'#f59e0b'; // 慢SQL:橙色}switch (data.sqlType) {case'SELECT':return'#10b981'; // 查询:绿色case'INSERT':return'#3b82f6'; // 插入:蓝色case'UPDATE':return'#8b5cf6'; // 更新:紫色case'DELETE':return'#ef4444'; // 删除:红色default:return'#6b7280'; // 默认:灰色}}/*** 获取节点标签*/getNodeLabel(data) {const time = data.executionTime || 0;return`${data.sqlType} (${time}ms)`;}/*** 显示工具提示*/showTooltip(event, data) {const tooltipContent = `<div class="font-semibold text-gray-900">${data.sqlType} 操作</div><div class="text-sm text-gray-600 mt-1"><div>执行时间: ${data.executionTime || 0}ms</div><div>影响行数: ${data.affectedRows || 0}</div><div>服务: ${data.serviceName || 'Unknown'}</div><div>方法: ${data.methodName || 'unknown'}</div>${data.errorMessage ? `<div class="text-red-600">错误: ${data.errorMessage}</div>` : ''}</div>`;this.tooltip.transition().duration(200).style('opacity', .9);this.tooltip.html(tooltipContent).style('left', (event.pageX + 10) + 'px').style('top', (event.pageY - 28) + 'px');}/*** 隐藏工具提示*/hideTooltip() {this.tooltip.transition().duration(500).style('opacity', 0);}/*** 显示空状态*/showEmptyState() {this.g.append('text').attr('x', (this.width - this.margin.left - this.margin.right) / 2).attr('y', (this.height - this.margin.top - this.margin.bottom) / 2).attr('text-anchor', 'middle').style('font-size', '18px').style('fill', '#6b7280').text('暂无SQL调用数据');}/*** 显示节点详情*/showNodeDetails(data) {// 在侧边栏显示详细信息const detailsPanel = document.getElementById('node-details');if (detailsPanel) {detailsPanel.innerHTML = `<h3 class="text-lg font-semibold mb-4">SQL详情</h3><div class="space-y-2"><div><span class="font-medium">类型:</span> ${data.sqlType}</div><div><span class="font-medium">执行时间:</span> ${data.executionTime || 0}ms</div><div><span class="font-medium">影响行数:</span> ${data.affectedRows || 0}</div><div><span class="font-medium">服务:</span> ${data.serviceName || 'Unknown'}</div><div><span class="font-medium">方法:</span> ${data.methodName || 'unknown'}</div><div><span class="font-medium">线程:</span> ${data.threadName || 'unknown'}</div>${data.sql ? `<div><span class="font-medium">SQL:</span><pre class="mt-1 p-2 bg-gray-100 rounded text-sm">${data.sql}</pre></div>` : ''}${data.parameters ? `<div><span class="font-medium">参数:</span><pre class="mt-1 p-2 bg-gray-100 rounded text-sm">${data.parameters}</pre></div>` : ''}${data.errorMessage ? `<div><span class="font-medium text-red-600">错误:</span><div class="mt-1 p-2 bg-red-50 rounded text-sm text-red-700">${data.errorMessage}</div></div>` : ''}</div>`;}}
}

核心特性

  • 🌳 树形布局:清晰展示SQL调用层次关系

  • 🎨 颜色编码:绿色(正常)、红色(慢SQL)

  • 🖱️ 交互操作:点击节点查看详情,悬停显示提示

  • 🔍 智能筛选:支持按执行时间、SQL类型等条件筛选

  • 📊 实时刷新:支持自动/手动刷新数据

快速开始

环境要求

Java 21+Maven 3.6+现代浏览器(支持ES6+)

访问系统

启动成功后,可以通过以下地址访问:

可视化界面:http://localhost:8080/index.htmlH2数据库控制台:http://localhost:8080/h2-console

JDBC URL: `jdbc:h2:mem:testdb`
用户名: `sa`
密码: (空)
项目配置

核心依赖(pom.xml)

<dependencies><!-- Spring Boot 3.4.5 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>3.4.5</version></dependency><!-- MyBatis 3.0.3 --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency><!-- H2 Database --><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>
</dependencies>

应用配置(application.yml)

server:port:8080spring:
application:name:springboot-sql-tree
datasource:url:jdbc:h2:mem:testdbdriver-class-name:org.h2.Driverusername:sapassword:schema:classpath:schema.sqldata:classpath:data.sql
h2:console:enabled:truepath:/h2-consolemybatis:
mapper-locations:classpath:mapper/*.xml
type-aliases-package:com.example.sqltree.entity
configuration:map-underscore-to-camel-case:truelazy-loading-enabled:truecache-enabled:truelog-impl:org.apache.ibatis.logging.slf4j.Slf4jImpl

实际应用场景

开发调试场景

场景1:复杂查询性能分析

当调用 /api/demo/user/1/detail 接口时,系统会自动捕获以下SQL调用链:

UserService.getUserDetailWithOrders()
├── SELECT * FROM users WHERE id = ? (2ms)
└── SELECT * FROM orders WHERE user_id = ? (15ms)└── SELECT * FROM order_items WHERE order_id IN (...) (45ms)

通过可视化界面可以清晰看到:

  • 总执行时间:62ms

  • SQL调用深度:2层

  • 性能瓶颈:order_items查询耗时最长

场景2:慢SQL识别

系统自动标识执行时间超过阈值(默认1000ms)的SQL:

{"nodeId": "uuid-123","sql": "SELECT * FROM orders o JOIN users u ON o.user_id = u.id WHERE o.status = ?","executionTime": 1250,"slowSql": true,"serviceName": "OrderService","methodName": "getOrdersWithUserInfo"
}
数据监控

统计信息示例

{"totalSqlCount": 1247,"slowSqlCount": 23,"errorSqlCount": 5,"averageExecutionTime": 35.6,"slowSqlThreshold": 1000,"traceEnabled": true
}

慢SQL分析报告

系统提供按执行时间排序的慢SQL列表:

[{"sql": "SELECT COUNT(*) FROM orders WHERE created_at BETWEEN ? AND ?","executionTime": 2150,"serviceName": "ReportService","methodName": "generateDailyReport","affectedRows": 1},{"sql": "UPDATE users SET last_login = ? WHERE id IN (...)","executionTime": 1890,"serviceName": "UserService","methodName": "batchUpdateLastLogin","affectedRows": 156}
]

总结

这个项目展示了如何结合Spring Boot生态和前端技术,构建一个实用的SQL监控工具,为日常开发和性能优化提供有力支持。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/920919.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/920919.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

部署 Go 项目的 N 种方法

Go 语言&#xff08;Golang&#xff09;以其简单、高效和易于部署的特点&#xff0c;成为了很多企业开发和部署服务的首选语言。无论是微服务架构&#xff0c;还是命令行工具&#xff0c;Go 的编译方式和标准库使得部署变得更加轻松。本文将介绍部署 Go 语言项目的几种常见方法…

【ARM】MDK工程切换高版本的编译器后出现error: A1167E\A1159E\A1137E\A1517E\A1150E报错

1、 文档目标解决工程从Compiler 5切换到Compiler 6进行编译时出现一些非语法问题上的报错。2、 问题场景对于一些使用Compiler 5进行编译的工程&#xff0c;要切换到Compiler 6进行编译的时候&#xff0c;原本无任何报错警告信息的工程在使用Compiler 6进行编译后出现了一些非…

AtCoder Beginner Contest 421

文章目录A MisdeliveryB Fibonacci ReversedC AlternatedD RLE MovingE YachtF Erase between X and YG Increase to make it IncreasingAtCoder Beginner Contest 421A Misdelivery Mansion AtCoder has N rooms numbered from room 1 to room N. Each room i is inhabited b…

数据结构:冒泡排序 (Bubble Sort)

目录 从最简单的操作开始 如何利用这个原子操作实现一个具体的小目标&#xff1f; 我们来手动模拟一下&#xff1a; 如何从一个小目标扩展到最终目标&#xff1f; 代码的逐步完善 第一阶段&#xff1a;定义函数框架和我们需要的“原子操作” 第二阶段&#xff1a;实现“…

教育项目管理工具新趋势:可视化与自动化如何提升效率?

课程项目不同于普通商业项目&#xff0c;它涉及 “教研设计→内容开发→师资准备→市场推广→学员服务” 全链路&#xff0c;环节多、角色杂、周期跨度大。传统的 Excel 表格、口头沟通不仅难以追踪进度&#xff0c;更易造成信息断层。而看板工具凭借 “可视化流程、轻量化协作…

计算两个二值图像的交集计算交点数量的基础上,进一步使用 DBSCAN 算法对交点进行聚

好的&#xff0c;如果你需要在计算交点数量的基础上&#xff0c;进一步使用 DBSCAN 算法对交点进行聚类&#xff0c;以合并距离较近的点&#xff0c;可以按照以下步骤实现&#xff1a; 计算交点&#xff1a;使用 cv2.bitwise_and 计算两个二值图像的交集&#xff0c;并提取交点…

Linux中的IP命令详解

华子目录 1.ip命令是什么1.1ip命令的由来1.2ip命令的安装包1.2ip选项&#xff08;基本不用&#xff09; 2.查看网络信息2.1显示全部网络接口信息2.2显示单个网络接口信息2.3显示单个接口状态2.4查看路由表2.5查看arp缓存 3.设置网卡ip地址3.1启用或停用网卡3.2设置默认网关3.3新…

如何解决pip安装报错ModuleNotFoundError: No module named ‘tox’问题

【Python系列Bug修复PyCharm控制台pip install报错】如何解决pip安装报错ModuleNotFoundError: No module named ‘tox’问题 摘要 在使用 PyCharm 2025 控制台执行 pip install 命令时&#xff0c;开发者经常会遇到如下错误&#xff1a; ModuleNotFoundError: No module nam…

拆分TypeScript项目的学习收获:处理编译缓存和包缓存,引用本地项目,使用相对路径

最近需要将工作中的一个TS包拆出一部分代码&#xff0c;以便在多个团队和项目中共享。原以为这会是一项特别简单的工作&#xff0c;但是也花了两天才大致拆成功。因此记录一下&#xff0c;也给有类似需求的同学一点经验。 所拆项目的大致功能&#xff1a;整个项目的结构大致分为…

瑞芯微RK3576平台FFmpeg硬件编解码移植及性能测试实战攻略

本文介绍瑞芯微RK3576平台&#xff0c;FFmpeg硬件编解码移植及性能测试方法。 FFmpeg简介与实测数据 FFmpeg简介 FFmpeg是一套多媒体框架&#xff0c;能够解码、编码、转码、复用、解复用、流、过滤和播放数字音频、视频&#xff0c;提供了录制、转换以及流化音视频的完整解…

【网络安全入门基础教程】网络安全零基础学习方向及需要掌握的技能

最近总有同学问我&#xff0c;0基础怎么学网络安全&#xff1f;0基础可以转行做网络安全吗&#xff1f;网络安全有哪些学习方向&#xff1f;每个方向需要掌握哪些技能&#xff1f;今天给大家简单写一下。 我的回答是先了解&#xff0c;再入行。 具体怎么做呢&#xff1f; 首…

Altium Designer中的Net-Tie:解决多网络合并与电气隔离的利器

Altium Designer中的Net-Tie:解决多网络合并与电气隔离的利器 在复杂的PCB设计中,我们常常会遇到一些特殊的电气连接需求。例如,需要将两个或多个逻辑上独立但物理上需要连接的网络(如不同电源域的GND)在特定点进行连接(单点连接),同时又要保持其网络标识的独立性。 …

计算机毕设项目 基于Python与机器学习的B站视频热度分析与预测系统 基于随机森林算法的B站视频内容热度预测系统

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Spark、hadoop、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题…

百胜软件×OceanBase深度合作,赋能品牌零售数字化实践降本增效

8月28日&#xff0c;由OceanBase主办的“2025零售数据底座创新大会”在上海举行。大会重磅发布了由爱分析、OceanBase携手王歆、沈刚两位行业专家联合编制的《零售一体化云数据库白皮书》。白皮书系统梳理了从“大促流量应对”到“AI应用落地”的全流程方法论&#xff0c;并为不…

2025年Java在中国开发语言排名分析报告

引言 在软件定义世界的2025年&#xff0c;编程语言的战略价值已超越工具属性&#xff0c;成为产业数字化转型的核心支撑与开发者思维模式的延伸载体。TIOBE指数作为全球技术市场变化的重要晴雨表&#xff0c;通过追踪工程师分布、课程设置、供应商动态及搜索引擎数据&#xff0…

TDengine 日期时间函数 DAYOFWEEK 使用手册

DAYOFWEEK 函数使用手册 函数描述 DAYOFWEEK 函数用于返回指定日期是一周中的第几天。该函数遵循标准的星期编号约定&#xff0c;返回值范围为 1-7&#xff0c;其中&#xff1a; 1 星期日 (Sunday)2 星期一 (Monday)3 星期二 (Tuesday)4 星期三 (Wednesday)5 星期四 (T…

从RNN到BERT

目录 序列模型简介RNN循环神经网络LSTM长短期记忆网络Transformer架构BERT模型详解实践项目 序列模型简介 什么是序列数据&#xff1f; 序列数据是按照特定顺序排列的数据&#xff0c;其中元素的顺序包含重要信息。常见的序列数据包括&#xff1a; 文本&#xff1a;单词或字…

椭圆曲线的数学基础

一、引言 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography, ECC&#xff09;是现代公钥密码学的核心工具之一。 相比传统的 RSA&#xff0c;ECC 可以用 更短的密钥长度 提供 同等甚至更高的安全性&#xff0c;因此被广泛应用于区块链、TLS、移动设备加密等场景。 要理解…

从能耗黑洞到精准智控:ASCB2智慧空开重构高校宿舍用电能效模型

随着智慧校园建设不断推进&#xff0c;校园宿舍的用电管理面临着安全性、智能化与可视化的多重挑战。传统用电监控手段在数据采集、实时控制和故障响应方面存在明显不足。安科瑞ASCB2系列物联网断路器通过集成多种智能感知、保护控制与通信手段&#xff0c;为高校宿舍提供了一种…

前端学习——JavaScript基础

前面我们已经学习了前端代码的骨架——HTML和前端美化工具——CSS。但是作为界面与客户进行交互我们还需要一个语言工具——JavaScript。 因此实际上HTML、CSS、JavaScript三者是这样的关系&#xff1a; HTML: 网页的结构(骨) CSS: 网页的表现(皮) JavaScript: 网页的行为(魂) …