首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP MySQL 數據庫隨機重排實現:分批處理避免超時的完整方案

     編輯:PHP 相關知識

當需要將大量數據(如 8.5 萬條)隨機打亂順序時,直接 ORDER BY RAND() 會導致全表掃描、臨時表、文件排序,極其低效且易超時。本文提供分批遷移、標記處理、自動翻頁的完整方案,適用於千萬級數據重排。

核心思路

  1. 建立目標表 table_bak 結構同源表
  2. 源表新增 isget 標記欄位(0 未處理,1 已遷移)
  3. 分批讀取源表未處理記錄,寫入目標表,更新標記
  4. 利用 Meta Refresh 實現瀏覽器自動翻頁,無需命令行

完整代碼實現

<?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 權限場景

進階版:真隨機排序(Fisher-Yates 洗牌)

若業務要求「完全隨機」而非「打亂順序」,可在目標表新增 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();
?>

生產環境建議

  1. 命令行模式:將邏輯封裝為 Artisan/Console 命令,使用 pcntl_fork 多進程並行處理
  2. 進度持久化:將 $s 寫入 Redis/文件,支援中斷續傳
  3. 監控告警:記錄每批耗時、成功率,異常自動通知
  4. 備份先行:操作前 mysqldump 核心表
  5. 低峰期執行:避開業務高峰,設置 innodb_flush_log_at_trx_commit=2 臨時提升寫入性能

替代方案對比

選擇方案時請根據數據量、業務對隨機度要求、維護窗口綜合評估。本文分批遷移方案在「無 CLI 權限、數據量中等、需可視化進度」場景下最為實用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
方案適用量級優點缺點
ORDER BY RAND()< 1萬單條 SQL 完成全表掃描、臨時表、極慢
分批標記遷移(本文)1萬-千萬可控、可恢復、無鎖表需多次請求、佔用存儲
Fisher-Yates + 批量更新< 100萬真隨機、一次性完成需記憶體載入所有 ID
新增隨機浮點數列任意插入時賦值、查詢 ORDER BY rand_col需修改寫入邏輯、索引維護成本