看起来行数不多,奈何每条数据里塞了好几个 LONGTEXT 字段,单条数据体量巨大。

最近需要对这张表做一个备份操作:根据批次 ID(batchid)把某批数据备份到另一个库,然后删掉原表里对应的数据。

目标库有同名表,用来归档历史数据。思路很直接,两条 SQL 搞定:

复制代码
– 先把数据迁过去
INSERT INTO history_db.target_table
SELECT * FROM target_table WHERE batchid = 381;

– 再把原表数据删掉
DELETE FROM target_table WHERE batchid = 381;
复制代码
结果一执行直接报错,要么是:

SQL 错误 [3] [HY000]: Error writing file ‘C:\Windows\TEMP\MLa4gsg6dpk9s2z8ry’ (OS errno 28 - No space left on device)

要么是:

SQL 错误 [1114] [HY000]: The table ‘C:\Windows\TEMP#sql6f88_bd086f_2’ is full

MySQL 是装在Windows服务器的,不过明明装在 D 盘,为什么 C 盘会爆?

实际上,MySQL 在执行大事务或者大查询时,会在系统临时目录(C:\Windows\TEMP)生成临时文件。当一次操作的数据量太大,临时文件直接把 C 盘写满了,SQL 自然就挂了。INSERT … SELECT 一次性搬运大量 LONGTEXT 数据,临时表膨胀得飞快(现象就是服务器剩余50G的C盘迅速飙红,待执行失败后又恢复正常)。

既然一步到位不行,那就化整为零——用存储过程分批处理:

复制代码
CREATE DEFINER=root@% PROCEDURE business_db.batch_migrate_and_delete(
IN p_table_name VARCHAR(128),
IN p_batchid BIGINT
)
batch_migrate_and_delete: BEGIN
DECLARE v_min_id BIGINT;
DECLARE v_max_id BIGINT;
DECLARE v_batch_size INT DEFAULT 500;

-- 获取 id 范围
SET @sql_range = CONCAT(
    'SELECT MIN(id), MAX(id) INTO @v_min, @v_max FROM ',
    p_table_name, ' WHERE batchid = ', p_batchid
);
PREPARE stmt FROM @sql_range;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET v_min_id = @v_min;
SET v_max_id = @v_max;

IF v_min_id IS NULL THEN
    LEAVE batch_migrate_and_delete;
END IF;

-- 每批先迁移到历史库,再删除原表数据
WHILE v_min_id <= v_max_id DO
    SET @sql_insert = CONCAT(
        'INSERT INTO history_db.', p_table_name,
        ' SELECT * FROM ', p_table_name,
        ' WHERE batchid = ', p_batchid,
        ' AND id BETWEEN ', v_min_id, ' AND ', v_min_id + v_batch_size - 1
    );
    PREPARE stmt FROM @sql_insert;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;

    SET @sql_delete = CONCAT(
        'DELETE FROM ', p_table_name,
        ' WHERE batchid = ', p_batchid,
        ' AND id BETWEEN ', v_min_id, ' AND ', v_min_id + v_batch_size - 1
    );
    PREPARE stmt FROM @sql_delete;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;

    COMMIT;
    SET v_min_id = v_min_id + v_batch_size;
END WHILE;

END batch_migrate_and_delete
复制代码
创建存储过程后,执行call即可

CALL batch_migrate_and_delete(‘target_table’, 381)

存储过程的核心思路很简单:先用 MIN(id) 和 MAX(id) 圈出要处理的数据范围,然后每次取 500 条(v_batch_size),做完 insert 和 delete 立刻 COMMIT,再继续下一批。这样每批事务体量都很小,临时文件来不及撑爆 C 盘就已经释放了。

⚠️ 需要注意的是:

​v_batch_size 要结合实际调。我这里的表单条数据就很大,设 500 差不多。如果你的表行数多但单条很小,可以适当调大;反过来单条更大就调小一些,核心是控制每批事务的数据总量。
按 id BETWEEN 分批的前提是 id 连续或大致连续。如果 id 有大量空洞,可以换成 LIMIT 配合游标的方式分批。
迁移和删除放在同一个事务批次里。先 insert 成功再 delete,万一 insert 失败,当前批次不会被删掉,数据不会丢。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