SpringBoot项目中mybatis执行sql很慢的排查改造过程(Interceptor插件、fetchSize、隐式转换等)

刚入职公司,就发现公司项目跑sql特别慢,差不多一万条数据插入到数据库要5秒以上(没有听错,就是这个速度),查询修改删除也是特别慢。直到22年年底实在是受不了了,我就去排查了一下。
用的是Oracle数据库,mybatismybatis plus,其中mybatis是引入的平台的依赖。平台封装了一些工具和插件。

做个对照试验

首先为了做对照试验,自己新建了一个SpringBoot项目T,里面引入了官方的mybatis。在自己项目中序列化一万条数据搞成文件,然后在T项目中反序列再插入到数据库。只花了100毫秒(一年了,具体我忘了,反正特别快),从5秒往上到现在这个速度,简直相差太大了。
然后又在T项目中引入了mybatis plus,使用mp的批量保存,速度也是100毫秒以内。接着我又把平台的mybatis依赖引入,重新插入数据,好家伙,单位成了秒。所以这下我就确定是平台的这个mybatis依赖有问题了。其实最开始百度了很多,用了很多种方法来写sql,但是效果都不是特别好.

mybatis Interceptor拦截器排查

排查自定义的mybatis Interceptor拦截器插件1

正好之前开通了平台代码的git权限,就去看了平台的代码,发现好多自定义的拦截器插件,还有一些其他的类,所以大概就是这些地方有问题了。
想到之前每次执行sql的时候,控制台都会输出一堆sql日志,还有执行时间,大概批量保存100条就会输出100条日志。后面我用arthas去看这个日志插件的执行速度,单条的话不算慢,但是量级一大,这个就很慢了。这个日志插件实现思路和【Mybatis】MybatisSqlInterceptor Interceptor 拦截器打印完整的sql语句这篇文章差不多,就是多了对不同厂商数据库连接池的处理。后面我关闭了这个插件,发现保存速度快了将近一秒。
正常的批量保存之所以快,是因为底层复用了PreparedStatement对象,一次批量保存多条数据,会先编译sql,后面都是发送占位符?的实际值就行了,但是调试发现用了平台的这个日志插件之后,会导致每次都生成一个新的PreparedStatement对象来执行,这相当于每插入一条数据都要编译一次sql。但是有些场景有需要用到sql日志(不是debug级别),所以我就想了其他办法来输出日志,具体参考这篇文章mybatis自定义日志实现,换成我这种的之后,测试发现对插入数据的速度影响不大。

排查自定义的mybatis Interceptor拦截器插件2

在移除掉平台的日志插件之后,速度快了一秒,但是还是很慢,然后接着排查。用了arthas的一些命令比如watchtrace等(安利一个插件arthas idea,真的好用),发现一个叫空集合处理的插件每次都会被调用,而且速度还不快。打开这个类发现里面是有两个处理,一是给没有是否删除条件的sql加上IS_DELETED,二是给有是否删除条件的sql检查值是否正确(这个是因为之前改过是否删除的枚举,为了兼容旧代码)。因为我们这边都是带了是否删除的标识,而且全部都是新的枚举,所以就跟平台协调能否去掉这个插件,最后是平台那边把他们那边的代码全部加上了这个标识,然后给这个插件加上了一个开关。拉新依赖之后,我这边直接关了,然后又关闭了一些其他不需要的插件(另外的插件其实没用调用,只是我们系统里面没有用到)。关闭这个空集合插件之后,重新保存,速度直接到了1秒多一点,相比之前快了很多。虽然说相比正常的还是很慢,但是没时间排查了。

查询很慢排查改造

fetchSize配置

保存的速度已经正常了,但是查询的速度还是异常的慢。但是因为项目比较忙,就搁置了。大概过了半年,正好安排优化报表项目,就顺带研究了一下查询为什么那么慢。
查询一万条数据,他要好几秒(数据量少很慢,数据量大更慢,想一秒查出来几乎不可能),但是我去DataGrip里面跑这条sql,几十毫秒,查询执行计划,发现这条sql是走了索引的。
后面去查了一下平台的mybatis默认配置,是没有配置fetchSize大小的,所以就很慢,
fetchSize的作用,文心一言

