| 策略 | SQL | 特點 |
|---|---|---|
| A: LIMIT 偏移 | SELECT * FROM t WHERE cond LIMIT offset, perpage | 資料庫內部跳過 offset 列,大偏移時效能下降 |
| B: 游標/記憶體 | SELECT * FROM t WHERE cond + mysql_data_seek() | 一次撈全表至記憶體,PHP 端定位,大資料量記憶體爆炸 |
記錄數 方案 平均耗時(秒) 效率比
10,000 A 1.6 4.8x
B 7.7
20,000 A 7.5 6.7x
B 49.9
30,000 A 26.0 3.3x
B 86.6
結論:LIMIT 方案 (A) 全勝。游標方案 (B) 隨資料量線性惡化,記憶體與網路傳輸為瓶頸。
MySQL LIMIT 1000000, 20 仍需掃描前 1,000,000 列再捨棄。解法:
SELECT * FROM t INNER JOIN (SELECT id FROM t WHERE cond ORDER BY id LIMIT 1000000, 20) tmp USING (id)WHERE id > last_id ORDER BY id LIMIT 20 (需唯一排序鍵、不支援跳頁)INDEX (cond_col, id) 讓子查詢只掃索引perPage;
// 總數
$total = $this->pdo->prepare("SELECT COUNT(*) FROM {$this->table} WHERE {$this->where}");
$total->execute($this->params);
$totalRows = $total->fetchColumn();
// 資料 (延遲關聯優化)
$sql = "SELECT t.* FROM {$this->table} t
INNER JOIN (
SELECT id FROM {$this->table} WHERE {$this->where} ORDER BY id LIMIT :offset, :limit
) tmp USING (id)";
$stmt = $this->pdo->prepare($sql);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->bindValue(':limit', $this->perPage, PDO::PARAM_INT);
foreach ($this->params as $k => $v) {
$stmt->bindValue($k, $v);
}
$stmt->execute();
$rows = $stmt->fetchAll();
return [
'data' => $rows,
'current_page' => $page,
'per_page' => $this->perPage,
'total' => $totalRows,
'last_page' => ceil($totalRows / $this->perPage),
];
}
}