首页 > 其他分享 >Mybatis-Wrapper导致的生产事故

Mybatis-Wrapper导致的生产事故

时间:2024-01-02 13:32:29浏览次数:43  
标签:事故 CPU Wrapper 查询 MyBatis GC Mybatis order id

【Java核心基础】一文带你了解Java中构造方法的重要作用! - 程序员古德

近期遭遇了一次生产环境的严重告警,涉及慢接口和CPU过载。经过排查,发现问题根源在于一段使用MyBatis的查询代码。当传入空列表作为查询条件时,MyBatis会忽略该条件,导致全表扫描,进而引发系统资源耗尽和频繁的Full GC

灾难回顾

【事故总结】Mybatis-Wrapper导致的生产事故 - 程序员古德

前两天晚上,正在收拾包准备下班,电脑刚放进包里,我的手机就开始不停地震动,打开一看,是生产环境告警群发来的信息,一段是慢接口告警,另一段是CPU告警。在这种即将回家的时候看到告警信息,是每一个技术人员最不想发生的事情,但我依然从包里取出电脑,准备处理这一突发情况。

由于最近并没有上线新功能,我的第一反应是怀疑中间件或数据库出了问题,毕竟,经验告诉我,很多时候性能问题都是由这些基础设施引起的,但经过和运维同事的一轮快速排查,各中间件表现正常,数据库中的慢SQL也并未达到触发告警的阈值。

看来问题比我想象的要复杂,我坐在运维同事旁边,让他登录主机上执行top命令,看看究竟怎么回事,从TOP可以看出,CPU占用情况异常地显示在主机CPU一直保持在100%(主机16核),没有任何下降的趋势,这意味着,某个进程或者线程正在疯狂地消耗CPU资源。

我突然想起之前遇到过的类似情况,那次是因为JVM的Full GC过于频繁导致的,有了上次的经验,我就让运维执行jstat -gcutil pid 1000命令查看GC情况,果不其然,FGC(Full GC)次数每几秒就增加1次,这频率高得异常,JVM显然在不停地进行垃圾回收,但为什么会这么频繁呢?

在这种情况下肯定是要先保住业务系统的运行,我的第一反应是尽快减轻服务器的压力,于是,我向领导申请决定重启部分机器,同时留下一台机器进行内存快照(dump)的生成,以便后续分析。10分钟后,内存快照分析完成。分析结果让我颇为震惊。有个叫OrderAddress的类占据了异常大的内存空间,包含对象数高达150多万。这明显不正常,很可能是它导致了频繁的Full GC。

为了找到问题的根源,我使用jstack命令(jstack -l pid)查看了JVM的线程堆栈信息。通过搜索关键词OrderAddress,我发现了很多与之相关的堆栈信息。这些信息就像是破案的关键线索,指引我逐步接近问题的真相。最终,基于这些堆栈信息,我找到了对应的代码位置。

场景回顾

【事故总结】Mybatis-Wrapper导致的生产事故 - 程序员古德

在我们日常的开发工作中,经常会遇到与数据库交互的情况。假设我们有这样一张数据库表jy_order_address,它记录了订单的收货地址信息:

create table `jy_order_address` (
  `id` int(11) unsigned not null auto_increment comment 'id',
  `order_id` int(11) not null comment 'jy_order.id',
  ...
  ...
  ...
) comment='订单收货地址';

为了满足某一业务需求,我们需要根据一批order_id来查询对应的收货地址,在Java中,使用MyBatis作为ORM框架,我们可能会写出如下的代码:

public List<OrderAddress> selectByOrderIdList(List<Integer> orderIdList) {
  Wrapper<OrderAddress> wrapper = new EntityWrapper<>();
  wrapper.in("order_id", orderIdList);
  List<OrderAddress> orderAddressList = orderAddressMapper.selectList(wrapper);
  return orderAddressList;
}

这段代码逻辑很清晰,通过MyBatis的Wrapper来组装SQL查询条件,实现在order_id在指定列表中的筛选。

正常情况下,如果orderIdList数据量不大,即使jy_order_address表中有大量数据,上述查询也是非常高效的,对应的SQL如下:

select * from jy_order_address where order_id in(?,?,?,...);

然而,当传入一个空列表orderIdList = []时,系统CPU占用率飙升,经过排查,发现竟然进行了全表扫描!

深入了解MyBatis的WrapperAPI后,我们发现了一个容易被忽视的细节,当查询条件的value为null或者空列表时,MyBatis会忽略该条件,这意味着上述查询代码在实际执行时变成了无条件的全表查询:

select * from jy_order_address;

对于一个包含150多万条数据的表来说,全表扫描无疑是一个巨大的负担,它会消耗大量的内存和CPU资源,进而导致JVM频繁进行Full GC(全局垃圾回收)。

预防措施

【事故总结】Mybatis-Wrapper导致的生产事故 - 程序员古德

严格的参数校验,在使用Wrapper进行查询之前,应对每个参数进行非空校验,这确保了查询的准确性和系统的健壮性,防止因空值或无效值导致的不可预测的行为。