JDBC中的fetchSize是一个重要的配置参数,它决定了每次从数据库中检索并传输到客户端的记录数。以下是对fetchSize的详细解释和配置方法:
一、fetchSize的作用
内存管理:通过控制每次从数据库检索的记录数,fetchSize有助于减少客户端的内存占用,特别是在处理大量数据时。如果fetchSize设置得过大,可能会导致内存溢出;如果设置得过小,则可能增加网络往返次数,影响性能。
性能优化:合理设置fetchSize可以在内存使用和性能之间找到平衡点。对于需要快速响应的应用,较小的fetchSize可能更合适;而对于可以容忍一定延迟的应用,较大的fetchSize可能有助于减少网络开销。

后面我在mapper文件的select标签中里面加上了fetchSize="600",相较于之前速度果然变的特别快,但是和DataGrip里面执行的速度还是相差挺多的
在这里插入图片描述
添加fetchSize有三种方式

  1. xml中在select标签里面加fetchSize属性
  2. mybatis注解方式写查询sql的可以加上@Options注解,配置fetchSize属性
  3. mybatis plus可以通过mybatis-plus.configuration.default-fetch-size属性来配置默认的,这个针对于mp自有的方法
jdbcType是否配置测试

在小组的项目中测试了一些查询语句,发现了一个问题,配置了jdbcType的和没有配置jdbcType的sql查询速度相差很大(之前我只有部分用IDEA的mybatis插件生成的sql脚本加了这些,其他人代码都没加),加了jdbcType的sql执行速度只比Data Grip里面执行慢一点点,几乎可以忽略不记。

做对照试验

在T项目中,同样的查询sql(xml方式),使用的平台mybatis依赖,添加jdbcType和不加jdbcType效果等同于小组的项目。
然后去除了平台的mybatis依赖,再次运行,不加的速度和加的一样快了。到这里我已经有点麻木了。
但是起码发现加上了速度会变快,最后项目组的项目xml里面的sql全部加上了这个jdbcType,mp的也加上了,通过@TableFieldjdbcType属性指定了
mp的还可以通过@TableName注解的两个属性来配置这个,这样就不用在每个类属性上配置了,具体可以去官网看一下
在这里插入图片描述

平台代码导致的赋值方法调用错误

本以为加上了这个,CRUD语句的速度都能变正常,但是他总能给我惊喜,用自定义sql方式跑速度都很快,用mp的批量删除跑速度也很快,但用mp的selectXXX系列方法,速度竟然和最开始一样,慢的离谱,秒级以上。
到这我已经想不到还有哪里有问题了,于是乎开始不停的debugmybatis还有mybatis plus的源码,一层套一层,看的人都麻了。后面发现一个问题,自己在xml里面写的sql,里面参数a如果指定了jdbcType,那么在MybatisDefaultParameterHandlersetParameters方法里面是可以拿的到,他就会调用StringTypeHandler(设置了VARCHAR的情况下)来设置占位符的值,实际上就是调用PreparedStatement实现类的setString方法。
但是我如果用mp的查询方法(实体类中已经通过注解方式指定),他这里拿到的竟然是个null,接下来他就调用了一个CustomNStringTypeHandler类来设置占位符的值,数据库中的字段是VARCHAR2类型,这个类里面实际调用了setNString,而setNString对应的数据库类型应该是NVARCHAR。所以,就导致了隐式转换,他就不会走索引,隐式转换加上不走索引,执行语句就很慢,特别是查询和删除,数据量一大,不走索引执行SQL应该知道那速度得有多慢吧。mybatis plus查询方法jdbcType为什么null的下一篇再说

隐式转换是啥?文心一言:

数据库隐式转换,也称为自动转换,是指在执行SQL语句时,数据库管理系统(DBMS)自动进行的数据类型转换。这种转换通常发生在数据类型不匹配但需要进行比较、计算或赋值等操作时。不同数据库系统(如MySQL、Oracle等)中的隐式转换机制可能有所不同,但基本原理相似。以下是对数据库隐式转换的详细解析:
一、隐式转换的场景
字符串与数字的混合比较:
当字符串与数字进行比较时,数据库会尝试将字符串转换为数字(如果可能)。例如,在MySQL中,SELECT * FROM table WHERE column = ‘123’; 如果column是数字类型,MySQL会尝试将字符串’123’转换为数字123进行比较。
插入操作中的类型转换:
当尝试将字符串插入到数字类型的列时,数据库会尝试将字符串转换为相应的数字类型。反之亦然,如果尝试将数字插入到字符串类型的列中,数据库也会进行隐式转换。

