Webアプリケーションを開発する場合、ページネーションクエリは、特にバックエンド管理システム、コメントセクション、または大量のデータを表示する必要がある製品リストなどのモジュールで非常に一般的なニーズの1つです。 MySQLはページングの制限条項を提供し、PHPのPDOのPDOSTATEMENT :: ROWCOUNT()メソッドを使用して結果セットの数を取得できます。 2つが適切に組み合わされている場合、コードの読みやすさを改善するだけでなく、クエリ効率を大幅に最適化することもできます。
従来のページネーションクエリには通常、2つのステップがあります。
現在のページのデータを取得します。制限オフセット、カウントを使用します。
レコードの総数を取得する:テーブルからSelectカウント(*)を使用します。
この方法は非常に一般的ですが、データテーブルが大きい場合、特に高い並行性環境では、カウント(*)のオーバーヘッドが非常に高くなる可能性があり、データベースに大きな圧力をかける可能性があります。
pdostatement :: rowcount()は、選択クエリで一貫性がありません。レコードの総数をカウントするのではなく、今回実行された声明の影響を受ける行の数を返すために使用されます。 Selectを使用する場合、一部のデータベース(PostgreSQLなど)はクエリ結果の行数を返すようにサポートしますが、MySQLでは、 PDO :: MySQL_ATTR_USE_BUFFERED_QUERYオプションが設定されている場合にのみ、正確な数の結果を返すことができます。
$pdo = new PDO('mysql:host=localhost;dbname=testdb', 'user', 'pass', [
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
]);
RowCount()を完全に依存することはできませんが、制限とインテリジェントロジックを組み合わせて、いくつかのシナリオでページネーションエクスペリエンスを最適化できます。たとえば、ページごとにもう1つのデータを照会して、次のページがあるかどうかを判断して、カウント(*)操作を回避できます。
<?php
$pdo = new PDO('mysql:host=localhost;dbname=exampledb', 'user', 'password', [
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$pageSize = 10;
$offset = ($page - 1) * $pageSize;
$limitPlusOne = $pageSize + 1;
$sql = "SELECT * FROM articles ORDER BY created_at DESC LIMIT :offset, :limit";
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->bindValue(':limit', $limitPlusOne, PDO::PARAM_INT);
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
$hasNextPage = false;
if (count($results) > $pageSize) {
$hasNextPage = true;
array_pop($results); // 余分なものを削除します
}
// レンダリングデータ
foreach ($results as $article) {
echo "<h2>{$article['title']}</h2>";
echo "<p>{$article['summary']}</p>";
echo "<a href=\"https://gitbox.net/articles/{$article['id']}\">全文を読んでください</a><hr>";
}
if ($hasNextPage) {
echo "<a href=\"https://gitbox.net/list?page=" . ($page + 1) . "\">次のページ</a>";
}
?>
カウント(*)クエリを避け、データベース圧力を下げます。
n+1を使用して、次のページがあるかどうかを判断します。
データ処理ロジックは簡潔で、パフォーマンスは良好です。
特に、データ量が大きく、ページングの深さが低いシナリオに適しています。
上記の方法は効率的ですが、すべての状況には適用できません。ページの合計ページ数、合計レコードカウントなどの情報を表示する必要がある場合、カウント(*)を使用することは避けられません。
$totalCount = $pdo->query("SELECT COUNT(*) FROM articles")->fetchColumn();
$totalPages = ceil($totalCount / $pageSize);
妥協点の1つは、情報の総数をキャッシュし、定期的に更新して、毎回リアルタイムの計算を避けることです。
ページングの最適化を行うとき、「万能」のベストプラクティスはありません。制限を合理的に組み合わせることで、 RowCount() 、およびビジネスニーズが高性能ページングクエリの鍵です。極端なパフォーマンスを追求している場合は、大規模なデータセットでパフォーマンスを発揮するプライマリキーまたはタイムスタンプに基づいて「カーソルベースのページネーション」を検討できます。
この記事で導入されたヒントを使用すると、優れたユーザーエクスペリエンスとページングの正確性を維持しながら、ほとんどのシナリオで高価なカウント(*)クエリを回避できます。