数据查询修改删除慢可使用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();


}
相关推荐
、BeYourself2 小时前
✅ 宝塔 PostgreSQL 安装UUID指南
数据库·postgresql·springai
一瓢西湖水10 小时前
列式数据库-以clickHouse为例
数据库·clickhouse
Elastic 中国社区官方博客10 小时前
使用 Elastic Cloud Serverless 扩展批量索引
大数据·运维·数据库·elasticsearch·搜索引擎·云原生·serverless
liulanba10 小时前
AI Agent技术完整指南 第一部分:基础理论
数据库·人工智能·oracle
逆天小北鼻10 小时前
Oracle 服务端与客户端的核心区分要点
数据库·oracle
2501_9462429310 小时前
MPV-EASY Player (MPV播放器) v0.41.0.1
数据库·经验分享·云计算·计算机外设·github·电脑·csdn开发云
MySQL实战11 小时前
Redis 7.0 新特性之maxmemory-clients:限制客户端内存总使用量
数据库·redis
VX:Fegn089512 小时前
计算机毕业设计|基于springboot + vue校园社团管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·后端·课程设计
luoluoal12 小时前
基于python的小区监控图像拼接系统(源码+文档)
python·mysql·django·毕业设计·源码