数据库隐式转换是指在不显式指定数据类型转换的情况下,数据库系统根据上下文自动进行数据类型转换的过程。这种转换通常发生在SQL查询中,当操作符两边的数据类型不一致时,数据库会尝试将它们转换为一种共同的数据类型以便进行比较或计算。
然而,隐式转换有时会导致数据库查询不使用索引,从而影响查询性能。这主要是因为索引是基于特定数据类型的列创建的,当查询条件中的数据类型与索引列的数据类型不匹配时,数据库可能无法直接利用索引来加速查询。以下是几个导致隐式转换不走索引的常见原因:

在这里插入图片描述
接着就去看了一下这个CustomNStringTypeHandler类,这个类实现了BaseTypeHandler类。嗯~,又是平台的类,类上面的注解

@MappedJdbcTypes(value = {JdbcType.NVARCHAR},includeNullJdbcType = true
)
@Component

这个意思就是说,如果jdbcTypeNVARCHAR类型的,就会调用这个类来进行占位符赋值,重点是这个includeNullJdbcType = true,如果jdbcTypenull的时候,也会使用这个。这就能解释的通为什么xml里面没加jdbcType就会很慢。因为数据库里面是VARCHAR2类型,然后又因为没有配置jdbcType,所以导致调用了这个类的赋值方法。
后面就去找平台沟通说这个代码有问题,平台说当初加上个是为了解决部分中文乱码问题(VARCHAR2有些中文字符存放的话会乱码,要用NVARCHAR),后面平台是把includeNullJdbcType = true给去掉了,我当时是建议直接删除这个类,因为mybatis那边是自带的。
之所以前面会发生乱码其实是同样的原因,之前没有这个类,然后写的sql里面没有配置jdbcType,所以导致他字符串类型的值默认使用了PS实现类的setString方法赋值,而数据库中又是NVARCHAR类型。
后面项目中引入了平台新的mybatis依赖,CRUD的速度都正常了。

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

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

相关文章

OpenCV小练习:身份证号码识别

目标:针对一张身份证照片,把身份证号码识别出来(转成数字或字符串)。 实现思路:需要将目标拆分成两个子任务:(1) 把身份证号码区域从整张图片中检测/裁剪出来;(2) 将图片中的数字转化成文字。第…

Ubuntu 22.04上稳定安装与配置搜狗输入法详细教程

摘要:本教程详细介绍了如何在Ubuntu 22.04上安装和配置搜狗输入法,每个步骤详细配图。由于在Ubuntu 24.04上存在兼容性问题,建议用户继续使用稳定的22.04版本。教程涵盖了从更新系统源、安装fcitx输入法框架,到下载和配置搜狗输入…

存储实验:基于华为存储实现存储双活(HyperMetro特性)

