PHP MySQL SELECT с LIMIT: подробное руководство
Узнайте, как использовать LIMIT и OFFSET в MySQL SELECT для ограничения числа строк и реализации постраничной навигации в PHP.
В базе данных часто нужна лишь часть таблицы — десять последних записей, первая страница результатов поиска, единственный «лидер» — а не все строки целиком. Оператор LIMIT в запросе MySQL SELECT ограничивает количество возвращаемых строк. В сочетании с OFFSET он служит основой постраничной навигации: крупные наборы результатов отдаются по одной странице за раз, а не загружаются тысячами строк в память.
В этой главе рассматриваются синтаксис LIMIT, пропуск строк через OFFSET, безопасное построение пагинации с помощью подготовленных запросов и типичные подводные камни. Предполагается, что вы уже умеете подключаться к MySQL и выполнять базовый SELECT.
Синтаксис SELECT с LIMIT
Базовая форма задаёт максимальное количество возвращаемых строк:
SELECT column1, column2, ... FROM table_name LIMIT row_count;Чтобы дополнительно пропустить строки с начала результата, укажите смещение. MySQL поддерживает два равнозначных варианта записи:
SELECT ... FROM table_name LIMIT offset, row_count; -- offset first
SELECT ... FROM table_name LIMIT row_count OFFSET offset; -- explicit OFFSETЧто означает каждая часть:
row_count— максимальное количество возвращаемых строк.offset— сколько строк пропустить перед началом выборки. Смещение начинается с нуля, поэтомуOFFSET 0соответствует первой строке.LIMIT 10, 5возвращает 5 строк, начиная после первых 10 (то есть строки 11–15). Тот же запрос читается понятнее какLIMIT 5 OFFSET 10.
LIMITбезORDER BYнедетерминирован. Без явногоORDER BYMySQL вправе возвращать строки в любом порядке, поэтому «первые 2 строки» могут различаться от запуска к запуску. Всегда сочетайтеLIMITсORDER BY, когда порядок строк важен.
Пример: получение первых строк
Рассмотрим таблицу students:
+----+---------+--------+-------+
| id | name | class | marks |
+----+---------+--------+-------+
| 1 | John | 10 | 90 |
| 2 | Michael | 9 | 85 |
| 3 | Jessica | 8 | 80 |
| 4 | Sarah | 10 | 88 |
| 5 | David | 9 | 72 |
+----+---------+--------+-------+Чтобы получить первых двух студентов (упорядоченных по id для стабильного результата):
<?php
$conn = mysqli_connect("localhost", "username", "password", "database");
$query = "SELECT id, name, class, marks FROM students ORDER BY id LIMIT 2";
$result = mysqli_query($conn, $query);
while ($row = mysqli_fetch_assoc($result)) {
echo "ID: {$row['id']} Name: {$row['name']} Class: {$row['class']} Marks: {$row['marks']}<br>";
}
mysqli_close($conn);
?>Результат:
ID: 1 Name: John Class: 10 Marks: 90
ID: 2 Name: Michael Class: 9 Marks: 85Обратите внимание на использование mysqli_fetch_assoc() (только ассоциативный массив) вместо mysqli_fetch_array(), который возвращает и числовые, и строковые ключи, расходуя вдвое больше памяти для тех же данных.
Пропуск строк с помощью OFFSET
Чтобы получить вторую страницу из двух студентов — строки 3 и 4 — пропустите первые две:
<?php
$conn = mysqli_connect("localhost", "username", "password", "database");
$query = "SELECT id, name, marks FROM students ORDER BY id LIMIT 2 OFFSET 2";
$result = mysqli_query($conn, $query);
while ($row = mysqli_fetch_assoc($result)) {
echo "ID: {$row['id']} Name: {$row['name']} Marks: {$row['marks']}<br>";
}
mysqli_close($conn);
?>Результат:
ID: 3 Name: Jessica Marks: 80
ID: 4 Name: Sarah Marks: 88Безопасное построение пагинации
При реальной пагинации номер страницы поступает из пользовательского ввода (URL вида ?page=3), поэтому смещение нельзя напрямую подставлять в SQL. Используйте подготовленный запрос с привязанными целочисленными параметрами:
<?php
$conn = mysqli_connect("localhost", "username", "password", "database");
$perPage = 2;
$page = max(1, (int) ($_GET['page'] ?? 1)); // force a positive integer
$offset = ($page - 1) * $perPage;
$stmt = mysqli_prepare(
$conn,
"SELECT id, name, marks FROM students ORDER BY id LIMIT ? OFFSET ?"
);
mysqli_stmt_bind_param($stmt, "ii", $perPage, $offset); // "ii" = two integers
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
while ($row = mysqli_fetch_assoc($result)) {
echo "ID: {$row['id']} Name: {$row['name']} Marks: {$row['marks']}<br>";
}
mysqli_close($conn);
?>Приведение страницы к (int) и передача её как целого числа ("ii") предотвращают SQL-инъекцию, поскольку LIMIT/OFFSET в любом случае принимают только числа.
Чтобы отображать «Страница 3 из N», нужно также знать общее количество строк. Выполните отдельный запрос COUNT(*) и разделите на размер страницы:
$total = (int) mysqli_fetch_row(
mysqli_query($conn, "SELECT COUNT(*) FROM students")
)[0];
$pageCount = (int) ceil($total / $perPage); // 5 rows / 2 per page = 3 pagesЧастые подводные камни
- Нет
ORDER BY— нет гарантии. Как отмечено выше,LIMITвозвращает стабильный срез только при наличииORDER BYпо уникальному (или однозначно определяемому) столбцу. - Большие смещения работают медленно.
LIMIT 20 OFFSET 100000всё равно заставляет MySQL просмотреть и отбросить 100 000 строк. Для глубокой пагинации предпочтительна keyset-пагинация —WHERE id > :lastSeenId ORDER BY id LIMIT 20— которая сразу переходит к нужному месту по индексу. См.WHERE. OFFSETнельзя использовать безLIMIT. MySQL требует наличияLIMITпри использованииOFFSET. Чтобы пропустить строки и вернуть «всё остальное», задайте очень большой лимит:LIMIT 18446744073709551615 OFFSET 10.- Страницы нумеруются с нуля внутри. Распространённая ошибка на единицу — забыть, что страница 1 соответствует
OFFSET 0, отсюда формула($page - 1) * $perPageв примере выше.