當需要將大量數據(如 8.5 萬條)隨機打亂順序時,直接 ORDER BY RAND() 會導致全表掃描、臨時表、文件排序,極其低效且易超時。本文提供分批遷移、標記處理、自動翻頁的完整方案,適用於千萬級數據重排。
table_bak 結構同源表isget 標記欄位(0 未處理,1 已遷移)<?php
// rand.php - 瀏覽器訪問自動執行
set_time_limit(0); // 移除執行時間限制
ini_set('memory_limit', '256M');
// 數據庫連接(建議使用 PDO)
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$pageSize = 50; // 每批處理條數
$totalCount = 85000; // 總數(可改為動態查詢)
$s = isset($_GET['s']) ? (int)$_GET['s'] : 0; // 起始偏移
if ($s < $totalCount) {
// 1. 讀取未處理記錄(按 ID 倒序保持相對穩定)
$stmt = $pdo->prepare("SELECT id, content, type_id FROM source_table WHERE is_get = 0 ORDER BY id DESC LIMIT ?, ?");
$stmt->execute([$s, $pageSize]);
$rows = $stmt->fetchAll();
if ($rows) {
// 2. 批量寫入目標表(使用事務保證一致性)
$pdo->beginTransaction();
try {
$insertSql = "INSERT INTO target_table (id, content, type_id, is_get) VALUES (?, ?, ?, 1)";
$insertStmt = $pdo->prepare($insertSql);
$updateSql = "UPDATE source_table SET is_get = 1 WHERE id = ?";
$updateStmt = $pdo->prepare($updateSql);
foreach ($rows as $row) {
$insertStmt->execute([$row['id'], $row['content'], $row['type_id']]);
$updateStmt->execute([$row['id']]);
}
$pdo->commit();
echo "批次 $s 成功處理 " . count($rows) . " 條
";
} catch (Exception $e) {
$pdo->rollBack();
die("事務失敗: " . $e->getMessage());
}
}
// 3. 自動跳轉下一批
$next = $s + $pageSize;
echo "<meta http-equiv=\"refresh\" content=\"0;url=rand.php?s=$next&e=$pageSize\">";
echo "正在處理... 當前進度: $s / $totalCount";
} else {
echo "✅ 所有數據處理完成 <a href='rand.php'>重新開始</a>";
}
?>
| 優化 | 說明 |
|---|---|
| PDO 預處理語句 | 防 SQL 注入、重複執行計劃緩存 |
| 事務包裹批次 | 保證寫入與標記更新原子性 |
| 索引優化 | 源表 is_get + id 聯合索引加速 WHERE is_get=0 ORDER BY id |
| 主鍵保持 | 目標表保留原 id,便於關聯查詢、回滾 |
| 瀏覽器自動翻頁 | 無需 CLI 環境,適用於虛擬主機、無 SSH 權限場景 |
若業務要求「完全隨機」而非「打亂順序」,可在目標表新增 sort_order 欄位:
<?php
// 1. 先將所有 ID 讀入記憶體(8.5萬 int 約 0.7MB,完全可行)
$ids = $pdo->query("SELECT id FROM source_table")->fetchAll(PDO::FETCH_COLUMN);
// 2. Fisher-Yates 洗牌算法 O(n)
for ($i = count($ids) - 1; $i > 0; $i--) {
$j = random_int(0, $i);
[$ids[$i], $ids[$j]] = [$ids[$j], $ids[$i]];
}
// 3. 批量更新 sort_order
$chunkSize = 1000;
$pdo->beginTransaction();
$stmt = $pdo->prepare("UPDATE source_table SET sort_order = ? WHERE id = ?");
foreach (array_chunk($ids, $chunkSize) as $idx => $chunk) {
foreach ($chunk as $order => $id) {
$stmt->execute([$idx * $chunkSize + $order, $id]);
}
// 可選:分批提交避免長事務
}
$pdo->commit();
?>
pcntl_fork 多進程並行處理$s 寫入 Redis/文件,支援中斷續傳mysqldump 核心表innodb_flush_log_at_trx_commit=2 臨時提升寫入性能| 方案 | 適用量級 | 優點 | 缺點 |
|---|---|---|---|
ORDER BY RAND() | < 1萬 | 單條 SQL 完成 | 全表掃描、臨時表、極慢 |
| 分批標記遷移(本文) | 1萬-千萬 | 可控、可恢復、無鎖表 | 需多次請求、佔用存儲 |
| Fisher-Yates + 批量更新 | < 100萬 | 真隨機、一次性完成 | 需記憶體載入所有 ID |
| 新增隨機浮點數列 | 任意 | 插入時賦值、查詢 ORDER BY rand_col | 需修改寫入邏輯、索引維護成本 |