java执行sql慢 navicat不慢 见鬼了

大家好,我是烤鸭:

   有点意思的问题,代码提示接口超时(10s+),接口逻辑很简单,就一个sql查询。本来也想是sql慢,可是拿sql去Navicat执行下,一点不慢(50ms)。

环境

DB:SqlServer

连接池:Druid

ORM:Mybatis

猜想

是刚好那个时段有其他操作造成的阻塞?

有特定参数造成的大量回表?

用了三方插件有bug?skywalking?pagehelper?

尝试复现

这个问题只在生产环境出现,即便是把生产数据备份到测试库,也不能复现。

可能生产库和测试库版本和配置不一样。

原sql

<if test="title != null and title!=''">AND vv.title  LIKE CONCAT(CONCAT('%', #{title}), '%')
</if>

同事有人说把参数写死试试,原来的sql类似这样。不慢了,什么原理。

<if test="title != null and title!=''">AND vv.title  LIKE CONCAT(CONCAT('%', 'abc'), '%')
</if>

再把#换成$,还是很快。大概就知道要看哪块的源码了。

<if test="title != null and title!=''">AND vv.title  LIKE CONCAT(CONCAT('%', '${title}'), '%')
</if>

关于mybatis的#和$,这篇文章写的挺好的,就不再重复了。

https://blog.csdn.net/weixin_43401380/article/details/122504003

源码分析

先说下sql执行的大致流程。

ORM框架(Mybatis做完动态sql之后) —> 连接池插件(Druid) —> JDBC —> DB(Sqlserver)。

下图是以查询方法为例。

在这里插入图片描述

接着我们看下源码:

SQLServerPreparedStatement.doPrepExec(执行RPC请求,构建请求sql,以及是否需要参数等)

final void doExecutePreparedStatement(PrepStmtExecCmd command) throws SQLServerException {resetForReexecute();// ...boolean hasExistingTypeDefinitions = preparedTypeDefinitions != null;boolean hasNewTypeDefinitions = true;if (!encryptionMetadataIsRetrieved) {// 动态sql变量初始化hasNewTypeDefinitions = buildPreparedStrings(inOutParam, false);}// ...String dbName = connection.getSCatalog();boolean needsPrepare = true;// Retry execution if existing handle could not be re-used.for (int attempt = 1; attempt <= 2; ++attempt) {try {// 构建 TDSWriter ,指令为TDS的RPCTDSWriter tdsWriter = command.startRequest(TDS.PKT_RPC);// PrepExec 执行needsPrepare = doPrepExec(tdsWriter, inOutParam, hasNewTypeDefinitions, hasExistingTypeDefinitions);// 结果监听ensureExecuteResultsReader(command.startResponse(getIsResponseBufferingAdaptive()));startResults();getNextResult();}catch (SQLException e) {// ...}break;}       
}

SQLServerPreparedStatement.buildPreparedStrings(

sql替换,把 ? 转换成 @p1,@p2 这种,比如 mybatis 的转换后是这样的。

AND vv.title  LIKE CONCAT(CONCAT('%', @p1), '%')

/*** Determines whether the statement needs to be reprepared based on a change in any of the type definitions of any of the parameters due to* changes in scale, length, etc., and, if so, sets the new type definition string.*/
private boolean buildPreparedStrings(Parameter[] params,boolean renewDefinition) throws SQLServerException {String newTypeDefinitions = buildParamTypeDefinitions(params, renewDefinition);if (null != preparedTypeDefinitions && newTypeDefinitions.equals(preparedTypeDefinitions))return false;   preparedTypeDefinitions = newTypeDefinitions;/* Replace the parameter marker '?' with the param numbers @p1, @p2 etc */preparedSQL = connection.replaceParameterMarkers(userSQL, params, bReturnValueSyntax);if (bRequestedGeneratedKeys)preparedSQL = preparedSQL + identityQuery;return true;
}

SQLServerPreparedStatement.doPrepExec(调用写SQL和写参数方法)

private boolean doPrepExec(TDSWriter tdsWriter,Parameter[] params,boolean hasNewTypeDefinitions,boolean hasExistingTypeDefinitions) throws SQLServerException {boolean needsPrepare = (hasNewTypeDefinitions && hasExistingTypeDefinitions) || !hasPreparedStatementHandle();// ...else {// Move overhead of needing to do prepare & unprepare to only use cases that need more than one execution.// First execution, use sp_executesql, optimizing for asumption we will not re-use statement.if (needsPrepare && !connection.getEnablePrepareOnFirstPreparedStatementCall() && !isExecutedAtLeastOnce) {// 第一次执行的时候buildExecSQLParams(tdsWriter);isExecutedAtLeastOnce = true;}// Second execution, use prepared statements since we seem to be re-using it.else if(needsPrepare)// 写入sqlbuildPrepExecParams(tdsWriter);elsebuildExecParams(tdsWriter);}// 写入参数sendParamsByRPC(tdsWriter, params);return needsPrepare;
}

SQLServerPreparedStatement.buildPrepExecParams(通过TCP(TDS协议),写入SQL和参数定义)

private void buildPrepExecParams(TDSWriter tdsWriter) throws SQLServerException {if (getStatementLogger().isLoggable(java.util.logging.Level.FINE))getStatementLogger().fine(toString() + ": calling sp_prepexec: PreparedHandle:" + getPreparedStatementHandle() + ", SQL:" + preparedSQL);// ...tdsWriter.writeShort((short) 0xFFFF); // procedure name length -> use ProcIDs// 执行方法是 SP_PREPEXECtdsWriter.writeShort(TDS.PROCID_SP_PREPEXEC);tdsWriter.writeByte((byte) 0);  // RPC procedure option 1tdsWriter.writeByte((byte) 0);  // RPC procedure option 2// <prepared handle>// IN (reprepare): Old handle to unprepare before repreparing// OUT: The newly prepared handletdsWriter.writeRPCInt(null, getPreparedStatementHandle(), true);resetPrepStmtHandle();// <formal parameter defn> IN,写入参数定义,比如上面的title就是 @P0 NVARCHAR(4000)tdsWriter.writeRPCStringUnicode((preparedTypeDefinitions.length() > 0) ? preparedTypeDefinitions : null);// <stmt> IN,写入SQLtdsWriter.writeRPCStringUnicode(preparedSQL);
}

在这里插入图片描述

在这里插入图片描述

DTVExecuteOp.execute(执行实际参数的赋值)

void execute(DTV dtv,String strValue) throws SQLServerException {if (null != strValue && strValue.length() > DataTypes.SHORT_VARTYPE_MAX_CHARS)dtv.setJdbcType(JDBCType.LONGNVARCHAR);// 实际的参数setTypeDefinition(dtv);
}

在这里插入图片描述

原因猜想

分析了一大顿源码,并没有找到问题所在,同样的SQL在程序执行隔一天就不慢了,不清楚是不是还有别的什么因素影响执行计划。

  1. 连接规范不同:像数据库client的实现规范有JDBC(针对java语言的)或者ODBC,我们Java服务程序用的JDBC,像navicat这些软件用的是ODBC。导致同样的SQL执行计划不同。
  2. SqlServer-Client 版本问题,不同版本的client也有不同的优化。
  3. sp_prepare | sp_execute 带参数执行 和 sp_executesql,生成的执行计划不同。

没找到答案,问题竟然自己消失了,先蹲个点,如果再出现就再研究一下。

总结

当出现这个问题的时候,不要迷,先想想这个题干成立么。

navicat 执行很快,java程序很慢,两边执行的是同一个sql么?
原本在navicat 执行的是(不慢)

SELECT TOP 15 id FROM test where tile like '%测试%'

实际执行的应该是这个 sp_prepare 和 sp_execute的语句(待验证,由于问题消失,没法验证了)

declare @N int
exec sp_prepare @n output,N'@p1 NVARCHAR (4000)',N'SELECT TOP 15 id FROM test where tile like ''%'' + @p1 +''%'' '
exec sp_execute @n,'aaa' --@n就是sp_prepare返回的句柄,使用sp_execute来通过这个句柄来传递参数

如果执行这个 navicat 也变慢了,那就可以推论是 sp_prepare 这种方式导致的。

看了源码还是很难定位问题,越难的问题,解决起来越有意思,等再出现记录一下。

相关文章

JDBC和ODBC区别:
https://www.php.cn/mysql-tutorials-414951.html

DTS协议分析:
https://www.docin.com/p-98157348.html

Sp_prepare | sp_execute 介绍:

https://www.cnblogs.com/gered/p/14648626.html

EXEC和sp_executesql使用介绍:

https://blog.csdn.net/neweastsun/article/details/40019439

SQLServer执行动态SQL:

https://www.gxlcms.com/mssql-350221.html

SQLServer执行计划:

https://www.jianshu.com/p/172a345fee95

常见问题

必须声明标量变量:

https://blog.csdn.net/dxnn520/article/details/17304573

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

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

相关文章

[vue] vue边界情况有哪些?

[vue] vue边界情况有哪些&#xff1f; 访问根实例、访问父组件、子组件个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端面试题

函数的基础

函数的初识&#xff1a; 封装一个功能。 def 函数名(): 函数体 函数的返回值&#xff1a;return 1,结束函数。 2&#xff0c;返回给执行者&#xff08;函数名()&#xff09;值。 return ----> None return 单个值----> 单个值 return 多个值----> &#xff08;多…

windows docker mongodb

大家好&#xff0c;我是烤鸭&#xff1a; 今天翻博客&#xff0c;发现4年前的一篇草稿&#xff0c;抽空给完善下。原本草稿写的是linux下mongo使用&#xff0c;还有java的一些api&#xff0c;现在就用容器实现下。 容器部署 官方网站&#xff1a; https://www.mongodb.com/ w…

[vue] 如何在子组件中访问父组件的实例?

[vue] 如何在子组件中访问父组件的实例&#xff1f; this.$parent拿到父组件实例 this.$children拿到子组件实例&#xff08;数组&#xff09;个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录…

[vue] watch的属性用箭头函数定义结果会怎么样?

[vue] watch的属性用箭头函数定义结果会怎么样&#xff1f; 因为箭头函数默绑定父级作用域的上下文&#xff0c;所以不会绑定vue实例&#xff0c;所以 this 是undefind个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢…

tensorflow的keras 与 原生keras几点比较

tensorflow的keras 与 原声keras几点比较&#xff0c;不是全面的比较&#xff0c;因为只是就使用时候发现的差异&#xff01; 使用函数式API时&#xff1a; 1. 定义模型模型时&#xff0c;用到输入的张量&#xff0c;也就是给Input的tensor赋值为你的inputs&#xff0c;在编译时…

[vue] 在vue项目中如何配置favicon?

[vue] 在vue项目中如何配置favicon&#xff1f; 也可以在当前项目部署的端口主目录下存放favicon.ico文件&#xff0c;默认就会显示该图标个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与…

Is the byte array a result of corresponding serialization for DefaultDeserializer

大家好&#xff0c;我是烤鸭&#xff1a; 问题记录&#xff0c;上线之后懵逼的问题。只能回滚?每次都是上线来暴击&#xff0c;不然多查查文章也不至于这么被动。 报错日志 org.springframework.data.redis.serializer.SerializationException: Cannot deserialize; nes…

[vue] 你有使用过babel-polyfill模块吗?主要是用来做什么的?

[vue] 你有使用过babel-polyfill模块吗&#xff1f;主要是用来做什么的&#xff1f; Babel默认只转换新的JavaScript句法&#xff08;syntax&#xff09;&#xff0c;而不转换新的API&#xff0c;比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对…

高级PHP工程师所应该具备的专业素养

高级PHP工程师所应该具备的专业素养 初次接触PHP&#xff0c;就为他的美所折服&#xff0c;于是一发不可收拾。 很多面试&#xff0c;很多人员能力要求都有“PHP高级工程师的字眼”&#xff0c;如果您真心喜欢PHP&#xff0c;并且您刚起步&#xff0c;那么我简单说说一个PHP高级…

容器环境 springcloud gateway grafana prometheus采集集成与问题

容器环境 springcloud gateway grafana prometheus采集集成与问题 大家好&#xff0c;我是烤鸭&#xff1a; 记录下网关上容器后&#xff0c;监控升级的过程。 原来的方式 grafana 和 prometheus 网上教程很多&#xff0c;就不细写了。 没上容器之前&#xff0c;可以在…

elasticsearch 问题

elasticsearch 的端口默认绑定到 127.0.0.1 上&#xff0c;对外开放 http 端口就配置 http.host&#xff0c;对外开放 tcp 端口就配置 network.host [1]: max file descriptors [65535] for elasticsearch process is too low, increase to at least [65536]编辑 /etc/security…

[vue] 说说你对vue的错误处理的了解?

[vue] 说说你对vue的错误处理的了解&#xff1f; 分为errorCaptured与errorHandler。 errorCaptured是组件内部钩子&#xff0c;可捕捉本组件与子孙组件抛出的错误&#xff0c;接收error、vm、info三个参数&#xff0c;return false后可以阻止错误继续向上抛出。 errorHandler…

easyui Combotree 怎么加载数据 支持多选

1、开发环境vs2012 mvc4 c# 2、HTML前端代码 <% Page Language"C#" AutoEventWireup"true" CodeBehind"DataGridTest.aspx.cs" Inherits"MvcAppTest.DataGridTest" %><!DOCTYPE html><html xmlns"http://www.w3.…

[vue] 在vue事件中传入$event,使用e.target和e.currentTarget有什么区别?

[vue] 在vue事件中传入$event&#xff0c;使用e.target和e.currentTarget有什么区别&#xff1f; event.currentTarget指向事件所绑定的元素&#xff0c;而event.target始终指向事件发生时的元素。个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&am…

idea 错误: 找不到或无法加载主类(汇总贴)

大家好&#xff0c;我是烤鸭&#xff1a; 现在是采坑实录。 idea 错误: 找不到或无法加载主类 xxx.xxx.xxxxx JDK环境&#xff0c;maven项目还是ee还是web项目&#xff0c;是否都正常。 如果是用idea打开的话,在源码目录上点击右键,然后找到Mark directory as->source ro…

C语言——生命游戏(初始

#include<stdio.h> #include<stdlib.h> #include<conio.h> #include<windows.h> #include<time.h>#define High 25 #define Width 50 //游戏画面的尺寸int cells[High][Width]; //所有位置细胞生为1&#xff0c;死亡为0void gotoxy(…

[vue] 在.vue文件中style是必须的吗?那script是必须的吗?为什么?

[vue] 在.vue文件中style是必须的吗&#xff1f;那script是必须的吗&#xff1f;为什么&#xff1f; style 不是必须的&#xff0c;script 是必须的&#xff0c;而且必须要写上个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定…

爬虫,关于 video 标签 src 带有blob:http的 一些想法

大家好&#xff0c;我是烤鸭&#xff1a; 之前玩爬虫的时候&#xff0c;看到过video标签中src属性引入的blob:http:xxxx&#xff0c;当时没找到解决思路&#xff0c;今天又遇到类似问题&#xff0c;就试着找了一下。 这是有人问过 https://vimeo.com/ 这个网站的视频怎么下载。…

4Python切片功能剖析

引用文章&#xff1a;https://mp.weixin.qq.com/s/NZ371nKs_WXdYPCPiryocw 切片基础法则&#xff1a; &#xff08;1&#xff09;公式[i : n : m]&#xff0c;i为起始位置索引(当i为首位0可省略)&#xff0c;in为结束位置索引(当n为长度len(li)可省略)&#xff0c;m为步长&…