Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое LIMIT OFFSET
LIMIT OFFSET — это SQL клауза для пагинации результатов запроса. LIMIT ограничивает количество возвращаемых строк, а OFFSET пропускает N первых строк. Это один из основных способов реализации постраничного отображения данных в веб-приложениях.
Основной синтаксис
# SQL
SELECT * FROM users
LIMIT 10 # Возвращаем максимум 10 строк
OFFSET 0 # Начиная с позиции 0 (первая строка)
# Результат: строки 1-10
Как это работает
# OFFSET пропускает строки, LIMIT ограничивает результат
SELECT * FROM users
LIMIT 10 OFFSET 20
# Означает: пропусти первые 20 строк, затем возьми 10
# Результат: строки 21-30
Пример с PostgreSQL
import psycopg2
conn = psycopg2.connect('dbname=shop')
cursor = conn.cursor()
# Страница 1: строки 1-10
cursor.execute('SELECT id, name, price FROM products LIMIT 10 OFFSET 0')
page1 = cursor.fetchall()
# Страница 2: строки 11-20
cursor.execute('SELECT id, name, price FROM products LIMIT 10 OFFSET 10')
page2 = cursor.fetchall()
# Страница 3: строки 21-30
cursor.execute('SELECT id, name, price FROM products LIMIT 10 OFFSET 20')
page3 = cursor.fetchall()
SQLAlchemy / ORM
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from models import User
session = Session(engine)
page = 2
page_size = 10
# Рассчитываем offset: (page - 1) * page_size
offset = (page - 1) * page_size
users = session.query(User).limit(page_size).offset(offset).all()
print(f'Страница {page}: {len(users)} пользователей')
FastAPI с пагинацией
from fastapi import FastAPI, Query
from sqlalchemy.orm import Session
from typing import List
app = FastAPI()
@app.get('/users')
async def get_users(
skip: int = Query(0, ge=0), # offset = skip
limit: int = Query(10, ge=1, le=100) # максимум 100
):
# skip и limit передаём в БД
db_users = db.query(User).limit(limit).offset(skip).all()
return db_users
# Использование:
# GET /users?skip=0&limit=10 → страница 1
# GET /users?skip=10&limit=10 → страница 2
# GET /users?skip=20&limit=10 → страница 3
Вычисление offset в коде
def calculate_offset(page: int, page_size: int) -> int:
"""Вычисляет offset из номера страницы"""
if page < 1:
raise ValueError('Page должна быть >= 1')
return (page - 1) * page_size
# Примеры
assert calculate_offset(1, 10) == 0 # Страница 1 → offset 0
assert calculate_offset(2, 10) == 10 # Страница 2 → offset 10
assert calculate_offset(3, 10) == 20 # Страница 3 → offset 20
assert calculate_offset(5, 25) == 100 # Страница 5 с размером 25
Вариант: LIMIT offset, limit (PostgreSQL)
# Альтернативный синтаксис: LIMIT count OFFSET offset
SELECT * FROM products
LIMIT 10 OFFSET 20
# Эквивалентно (в некоторых БД):
SELECT * FROM products
LIMIT 10, 20 # MySQL синтаксис (LIMIT offset, count)
Сортировка ОБЯЗАТЕЛЬНА при пагинации
НЕПРАВИЛЬНО (без ORDER BY):
# Опасно! Порядок может измениться между запросами
SELECT * FROM users LIMIT 10 OFFSET 0
SELECT * FROM users LIMIT 10 OFFSET 10 # Может быть другой порядок
ПРАВИЛЬНО (с ORDER BY):
# Порядок фиксирован
SELECT * FROM users
ORDER BY id ASC
LIMIT 10 OFFSET 0
SELECT * FROM users
ORDER BY id ASC
LIMIT 10 OFFSET 10 # Гарантированно строки 11-20
Пример с Django ORM
from django.core.paginator import Paginator
from .models import Product
# Способ 1: встроенный Paginator
products = Product.objects.all()
paginator = Paginator(products, 10) # 10 на странице
page = paginator.get_page(1) # Страница 1
# Под капотом использует LIMIT OFFSET
# Способ 2: напрямую
page_num = 2
page_size = 10
offset = (page_num - 1) * page_size
products = Product.objects.all()[offset:offset + page_size]
# Django преобразует это в: LIMIT 10 OFFSET 10
Производительность: OFFSET проблема
Проблема OFFSET при больших смещениях:
# Быстро: OFFSET 0
SELECT * FROM products LIMIT 10 OFFSET 0
# Медленнее: OFFSET 1000
SELECT * FROM products LIMIT 10 OFFSET 1000
# БД читает 1010 строк, затем отбрасывает первые 1000!
# Очень медленно: OFFSET 100000
SELECT * FROM products LIMIT 10 OFFSET 100000
# БД читает 100010 строк! Неэффективно!
Оптимизация: Keyset Pagination
Для больших смещений лучше использовать keyset pagination (seek-based):
# Вместо OFFSET, используем WHERE с последним ID
LAST_ID = 5000 # ID последнего элемента на предыдущей странице
# НЕПРАВИЛЬНО (медленно при больших offset)
SELECT * FROM products LIMIT 10 OFFSET 10000
# ПРАВИЛЬНО (быстро, использует индекс)
SELECT * FROM products
WHERE id > LAST_ID
ORDER BY id ASC
LIMIT 10
Полный пример пагинации
from fastapi import FastAPI, Query
from sqlalchemy import func
from sqlalchemy.orm import Session
app = FastAPI()
class PaginationResponse:
def __init__(self, items, total, page, page_size):
self.items = items
self.total = total
self.page = page
self.page_size = page_size
self.pages = (total + page_size - 1) // page_size
@app.get('/products')
async def get_products(
page: int = Query(1, ge=1),
page_size: int = Query(10, ge=1, le=100),
db: Session = Depends(get_db)
):
# Считаем общее количество
total = db.query(func.count(Product.id)).scalar()
# Рассчитываем offset
offset = (page - 1) * page_size
# Получаем данные
items = (
db.query(Product)
.order_by(Product.id)
.limit(page_size)
.offset(offset)
.all()
)
return PaginationResponse(items, total, page, page_size)
# Результат:
# {
# "items": [...],
# "total": 1000,
# "page": 1,
# "page_size": 10,
# "pages": 100
# }
Правила и лучшие практики
✅ ВСЕГДА:
- Используй ORDER BY при пагинации
- Ограничивай максимальный LIMIT (например, <= 100)
- Валидируй page и page_size на входе
- Кэшируй COUNT если он дорогой
❌ НИКОГДА:
- Не забывай ORDER BY
- Не используй OFFSET для очень больших смещений (>10000)
- Не позволяй клиентам устанавливать LIMIT > 100
- Не выполняй COUNT для каждого запроса без кэша
Оптимизация для большших наборов:
# Переходи на keyset pagination при offset > 10000
if offset > 10000:
# Используй WHERE id > last_id вместо OFFSET
query = db.query(Product).filter(Product.id > last_id)
else:
query = db.query(Product).offset(offset)
LIMIT OFFSET — простой и надёжный способ пагинации для большинства случаев, но требует внимания к производительности при больших смещениях.