数据查询修改删除慢可使用mysql表分区功能(心跳记录表分区操作)

前提:仅 InnoDBMyISAM 存储引擎支持分区;

心跳记录表分区操作:

设备心跳每天新增数据量80万条;需要定时删除7天前的心跳数据,普通删除语句属于行级删除,即便使用到索引,速度依然慢;后改成使用存储过程分批次多次删除,虽然每个批次删除速度很快但是总耗时还是长;最后决定改成使用分区操作,对7天前的数据进行删除:速度快,毫秒级(直接删除那些需要清理的数据所在的分区)。

1.将分区字段beat_time(字段类型datetime)设置成主键(原主键Id,新增主键beat_time);

2.创建历史分区和兜底分区(分区规则是按照日期递增,可以跳跃(举例:创建p20251222分区,不创建p20251223分区,直接创建p20251224分区),但不能有数据重叠(举例:兜底分区存的是29号以后的数据,如果直接创建p20251230分区,sql就会报错,原因就是兜底分区和p20251230分区出现了重叠);必须创建兜底分区,防止出现数据插入时找不到对应分区,兜底分区就可以存这些数据):表里有无数据都可以进行分区操作,在navicat数据库管理软件中执行以下sql创建按天划分的 RANGE 分区

sql 复制代码
ALTER TABLE kt_door_hearbeat_log PARTITION BY RANGE (TO_DAYS(beat_time))(
        --22号及之前的数据都放到p20251222分区里
        PARTITION p20251222
	VALUES
		LESS THAN (TO_DAYS('2025-12-23')),
        --23号的数据都放到p20251223分区里
		PARTITION p20251223
	VALUES
		LESS THAN (TO_DAYS('2025-12-24')),
        --24号的数据都放到p20251224分区里
		PARTITION p20251224
	VALUES
		LESS THAN (TO_DAYS('2025-12-25')),
        --25号的数据都放到p20251225分区里
		PARTITION p20251225
	VALUES
		LESS THAN (TO_DAYS('2025-12-26')),
        --26号的数据都放到p20251226分区里
		PARTITION p20251226
	VALUES
		LESS THAN (TO_DAYS('2025-12-27')),
        PARTITION p20251227
	VALUES
		LESS THAN (TO_DAYS('2025-12-28')),
        PARTITION p20251228
	VALUES
		LESS THAN (TO_DAYS('2025-12-29')),
        PARTITION p20251229
	VALUES
		LESS THAN (TO_DAYS('2025-12-30')),
		-- 兜底分区:存放2025-12-29之后的所有数据
		PARTITION p_future
	VALUES
		LESS THAN MAXVALUE
);

3.定期(每天00:00:00秒执行定时任务)删除7天前分区数据:

1.DoorHearBeatLogServiceImpl方法

java 复制代码
 @Override
    public void cleanUpDoorHearBeatLog() {
        //25-12-26xg
        List<String> partitions = doorHearBeatLogMapper.getPartitions();
        try {
            if (partitions != null && !partitions.isEmpty()) {
                LocalDate now = LocalDate.now();

                DateTimeFormatter ymdFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
                String partitionName = "p" + now.format(ymdFormatter);
                LocalDate tomorrow = now.plusDays(1);
                DateTimeFormatter dashYmdFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
                String tomorrowTime = tomorrow.format(dashYmdFormatter);
                //必须保证兜底分区p_future存在,才能继续执行
                if (!partitions.contains("p_future")) {
                    doorHearBeatLogMapper.addPFuture();
                    logger.info("原本p_future分区不存在已重新创建");
                } else {
                    logger.info("原本已存在分区 p_future");
                }
                if (!partitions.contains(partitionName)) {
                    doorHearBeatLogMapper.reorganizeFuturePartition(partitionName, tomorrowTime);
                    partitions = doorHearBeatLogMapper.getPartitions();
                    //验证创建结果,非常重要
                    boolean pFutureExists = partitions.contains("p_future");
                    boolean targetPartitionExists = partitions.contains(partitionName);
                    if (!pFutureExists & !targetPartitionExists) {//两个都没创建成功
                        doorHearBeatLogMapper.addPartition(tomorrowTime, partitionName);
                        logger.warn("两个分区创建失败!重新创建" + partitionName + "分区和兜底分区");
                    } else if (!pFutureExists & targetPartitionExists) {//p_future没创建成功
                        doorHearBeatLogMapper.addPFuture();
                        logger.warn("兜底分区创建失败!重新创建兜底分区");
                    } else if (!targetPartitionExists & pFutureExists) {//partitionName没创建成功
                        doorHearBeatLogMapper.reorganizeFuturePartition(partitionName, tomorrowTime);
                        logger.warn(partitionName + "分区创建失败!重新原子性拆分旧兜底分区,并创建" + partitionName + "分区和新兜底分区");
                    }
                } else {
                    logger.info(partitionName + "已经存在无需创建此分区 ");
                }
                partitions = doorHearBeatLogMapper.getPartitions();
                logger.info("创建完后的所有历史分区:" + partitions);
                List<String> partitionsDel = partitions.stream().filter(partition -> !"p_future".equals(partition)).sorted((o1, o2) -> o2.compareTo(o1)).skip(7).collect(Collectors.toList());
                logger.info("要清除的历史分区:" + partitionsDel);
                partitionsDel.forEach(partition -> doorHearBeatLogMapper.cleanUpDoorHearBeatLog(partition));
            }
        } catch (Exception e) {
            logger.error("系统自动记录快件日志时异常:{}", e.getMessage());
        }
    }

