IDEA中输出SQL的完整指南:从MyBatis日志到Druid监控
我经常遇到这样的咨询项目跑起来了接口也通了但控制台就是看不到SQL或者MyBatis把SQL和参数分两行打印想复制到Navicat里直接跑还得手动替换那一堆问号还有人用的是JPA开了show-sql却只看到一句Hibernate: select...参数值完全无迹可寻。这篇文章就把IDEA里输出SQL语句的几条路一次性理清楚从项目日志配置、IDEA自带数据库控制台到各种插件和第三方监控组件每条路的原理、配置和避坑点都会讲到。不管你用的是MyBatis还是JPA还是单纯想在IDEA里手写SQL做验证都可以按图索骥。1. SQL不打印或打印不全先摸清日志链路去了哪很多人第一反应是我代码里写了SQL控制台就该打印出来。这个想法一半对一半错。SQL能不能出现在控制台取决于框架有没有把SQL交给日志系统以及日志系统的级别允不允许它出现。排查之前先搞清楚这两件事。1.1 连SELECT都不打印八成是日志配置压根没生效最常见的场景是Spring Boot项目引入了mybatis-spring-boot-starter结果控制台一条SQL都没有。检查顺序通常是这样的先看有没有引入日志门面。现在绝大多数项目是Spring Boot默认的Logback SLF4J组合spring-boot-starter-web里已经带上了这些依赖所以没引入SLF4J这个原因在Spring Boot项目里反而不常见。更常见的问题是MyBatis不知道用哪个日志实现。MyBatis内部有一套日志适配逻辑它会按固定顺序去ClassPath里找日志框架SLF4J → Apache Commons Logging → Log4j2 → Log4j → JDK logging → 还有它自己的StdOutImpl。如果项目里存在多个日志框架或者MyBatis找到了一个日志框架但级别不对SQL就可能被吞掉。我见过一个比较典型的配置mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl用StdOutImpl的好处是它不走日志框架直接往标准输出里打在IDEA的控制台一定能看到。缺点是它的输出格式不受你的logback或log4j2配置约束生产环境不好控制而且日志级别也没法通过logging.level来动态调。所以我的建议是mybatis: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl配合下面的日志级别配置一起用这样SQL日志的控制权就完全交回给项目自己的日志体系了。1.2 只看到Preparing和Parameters看不到完整SQL很正常如果你已经能够看到类似这样的日志 Preparing: SELECT * FROM user WHERE age ? AND city ? Parameters: 18(Integer), 北京(String) Columns: id, name, age, city Row: 1, 张三, 19, 北京这说明日志链路已经通了。但很多人会问一个问题为什么MyBatis不直接打印一条完整的SQL非得拆成模板和参数两段因为MyBatis用的是JDBC的PreparedStatement预编译机制SQL模板先发给数据库做预编译参数再通过setInt、setString这些方法绑定进去。框架层面确实拿不到一条拼好的完整SQL它只能分别打印模板和参数。数据库那边把SQL模板和参数合起来执行但这个合起来的动作发生在数据库服务端MyBatis自己看不到。这个设计是刻意的——预编译能防SQL注入又能复用执行计划。所以别再纠结为什么MyBatis不帮你拼SQL了从代码安全角度来说拆开打印反而是对的。1.3 最容易被忽略的坑多数据源或ORM混用有些项目里既有MyBatis又有JPA或者配置了多个数据源这时候日志配置就要分开看。如果你用的是MyBatis日志是打在你的Mapper接口上的logging.level要配置到Mapper所在的包或具体接口比如logging: level: com.example.project.mapper: debug如果你只在application.yml里写了一个全局logging.level.root: info那Mapper包下的debug日志就不会被打印出来。如果你用的是JPA/Hibernate日志是打在Hibernate的类上的配置的key又不一样后面第4节会详细说。很多人以为配了一大堆就能全打出SQL结果MyBatis的SQL出来了JPA的没有或者反过来原因就在这里——两套框架的logger名称体系不同得分别配置。2. IDEA数据库控制台写SQL和验证SQL的自带考场如果目标只是手写一条SQL看看能不能跑通、结果对不对那IDEA自带的数据库工具可能比任何日志方案都直接。IDEA Ultimate和免费的IntelliJ IDEA Community版本里可以通过插件方式安装Database插件DataGrip的内嵌版连接数据库后在集成的SQL控制台里直接执行语句。2.1 连接数据库并打开SQL Console打开IDEA右侧的Database面板点加号选择你的数据源类型。这里有个小细节如果你本机装的是MySQL 8.x驱动版本建议选8.0以上的否则连接时会报时区错误或认证方式不兼容。连接串里的serverTimezone参数如果不确定可以直接在URL后面加?serverTimezoneUTCuseSSLfalse能少踩好几个坑。连接成功后右键数据源或某个表选择New → Query Console就打开了一个SQL控制台。这个控制台自带智能提示表名、字段名、函数都能补全写完SQL后按快捷键CtrlEnterWindows或CmdEnterMac就能执行。我平时用这个控制台做两件事一是验证新写的复杂SQL能不能跑通二是查看表结构。IDEA的控制台还可以在结果表格里直接编辑数据删除行、修改字段值都会生成对应的UPDATE或DELETE语句比开Navicat或DBeaver再拖一遍要顺手。2.2 把日志里拼好的SQL粘贴进来跑回到第1节的问题——日志里只有Preparing和Parameters怎么快速验证这条SQL我的习惯是先在IDEA控制台里建一个本地测试场景把SQL模板复制进去再把Parameters里的参数一个个替换到问号位置然后执行。听着有点原始但实际挺快。因为IDEA控制台的SQL编辑区可以直接格式化CtrlAltL粘贴过来的乱糟糟的SQL会被整理成可读性很好的格式再用查找替换把?换掉半分钟就能跑起来。如果你用的是MyBatis Log Plugin这类插件第3节会介绍它能直接生成完整可执行的SQL文本然后你选中那行SQL右键选择Execute in Database ConsoleIDEA还能自动把这条SQL灌进控制台执行。这两个工具链配合起来非常顺滑。2.3 用Explain看执行计划来辅助调优写SQL不只是能跑就行有时候还得看它跑得快不快。IDEA数据库控制台对执行计划的支持相当不错。选中SQL后右键选择Explain Plan或者直接按CtrlShiftEnter就能看到这条SQL的执行计划。执行计划里最重要的几个列是type连接类型、key实际使用的索引、rows预估扫描行数。我在实际排查慢SQL时看到typeALL全表扫描基本就知道要建索引了看到key字段为空就知道连索引都没用上。IDEA执行计划默认有图形化展示和表格两种视图把SQL拿到这里分析有时候比去数据库命令行工具里看更直观。3. MyBatis场景从一行Preparing到完整可执行语句大部分Spring Boot MyBatis项目的SQL输出问题都可以拆成两步来解决先把SQL打到控制台再把控制台日志转成能直接执行的完整SQL。下面按这个顺序说。3.1 配置日志实现并打开Debug级别有两种常见的配置方式先区分一下。第一种在application.yml里通过MyBatis配置项指定日志实现mybatis: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl第二种用Spring Boot标准的日志级别配置logging: level: com.example.project.mapper: debug这两个配置到底怎么互相作用实际上当你设置了log-impl为Slf4jImpl后MyBatis就会把SQL日志交给SLF4J日志名是com.example.project.mapper.UserMapper也就是Mapper接口的全限定名。这时候如果logging.level里把对应包的级别设成了debug日志就会显示出来。有些教程只写了第一种配置就完事了结果读者复制过去发现还是没有SQL就是因为漏了logging.level这一项。反过来如果你已经用Spring Boot的logging.level把Mapper包调成了debug但log-impl没设置MyBatis也会自动找到SLF4J并打印因为它的自动探测机制会生效。所以严格来说两处配置你可能只需要写一处——但为了保险也为了让后人看得懂我建议两处都写清楚。3.2 为什么MyBatis的SQL日志要打在Mapper接口上这一点值得单独说因为理解它之后你就不会再配错logging.level的路径了。MyBatis在启动时会扫描Mapper接口为每个接口创建动态代理对象。当你调用userMapper.selectById(1)时真正执行SQL的是MapperProxy。MyBatis打印日志时默认用的类名是绑定SQL语句的那个Mapper接口的全限定名而不是某个实现类。所以你在日志里看到的logger名称是com.example.project.mapper.UserMapper而不是UserMapperImplMyBatis压根就没有实现类。这里就引出一个新人经常犯的错误在logging.level里配了实现类的包名比如com.example.project.mapper.impl: debug结果什么都不打。真相是MyBatis日志的logger是Mapper接口本身你应该配接口所在的包名或接口全限定名。如果你嫌一个包太大、日志太多可以直接精确到某个接口logging: level: com.example.project.mapper.UserMapper: debug3.3 用MyBatis Log Plugin插件还原完整SQL日志能打出来了但Preparing和Parameters是分开的要复制给同事或者拿到生产环境排查还是不方便。这时候就轮到MyBatis Log Plugin这类插件登场了。在IDEA的插件市场搜索MyBatis Log Plugin安装后重启IDE然后启动项目触发SQL执行插件会单独开一个名为MyBatis Log的窗口面板自动把MyBatis打在控制台的SQL和参数拼接成一条可以直接执行的语句参数值会直接引号包裹或整型直出。复制出来就能跑非常省事。这个插件是免费且开源的实测下来对绝大多数MyBatis项目都有效。它的原理是监听IDEA控制台的输出流所以它能在不修改你项目代码的情况下工作——你甚至不需要特地为它调整日志级别只要能看到Preparing和Parameters它就能帮你拼好。注意事项有两点。第一由于它是靠解析控制台输出实现的如果你的项目里Preparing日志被其他日志刷掉了或者日志输出文件被日志框架二次改造过插件可能拼不完整。这时候可以临时把logging.level.com.example.project.mapper调成trace让日志更完整。第二如果你装了多个类似插件比如还有MyBatisX两个插件可能会同时监听面板里SQL重复显示是正常的不用纠结。3.4 没有插件时怎么手动拼出完整SQL如果公司电脑不让装插件或者你在服务器上排查问题就只能手动拼SQL了。这个技能在我看来反而比插件更重要——它逼着你理解MyBatis的#{}和${}差别。-- MyBatis XML 中写的 SELECT * FROM user WHERE age #{age} AND city #{city}这条SQL在Preparing阶段会被转成SELECT * FROM user WHERE age ? AND city ?#{}在这里的作用是生成占位符?对应的参数会在Parameters阶段依次列出来。手动拼的时候你只需要把?按顺序替换成Parameters里的值。但要注意字符串类型要加引号比如Parameters里显示北京(String)替换过来就是北京而18(Integer)不需要加引号。如果你看到Parameters里有2024-01-01 10:00:00(Timestamp)这种日期类型直接复制进SQL也是可以执行的但部分数据库可能需要你加一个DATE(2024-01-01 10:00:00)来保证类型正确。如果XML里写的是${city}那拼接规则完全不同。${}是字符串替换MyBatis在构建SQL阶段就把变量直接拼接进SQL模板日志里你会直接看到值没有参数阶段。这种情况下要特别注意SQL注入风险——${}只建议用在表名、排序字段这种没法用占位符的地方业务条件一律用#{}。手动拼完SQL之后一般我都会粘到第2节说的IDEA数据库控制台里跑一遍验证语法和执行结果。这样形成一个从日志到执行的完整闭环。4. JPA/Hibernate场景让Hibernate把话说完MyBatis的SQL日志虽然拆成两段至少信息是完整的。JPA/Hibernate这边情况更隐蔽你开了show-sql它打出了一行Hibernate: select ...但同样不打印参数。而且Hibernate生成的SQL通常还带了很多别名和嵌套子查询可读性比MyBatis差不少。4.1 show-sql开启后看到的是什么先看最基础的配置spring: jpa: show-sql: true这个配置的作用很简单开启后Hibernate会把生成的SQL打印出来默认走的是System.out不经过日志框架。所以有时候你会看到控制台里SQL是杂乱的白色输出完全不受logback的颜色和格式控制原因就是它走的是标准输出而不是logger。只配这一项你看到的日志大概是这样的Hibernate: select u.id as id1_0_, u.name as name2_0_, u.age as age3_0_ from user u where u.age?参数值藏在问号里SQL很长又没有格式化。它能告诉你大概执行了什么但没法帮你直接复制去数据库验证。补充一点很多人以为spring.jpa.show-sqltrue就够了实际上它只是Hibernate的一个开关而Hibernate通过System.out输出。如果你在日志配置文件里把标准输出禁用了那SQL可能直接消失。更稳妥的做法是用下一小节的方式让Hibernate的SQL也走日志框架。4.2 format-sql与use_sql_comments带来的可读性提升想让SQL可读性变好可以追加两个属性spring: jpa: show-sql: true properties: hibernate: format_sql: true use_sql_comments: trueformat_sqltrue会把SQL格式化成一个字段一行的样子嵌套子查询的缩进也清晰很多。use_sql_commentstrue则会在SQL前面加一段注释用来标明这条SQL是从哪个实体或哪个查询方法生成的。比如开启后你会看到Hibernate: /* select com.example.entity.User */ select u.id as id1_0_, u.name as name2_0_, u.age as age3_0_ from user u where u.age?虽然比原来好一点但参数还是看不到。生产环境排查问题的时候光知道SQL样子不知道参数值等于白看。4.3 把绑定参数一并打出来的关键配置Hibernate把参数绑定信息打在名叫org.hibernate.type.descriptor.sql.BasicBinder的logger上级别为TRACE。所以你要做的配置是logging: level: org.hibernate.SQL: debug org.hibernate.type.descriptor.sql.BasicBinder: trace这样设置之后日志会是这样的Hibernate: select u.id as id1_0_, u.name as name2_0_, u.age as age3_0_ from user u where u.age? binding parameter [1] as [INTEGER] - [18]看到binding parameter这行参数值就出来了。它和MyBatis的Parameters一样告诉你第几个?绑定了什么值。注意这里的logger路径和MyBatis的logger路径完全不同别把MyBatis的Mapper包和Hibernate的logger混在一个配置里。在一个同时使用MyBatis和JPA的项目里最终的日志配置通常长得像下面这样logging: level: com.example.project.mapper: debug org.hibernate.SQL: debug org.hibernate.type.descriptor.sql.BasicBinder: trace两个体系各打各的互不干扰。还有一个小技巧如果你用的是Hibernate 6.xSpring Boot 3.x默认就是BasicBinder的logger路径可能会因为包结构变化略有调整建议在启动日志里搜一下binding parameter关键字看看它实际是从哪个logger打出来的然后再对应配置。时代不同版本不同配置可能会微调。5. 数据源和监控层方案Druid监控页与p6spy日志在框架层解决的是这条SQL是什么的问题而数据源和监控层解决的是这个查询到底耗了多少时间、执行了多少次、有没有慢SQL的问题。到排查性能问题的时候这两类方案更好用。5.1 Druid自带SQL监控面板国内项目用Druid连接池非常普遍它自带一个SQL监控功能。如果你的数据源是Druid只需要开启StatFilter再配置一个监控页面就能通过浏览器看到所有SQL的执行统计。核心配置大致如下spring: datasource: druid: filter: stat: enabled: true slow-sql-millis: 1000 stat-view-servlet: enabled: true url-pattern: /druid/* login-username: admin login-password: 123456slow-sql-millis1000表示执行超过1秒的SQL会被标记为慢SQL。启动项目后访问http://localhost:8080/druid/输入账号密码就能看到SQL监控面板。里面会列出每条SQL的执行次数、总耗时、最大耗时、返回行数等信息还能按慢SQL筛选。我曾经靠它一次定位出一个线上接口每2秒卡一次的问题——把监控面板打开看到某一类SQL执行时间波动很大再点进去看执行计划和参数很快就锁定了缓存失效的问题。这个方案的优点是不用改业务代码引入Druid后配置即生效。缺点是它监控的是SQL存储在数据库层面的聚合统计对于某一条具体请求执行了哪些SQL这种问题帮助有限——它更多是给你一个全局视角看哪些SQL最需要优化。5.2 p6spy一个被低估的SQL打印利器p6spy是一个JDBC驱动级别的代理框架它在你真正的数据库驱动外面包了一层拦截所有经过JDBC的SQL语句包括MyBatis、JPA、Spring JDBC甚至手写JDBC然后统一打印出来。集成p6spy也很简单。引入依赖之后配置spy.propertiesappendercom.p6spy.engine.spy.appender.Slf4JLogger logMessageFormatcom.p6spy.engine.spy.appender.CustomLineFormat customLogMessageFormat%(executionTime)ms | %(sql)然后在application.yml里把JDBC URL改掉spring: datasource: url: jdbc:p6spy:mysql://localhost:3306/demo?serverTimezoneUTCuseSSLfalse driver-class-name: com.p6spy.engine.spy.P6SpyDriver启动项目后所有SQL会以12ms | SELECT * FROM user WHERE age 18这种格式打印时间、完整SQL一次到位参数和模板提前帮你拼好了。这对于想快速看到能直接复制的SQL这个需求来说体验比MyBatis Log Plugin还好因为它在驱动层就把活干完了跟ORM框架无关JPA、MyBatis都能覆盖。不过要提两个坑。第一p6spy包在JDBC驱动外层意味着你拿到的Connection被代理了连接池测试、服务降级等场景可能会受到影响生产环境需要充分测试后再决定开不开。第二p6spy会把所有的SQL都打出来包括某些框架内部生成的查询元数据的SQL日志量会比框架日志方案大不少所以线上环境建议只在调试窗口期短暂开启平时关闭。5.3 两种方案怎么选很多人会在Druid监控和p6spy之间犹豫我的选择标准很简单表格对比一下维度Druid监控面板p6spy能否看到完整SQL能看到具体的SQL文本能看到且带执行耗时参数是否拼接好不拼接直接拼接好统计SQL执行次数支持有聚合统计不支持聚合只能逐条看慢SQL分析支持可配置慢SQL阈值不支持直接标慢SQL要靠日志分析对生产的影响低已是常用连接池中代理JDBC有额外开销适合场景长期监控、慢SQL治理开发调试、联调排查、短时间定位问题如果你要长期观察数据库的压力用Druid监控面板如果你就是想在开发和联调阶段快速看清SQL、复制SQL、验证SQL用p6spy或者第3节说的MyBatis Log Plugin就够了。两个方案不是互相排斥的我就见过项目里开发环境开p6spy生产环境靠Druid监控面板兜底两边各司其职。还有一个折中的做法如果你不想动JDBC URL也可以直接用log4jdbc配合Spring Boot的DataSource代理。但log4jdbc在国内社区维护得不如p6spy活跃配置起来更绕所以除非你本身就在用log4jdbc否则新增项目我建议直接上p6spy更省心。写在最后现在回头看整个IDEA输出SQL的链条其实每一条路都有它明确的适用边界。我自己的习惯是开发环境装一个MyBatis Log Plugin遇到JPA项目就靠show-sql BasicBinder trace需要长期关注SQL性能的时候开Druid监控面板真要快速定位现场问题就直接用IDEA自带的数据库控制台跑一遍。这里面还有一个很容易被忽略的实际经验在公司里排查问题经常是拿到一个线上报错只有SQL文本却没有参数值。这时候如果你能用第3.4节的方法手动把参数拼回去再连到测试环境数据库跑一遍很多问题就能当场复现和验证。这个手工能力比任何插件都靠得住。希望这篇写下来能帮你省下那些在控制台和数据库客户端之间来回粘贴、反复替换参数的时间。