PHP 数据归档怎么做

wen PHP项目 2

本文目录导读:

PHP 数据归档怎么做

  1. 归档策略选择
  2. 核心实现方案
  3. 最佳实践建议
  4. 扩展方案:归档到 ClickHouse 做分析

在 PHP 中实现数据归档,通常是为了处理海量数据、优化数据库性能或满足法规要求,下面从策略、技术到代码实现,系统性地给你梳理一下。

归档策略选择

首先明确要归档什么、归档到哪里,以及归档后怎么查询。

策略 适用场景 优点 缺点
软删除(逻辑归档) 数据量小,需频繁恢复 简单、可逆 查询性能下降,数据未真正减少
分区表归档 时间序列数据(日志、订单) 删除分区极快,管理方便 数据库需支持分区(MySQL 8.0+/PG)
历史表迁移 业务明确区分热/冷数据 主表保持轻量 查询需跨表,需处理关联
文件/对象存储导出 合规审计、长期保存 节省数据库空间 查询必须通过外部工具

推荐组合:生产库用 分区表 + 定期把老分区迁移到 归档库/对象存储,同时保留软删除标记用于业务逻辑。


核心实现方案

方案 1:基于分区表的自动归档(MySQL 8.0+)

// 1. 创建分区表(按月分区)
CREATE TABLE `orders` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `order_no` VARCHAR(32) NOT NULL,
  `status` TINYINT NOT NULL DEFAULT '0',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`, `created_at`)   // 注意:主键必须包含分区键
) PARTITION BY RANGE (YEAR(created_at) * 100 + MONTH(created_at)) (
  PARTITION p202401 VALUES LESS THAN (202402),
  PARTITION p202402 VALUES LESS THAN (202403),
  PARTITION p202403 VALUES LESS THAN (202404)
  // 后续可动态添加
);
// 2. 归档调度:每天凌晨3点执行
// 用 cron 执行:0 3 * * * php /path/to/archive.php
// 3. PHP 执行归档逻辑
<?php
class OrderArchiver {
    private $pdo;
    public function __construct() {
        $this->pdo = new PDO(
            'mysql:host=localhost;dbname=main_db;charset=utf8mb4',
            'root', 'password',
            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
        );
    }
    /**
     * 归档 3 个月前的数据到归档库
     */
    public function archiveOldData(): int {
        $archiveMonth = date('Ym', strtotime('-3 months'));
        $nextMonth = date('Ym', strtotime('-3 months +1 month'));
        try {
            // 开启事务
            $this->pdo->beginTransaction();
            // 创建归档库中的分区(如果不存在)
            $this->createArchivePartitionIfNotExists($archiveMonth);
            // 步骤1:从主表 SELECT 并 INSERT 到归档库
            $sql = "INSERT INTO archive_db.orders (
                        id, order_no, status, created_at
                    ) SELECT 
                        id, order_no, status, created_at 
                    FROM main_db.orders 
                    WHERE created_at >= ? AND created_at < ? 
                    LIMIT 10000"; // 分批处理防止内存溢出
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute([
                date('Y-01-01 00:00:00', strtotime("-$archiveMonth month")),
                date('Y-m-01 00:00:00', strtotime("-$archiveMonth month +1 month"))
            ]);
            $moved = $stmt->rowCount();
            // 步骤2:删除主表中已经归档的数据(快速删除)
            $deleteSql = "DELETE FROM main_db.orders 
                          WHERE created_at < ? 
                          LIMIT 10000"; // 关联相同条件
            $deleteStmt = $this->pdo->prepare($deleteSql);
            $deleteStmt->execute([
                date('Y-m-01 00:00:00', strtotime('-3 months'))
            ]);
            // 步骤3:手动 DROP 或 TRUNCATE 掉主表中该月分区(最快,推荐)
            $this->dropPartition($archiveMonth);
            $this->pdo->commit();
            return $moved;
        } catch (Exception $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }
    private function createArchivePartitionIfNotExists($archiveMonth) {
        $sql = "ALTER TABLE archive_db.orders ADD PARTITION (
                    PARTITION p_{$archiveMonth} VALUES LESS THAN (?)
                )";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$archiveMonth . '01']);
    }
    private function dropPartition($archiveMonth) {
        $sql = "ALTER TABLE main_db.orders DROP PARTITION p_{$archiveMonth}";
        $this->pdo->exec($sql);
    }
}
// 执行
$archiver = new OrderArchiver();
$count = $archiver->archiveOldData();
echo "归档完成,移动 {$count} 条记录";

方案 2:业务触发式软删除 + 定期批量迁移

适用于业务逻辑需要保留记录,但主表数据量变大需要归档。

<?php
class DataArchiver {
    private $db;
    public function __construct(PDO $db) {
        $this->db = $db;
        $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }
    /**
     * 标记软删除(业务层面归档)
     */
    public function softArchive(int $userId): bool {
        $sql = "UPDATE users SET status = 2, archived_at = NOW() WHERE id = ?";
        $stmt = $this->db->prepare($sql);
        return $stmt->execute([$userId]);
    }
    /**
     * 定时任务:将 archived_at 超过90天的数据物理迁移到 archive.users
     */
    public function hardMigration(int $days = 90): int {
        $threshold = date('Y-m-d H:i:s', strtotime("-{$days} days"));
        $this->db->beginTransaction();
        try {
            // 1. 批量选取待归档数据
            $fetchSql = "SELECT * FROM users WHERE archived_at < ? AND archived_at IS NOT NULL LIMIT 5000";
            $fetchStmt = $this->db->prepare($fetchSql);
            $fetchStmt->execute([$threshold]);
            $rows = $fetchStmt->fetchAll(PDO::FETCH_ASSOC);
            // 2. 插入归档库(假设 archive_users 表结构相同)
            foreach ($rows as $row) {
                $insertSql = "INSERT INTO archive.users (". implode(', ', array_keys($row)) .") VALUES (?, ?, ...)";
                // 拼接参数绑定
                $this->db->prepare($insertSql)->execute(array_values($row));
            }
            // 3. 删除原表数据(分批)
            $deleteSql = "DELETE FROM users WHERE archived_at < ? AND archived_at IS NOT NULL LIMIT 5000";
            $stmt = $this->db->prepare($deleteSql);
            $stmt->execute([$threshold]);
            $this->db->commit();
            return count($rows);
        } catch (Exception $e) {
            $this->db->rollBack();
            throw $e;
        }
    }
}
// 在业务代码中调用软归档
$archiver = new DataArchiver($pdo);
$archiver->softArchive(123);
// 在 cron 中每天调用硬迁移
// $moved = $archiver->hardMigration(180);

方案 3:导出到 CSV/JSON + 压缩存储(适合合规审计)

<?php
class FileArchiver {
    private $db;
    public function __construct(PDO $db) {
        $this->db = $db;
    }
    /**
     * 将超过一年的日志导出为 CSV 并压缩存储
     */
    public function exportLogsToFile(int $olderThanDays = 365): string {
        $threshold = date('Y-m-d 00:00:00', strtotime("-{$olderThanDays} days"));
        $filePath = '/var/data/archive/' . date('Y-m-d') . '_logs.csv.gz';
        // 打开 gzip 写入流
        $fp = gzopen($filePath, 'w');
        // 写入表头
        fputcsv($fp, ['id', 'user_id', 'action', 'ip', 'created_at']);
        // 分批查询写入
        $offset = 0;
        $batchSize = 1000;
        while (true) {
            $sql = "SELECT id, user_id, action, ip, created_at 
                    FROM activity_logs 
                    WHERE created_at < ? 
                    LIMIT ?, ?";
            $stmt = $this->db->prepare($sql);
            $stmt->bindValue(1, $threshold);
            $stmt->bindParam(2, $offset, PDO::PARAM_INT);
            $stmt->bindParam(3, $batchSize, PDO::PARAM_INT);
            $stmt->execute();
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
            if (empty($rows)) break;
            foreach ($rows as $row) {
                fputcsv($fp, $row);
            }
            $offset += $batchSize;
        }
        gzclose($fp);
        // 删除原表数据
        $deleteSql = "DELETE FROM activity_logs WHERE created_at < ?";
        $stmt = $this->db->prepare($deleteSql);
        $stmt->execute([$threshold]);
        return $filePath;
    }
}

最佳实践建议

  1. 分批处理:永远不要一次性 SELECT 全部数据再归档,用 LIMIT N 循环处理。

  2. 索引优化

    • 归档条件字段(如 created_at)务必加索引
    • 软删除字段加复合索引:(status, archived_at)
  3. 事务控制:数据迁移必须用事务,确保主库和归档库数据一致。

  4. 失败重试机制:在定时任务中加入日志记录,失败后自动重试或人工介入。

  5. 归档验证:迁移完成后执行 COUNT(*) 对比,确保没有数据丢失。

  6. 备份保护:归档文件定期做异地备份(OSS/S3/磁带库)。

  7. 监控:观察归档任务执行时间、归档数据量、磁盘空间变化,设置告警。


扩展方案:归档到 ClickHouse 做分析

如果归档数据还需要进行大数据分析,可以将归档数据直接写入 ClickHouse:

class ClickHouseArchiver {
    private $client;
    public function __construct() {
        // 使用 clickhouse-php-client
        $this->client = new ClickHouseClient([
            'host' => 'localhost',
            'port' => 8123,
            'database' => 'analytics'
        ]);
    }
    public function archiveOrdersToCH(int $months = 6): void {
        // 从 MySQL 导出
        $sql = "SELECT * FROM orders WHERE created_at < NOW() - INTERVAL {$months} MONTH";
        $stmt = $this->pdo->query($sql);
        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
        // 批量插入 ClickHouse
        $this->client->insert('orders_archive', $rows);
        // 删除 MySQL 中记录
        $this->pdo->exec("DELETE FROM orders WHERE created_at < NOW() - INTERVAL {$months} MONTH");
    }
}

用哪个方案取决于:

  • 数据量:百万级用分区+迁移;千万级用分区+定期删除分区;海量需上分布式。
  • 查询需求:需要在线查询历史数据就保留在归档库;只需审计就导出文件。
  • 基础设施:是否有足够的磁盘/备份空间,是否用云数据库(RDS 自带归档功能)。

基本流程软删除(业务标记) → 定期迁移(到归档库/文件) → 物理删除(清空主表)= 闭环归档

抱歉,评论功能暂时关闭!