2.DoorHearBeatLogMapper

java 复制代码
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jsdye.project.kt.doorAccess.domain.pojo.DoorHearBeatLog;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.StatementType;

import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author fgz
 * @date 2025/9/29 15:55
 * @description 门禁心跳日志持久层
 */
@Mapper
public interface DoorHearBeatLogMapper extends BaseMapper<DoorHearBeatLog> {
    //25.11.20添加批量删除门禁心跳日志
//    @Delete("CALL batch_delete_door_heartbeat(#{beatTime},30000)")
//    @Options(statementType = StatementType.CALLABLE)
//    void cleanUpDoorHearBeatLog(@Param("beatTime") Date beatTime);

    //25.12.26删除门禁心跳日志
    @Delete({
            "ALTER TABLE kt_door_hearbeat_log DROP PARTITION ${beatTime}"
    })

    void cleanUpDoorHearBeatLog(@Param("beatTime") String beatTime);
    //25-12-26xz
    @Select("SELECT PARTITION_NAME FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_SCHEMA = 'kt' AND TABLE_NAME = 'kt_door_hearbeat_log'")
    List<String> getPartitions();

    //25-12-26xz
    @Insert({"ALTER TABLE kt_door_hearbeat_log ADD PARTITION ( PARTITION ${partitionName} VALUES LESS THAN (TO_DAYS(#{currentTime})), PARTITION p_future VALUES LESS THAN MAXVALUE )"
    })
    void addPartition(@Param("currentTime") String currentTime, @Param("partitionName") String partitionName);

    //25.12.29批量删除门禁心跳日志
//    @Delete({
//            "ALTER TABLE kt_door_hearbeat_log DROP PARTITION ${beatTime};",
//            "ALTER TABLE kt_door_hearbeat_log ADD PARTITION (",
//            "    PARTITION ${partitionName} VALUES LESS THAN (TO_DAYS(#{currentTime})),",
//            "    PARTITION p_future VALUES LESS THAN MAXVALUE",
//            ");"
//    })
//    void cleanAndAddPartition(@Param("beatTime") String beatTime,@Param("currentTime") String currentTime, @Param("partitionName") String partitionName);

    /**25.12.29
     * 原子性拆分兜底分区p_future(无空窗期,避免心跳数据丢失)
     * @param newPartitionName 新历史分区名(如p20251229)
     * @param partitionTime 分区截止时间(如2025-12-29 00:00:00,无需手动加单引号)
     */
    @Update({
            "ALTER TABLE kt_door_hearbeat_log ",
            "REORGANIZE PARTITION p_future INTO (",
            "    PARTITION ${newPartitionName} VALUES LESS THAN (TO_DAYS(#{partitionTime})),",
            "    PARTITION p_future VALUES LESS THAN MAXVALUE",
            ");"
    })
    void reorganizeFuturePartition(
            @Param("newPartitionName") String newPartitionName,
            @Param("partitionTime") String partitionTime
    );

    /**25.12.29
     * 添加兜底分区p_future
     */
    @Insert( {"ALTER TABLE kt_door_hearbeat_log ADD PARTITION (PARTITION p_future VALUES LESS THAN MAXVALUE);"})
    void addPFuture();


}
相关推荐
自不量力的A同学26 分钟前
Redisson 4.2.0 发布,官方推荐的 Redis 客户端
数据库·redis·缓存
Exquisite.28 分钟前
Mysql
数据库·mysql
全栈前端老曹1 小时前
【MongoDB】深入研究副本集与高可用性——Replica Set 架构、故障转移、读写分离
前端·javascript·数据库·mongodb·架构·nosql·副本集
R1nG8631 小时前
CANN资源泄漏检测工具源码深度解读 实战设备内存泄漏排查
数据库·算法·cann
阿钱真强道1 小时前
12 JetLinks MQTT直连设备事件上报实战(继电器场景)
linux·服务器·网络·数据库·网络协议
逍遥德2 小时前
Sring事务详解之02.如何使用编程式事务?
java·服务器·数据库·后端·sql·spring
笨蛋不要掉眼泪2 小时前
Redis哨兵机制全解析:原理、配置与实战故障转移演示
java·数据库·redis·缓存·bootstrap
Coder_Boy_2 小时前
基于SpringAI的在线考试系统-整体架构优化设计方案
java·数据库·人工智能·spring boot·架构·ddd
fen_fen10 小时前
Oracle建表语句示例
数据库·oracle
砚边数影12 小时前
数据可视化入门:Matplotlib 基础语法与折线图绘制
数据库·信息可视化·matplotlib·数据可视化·kingbase·数据库平替用金仓·金仓数据库