目录 什么是存储双活仲裁机制 实验需求实验拓扑实验环境实验步骤1. 双活存储存储初始化(OceanStor v3 模拟器)1.1开机,设置密码1.2登录DM,修改设备名、系统时间和导入License1.3 设置接口IP 2. 仲裁服务器配置(Centos7…

全局点云配准的新思考:没有良好初值时如何配准?

更多优质内容,请关注公众号:智驾机器人技术前线 1.论文信息 论文标题:BiEquiFormer: Bi-Equivariant Representations for Global Point Cloud Registration 作者:Stefanos Pertigkiozoglou*, Evangelos Chatzipantazis∗ and K…

【循环顺序队的实现】

1.队列的逻辑结构 与 抽象数据类型定义 先进先出的线性表 在顺序队列中,我们使用头指针front指向队首元素;用尾指针rear指向队尾元素的下一个位置(当然这里的指针是用下标模拟出来的) 同时顺序队列中的元素当然是用数组来存储的 …

解决STM32使用J-Link可以擦除和读取但是无法烧录问题

现象 使用J-Link烧录模组固件,出现可以读取和擦除,但是无法烧录问题,提示错误如下: ERROR: Programming failed address 0x08000080 (program error)End of flash programmingERROR: Program failed 读出来的时候这个地址数据…

Linux 软件包管理器yum 自动化构建工具-make/makefile

Linux 工具 linux 软件包管理器 yum 把一些常用的软件提前编译好,做成软件包放在一个服务器上,通过包管理器可以很方便的获取到在这个编译好的软件包。直接进行安装。 软件包和软件包管理器就相当于 App 和应用商店这样的关系。 Linux 安装软件 源代码…

poe供电原理以及应用

1,根据IEEE802.3af标准,一个完整的PoE系统包括供电端设备PSE和受电端设备PD两部分; 供电设备PSE是整个系统的电源提供者,为PD设备提供直流电源,其可分为M

如何理解进程

一、进程的概念 进程:顾名思义,就是一个完整执行程序的过程。没错,就是这么简单,但是在程序执行的过程之中,系统会为这个执行的程序分配内存资源,这些过程也包含在进程当中。 进程是动态的,是程…

【网络编程通关之路】 Tcp 基础回显服务器(Java实现)及保姆式知识原理详解 ! ! !

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. 🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人…

Linux下IO多路复用—select,poll,epoll

一.概述 1.IO多路复用介绍 IO多路复用是一种操作系统的技术,用于在单个线程或进程中管理多个输入输出操作。它的主要目的是通过将多个IO操作合并到一个系统调用中来提高系统的性能和资源利用率,避免了传统的多线程或多进程模型中因为阻塞IO而导致的资源…

在Linux下搭建go环境

下载go go官网:All releases - The Go Programming Language 我们可以吧压缩包下载到Windows上再传到Linux上,也可以直接web下载: wget https://golang.google.cn/dl/go1.23.0.linux-amd64.tar.gz 解压 使用命令解压: tar -x…

解决有向图中节点出度和入度计算问题

解决有向图中节点出度和入度计算问题 引言邻接链表表示法邻接链表的数据结构创建图添加边计算节点的出度伪代码C代码计算节点的入度伪代码C代码时间复杂度示例结论引言 在图论中,有向图是一种重要的数据结构,用于表示元素之间的方向性关系。有向图中的节点(顶点)通过边连接…

VBA之正则表达式(47)-- 快速将公式转换为静态值计算

实例需求:工作表I列包含多种计算公式,为了便于演示,将I列公式显示在J列单元格中,现在需要将公式的单元格引用转换为静态值,如K列所示。 示例代码如下。 Sub RegExpDemoReplace()Dim Res()Dim objRegEx As ObjectDim o…

[解决]Invalid configuration `aarch64-openwrt-linux‘: machine `aarch64-openwrt

背景 交叉编译libev-4.19 问题 checking host system type… Invalid configuration aarch64-openwrt-linux: machine aarch64-openwrt’ not recognized 解决 打开config.sub,在244行后添加"| aarch64-openwrt \ "

Git学习(001 git介绍以及安装)

尚硅谷2024最新Git企业实战教程,全方位学习git与gitlab 总时长 5:42:00 共40P 此文章包含第1p-第p4的内容 文章目录 介绍Git介绍GitLab介绍 概述Git安装版本控制工具介绍 介绍 Git介绍 GitLab介绍 相当于中央仓库 概述 Git安装 进入官网(下载当前版本 2.43.0) …

解决 RT-Thread bsp stm32l476-st-nucleo STM32L4 HAL库缺失问题

问题描述 当前最新的 RT-Thread 版本:5.2.0,发现在 编译 BSP stm32l476-st-nucleo,缺少了 STM32L4xx_HAL 驱动库,造成生成的 工程,如 Keil MDK5 工程无法编译通过 初步的【临时】解决方法是 回退 RT-Thread 的版本&am…

rabbitmq发送的消息接收不到

1.消息被其他消费者消费 2.主要说的2这种情况,就是在延迟队列中,忘记给一个bean加注解导致日志报exchange not found. 这个报错,进而引发了bindings没有绑定。没有绑定的话,发送消息就会接收不到。

心脑血管科曹启富医生谈:引起高血压的原因

曹医生指出,高血压这一日益普遍的健康问题,其根源深藏于多重复杂因素之中。首要提及的便是年龄因素,它如同时间的刻度,悄然影响着我们的血管健康。随着年龄的增长,血管逐渐失去往昔的弹性与活力,变得僵硬而…

HTMl标签;知识回忆;笔记分享;

HTML标签是用于定义和组织网页内容的基础构建块。每个标签都有特定的作用。 一&#xff0c;标准结构标签&#xff1a; HTML文档标准结构&#xff1a; <html><head></head><body>this is my second html... </body> </html> 【1】htm…