**审慎使用Wrapper,尽管Wrapper提供了方便的查询条件组装,但在某些情况下,它可能导致不期望的查询行为。**为了避免这种情况,推荐直接编写SQL语句,特别是在处理关键查询时。例如,使用order_id in()这样的条件时应当特别小心,因为这可能导致SQL语法错误,一个更安全的方法是添加一个始终为假的条件(如1<>1),这样即使其他条件出现问题,SQL仍然可以正确执行,只是返回0条数据。

限制查询结果,为了防止大量数据一次性加载到内存中导致系统崩溃,建议使用MyBatis拦截器为所有查询统一设置结果条数上限,例如,每次查询最多返回1000条记录,当达到这个限制时,系统可以发出警告,并提示开发者进行分页查询以获取更多数据,这样不仅能提高系统的稳定性,还能提升用户体验,使大数据量的处理更加流畅。

关注我,每天学习互联网编程技术 - 程序员古德

标签:事故,CPU,Wrapper,查询,MyBatis,GC,Mybatis,order,id
From: https://blog.51cto.com/bytegood/9068550

相关文章

  • 24届春招实习必备技能(一)之MyBatis Plus入门实践详解
    一、什么是MyBatisPlus?MyBatisPlus简称MP,是mybatis的增强工具,旨在增强,不做改变。MyBatisPlus内置了内置通用Mapper、通用Service,仅仅通过少量配置即可实现单表大部分CRUD操作,更有强大的条件构造器,满足各类使用需求。官网地址:https://mp.baomidou.com/主要特性无侵入:只做增......
  • SpringBoot接入Mybatis-Flex
    相信不少的伙伴在日常开发中应该都用过MyBatis增强框架吧,目前来说国内用的比较多的无非就是MyBatis-Plus,那么今天我再给大家介绍一款新的MyBatis增强框架,它就是MyBatis-Flex。那么这个框架到底怎么样呢,跟MyBatis-Plus有什么不一样的呢,下面我们先来看下它的介绍,这是官网的一段介......
  • IDEA 中 SpringBoot2 整合 Mybatis 实例实例
    记录在IDEA中 使用SpringBoot2整合Mybatis的实例,环境:Java8+Maven+MySQL8。1. 添加依赖 添加MyBatis依赖,MySQL连接依赖,,数据库用的MySQL8。<!--MyBatis依赖--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-s......
  • Spring Boot学习随笔- 集成MyBatis-Plus,第一个MP程序(环境搭建、@TableName、@TableId
    学习视频:【编程不良人】Mybatis-Plus整合SpringBoot实战教程,提高的你开发效率,后端人员必备!引言MyBatis-Plus是一个基于MyBatis的增强工具,旨在简化开发,提高效率。它扩展了MyBatis的功能,提供了许多实用的特性,包括强大的CRUD操作、条件构造器、分页插件、代码生成器等。MyBati......
  • 两个 mybatis insert方法返回自增主键值的方法
    1、使用useGeneratedKeys<insertid="saveReturnId"useGeneratedKeys="true"keyProperty="id"2、使用selectkey,<insertid="saveReturnId"parameterType=""><selectKeyresultType="java.lang.......
  • 自己写的mapper.xml如何使用mybatis-plus的自带分页?
    在MyBatis-Plus中,使用自带的分页功能非常简单。首先,确保你的mapper.xml文件中定义了需要的SQL语句,并在相应的mapper接口中使用IPage类型的参数进行分页。接下来,使用Page类来包装查询条件,并调用Mapper接口的分页方法。首先,假设你的mapper.xml中有类似如下的查询语句:<!--在mapper.xm......
  • Mybatis-Plus 常用注解总结
    在框架的使用中,注解约定大于配置,我们可以轻松通过注解完成很多工作,比如字段改名映射,插入更新的时间写入等,下面的学习内容主要列举了常用的注解。我们看看官网中列出的注解有哪些[1]:本文的注解学习主要内容集中在以下的注解中:@TableName@TableId@TableField@EnumValue@Ver......
  • Java Spring Boot Mybatis-Plus 的简单使用
    此文主要基于官网case整理,如需了解更多详情,请移步官网。环境:SpringBoot:3.1.6JDK:17MySQL:5.7数据准备主要是MySQL建库建表,插入一些数据。建库:CREATEDATABASEmybatis_demo;建表:DROPTABLEIFEXISTS`user`;CREATETABLE`user`(idBIGINTNOTNULLCOMME......
  • 【SpringBoot快速入门】(3)SpringBoot整合junit和MyBatis 详细代码示例与讲解
    目录1.SpringBoot整合junit1.1环境准备1.2编写测试类2.SpringBoot整合mybatis2.1回顾Spring整合Mybatis2.2SpringBoot整合mybatis2.2.1创建模块2.2.2定义实体类2.2.3定义dao接口2.2.4定义测试类2.2.5编写配置2.2.6测试2.2.7使用Druid数据源之前我们已经学习的Spring、......
  • 【Spring教程31】SSM框架整合实战:从零开始学习SSM整合配置,如何编写Mybatis SpringMVC
    目录1流程分析2整合配置2.1步骤1:创建Maven的web项目2.2步骤2:添加依赖2.3步骤3:创建项目包结构2.4步骤4:创建SpringConfig配置类2.5步骤5:创建JdbcConfig配置类2.6步骤6:创建MybatisConfig配置类2.7步骤7:创建jdbc.properties2.8步骤8:创建SpringMVC配置类2.9步骤9:创......