暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

夜间数据库IO负载飙升?MySQL批量删除操作引发的问题排查

DBdoctor 2024-09-20
121

问题现象

近日,某用户反馈他们的MySQL数据库实例在凌晨时段会频繁出现IO负载急剧上升的情况,这种状态会持续一段时间,随后自行恢复正常。为了查明原因,该用户通过DBdoctor工具收集了相关的监控数据和审计日志进行分析。以下是他们收集到的关键数据:AAS监控数据和审计日志分析结果。



问题分析

通过审计日分析我们可以看到,在IO突增时间的时间段内, delete from order_info where id=? 这个SQL被执行超过了100万+次,这种短时间内的大量删除操作可能会对MySQL的IO性能造成影响。用户将分析数据提供给开发同学,开发团队通过追踪这个SQL语句,发现了一个新近添加的定时任务,该任务设置在每天凌晨运行,目的是清除历史数据。该代码如下:

    public void cleanExpireData(long startTime, long endTime) {
    List<OrderInfo> orderList = orderInfoDao.findByTime(startTime, endTime);
    if (CollectionUtils.isEmpty(orderList)) {
    log.info("no data found between {} and {}", startTime, endTime);
    return;
    }
    for (OrderInfo orderInfo : orderList) {
    orderInfoDao.deleteById(orderInfo.getId());
    }
    log.info("clean all data success");
    }
    通过分析这个 cleanExpireData
    的目的是清理在指定时间范围内的过期数据。它通过以下步骤实现:
    1. 调用orderInfoDao.findByTime(startTime,endTime)
      获取在 startTime
      endTime
      之间的所有数据的列表。

    2. 如果找到数据,则遍历每个对象ID
      ,并调用orderInfoDao.dele
      teById(id)
      方法逐个删除。
    那原因找到了,这是因为该定时任务在批量删除数据时先进行了查询操作,再进行一条一条数据删除而导致的数据库IO异常,同时该逻辑还可能会产生OOM,具体分析如下:

    1.潜在OOM风险:当查询时间范围内若数据量异常过大,一次查询出来,可能会占用大量内存,造成程序OOM。

    2.逐个删除记录效率低下

    • 每次删除一条记录时,都会发起一次单独的数据库操作(deleteById
      )。如果 allIds
      列表中有很多条数据,逐条删除会导致大量的数据库请求,从而导致性能降低。

    3.频繁大量进行MySQL删除操作会增加系统IO

    • 每次删除操作都会导致 MySQL 更新数据文件和索引文件,以标记删除的记录。这会触发大量的磁盘写入操作,特别是如果表中有大量索引需要更新。

    • 对于 InnoDB 引擎,删除操作还会产生大量的 Undo LogRedo Log 写操作,用于维护事务一致性和崩溃恢复。

    • 如果 MySQL 开启了 二进制日志(binlog),每个删除操作都会记录到 binlog 中,以便进行事务恢复和主从复制。删除大量数据会导致 MySQL 频繁地将这些操作写入到 binlog 中,增加磁盘的 I/O 压力

    • 删除大量数据后,表会产生空间碎片,尤其是在 InnoDB 引擎中,删除操作并不会立即释放磁盘空间,数据页中的记录会被标记为“已删除”,这可能导致表空间利用率降低。后续的操作(如INSERT
      UPDATE
      )会导致表空间整理,进一步增加 I/O 负载

    修改建议
    1. 避免大数据查询:简单的删除操作,可根据过滤条件直接从数据库中删除,无需查询出来再删除。

    2. 批量删除:根据指定时间范围内直接删除,同时限制批量删除个数,防止数据量过大,对数据库造成负载。


    修改后代码如下:

      public void cleanExpireData(long startTime, long endTime) {
      long effectNum = 1;
      long totalDeleted = 0; // 记录删除的总数
      while (effectNum > 0) {
      effectNum = orderInfoDao.deleteByTimeAndLimit(startTime, endTime, 10000);
      totalDeleted += effectNum; // 累积删除的数量
      }
      log.info("Successfully cleaned {} records between {} and {}", totalDeleted, startTime, endTime);
      }


      ##SQL如下:
      delete from orderInfo where start_time > #{startTime} and end_time < #{endTime} limit #{num}

            修改后的代码实现了直接删除满足条件的数据,避免了先查询再删除的步骤。为了提高效率并防止一次性删除大量数据,我们采用了分批删除的策略,每次删除不超过10000条记录。这样的改进使得代码更加简洁,执行速度也得到了显著提升。代码修改点如下:

      1. 避免一次性处理大量数据,采取批量操作

      在处理大数据量的操作时(如删除、更新等),避免一次性加载或操作过多数据,防止耗尽内存及造成的数据库性能问题。分批次处理(如使用 LIMIT
      )可以有效降低系统压力,同时避免长时间的锁定资源,保持系统的稳定性。

      2. 循环操作和停止条件

      在执行分批操作时,我们引入了循环机制,并设定了明确的停止条件,以确保所有数据都能被逐一处理。通过限制每次操作处理的数据量,并让循环持续执行直到没有更多数据需要处理,我们能够有效地避免数据遗漏,确保数据处理的完整性和准确性。

      3. 记录并跟踪处理结果

      通过记录每次操作的结果(如删除的条数)来追踪进度,能够帮助我们准确了解批量操作的效果。此外,正确处理并累积删除的记录总数,有助于调试、日志输出和分析。

      4. 灵活选择批量操作的数量

      设定合适的批量操作数量(如每次删除 10,000 条),根据数据库的规模和性能动态调整。过大可能导致数据库负载过高,过小则会使效率低下。

      总结

           在执行数据库定期清理任务时,建议采用分批删除策略,以减少对数据库性能的影响。首先,根据设定的条件筛选出待删除的数据。然后,为了避免一次性删除大量数据导致数据库压力过大,可以实施分批处理。此外,如果数据库频繁出现性能问题,应检查是否有定时任务在特定时间点执行。利用DBdoctor审计日志分析工具,可以帮助我们迅速诊断并定位问题源头。



      1️⃣ 产品介绍:

      DBdoctor产品介绍
      2️⃣免费下载  一键部署:
      https://www.dbdoctor.cn

      3️⃣在线试用  立即体验:

      试用地址 https://demo.dbdoctor.cn
      (无需下载安装,点击公众菜单栏【产品服务-在线试用】即可获取试用环境专属账号密码

      文章转载自DBdoctor,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

      评论