← Назад к вопросам

Что такое последовательный доступ в БД?

2.0 Middle🔥 111 комментариев
#Python Core#Soft Skills

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Последовательный доступ в базе данных

Последовательный доступ (Sequential Access) в БД — это способ чтения данных из таблицы строка за строкой в том порядке, в котором они хранятся физически на диске. В отличие от прямого доступа (Direct Access), который позволяет получить конкретную строку по ID или индексу, последовательный доступ требует просмотра всех записей.

Последовательный доступ vs Прямой доступ

Прямой доступ (Index/Direct Access):

SELECT * FROM users WHERE id = 5;

БД использует индекс и находит строку за O(log n) время.

Последовательный доступ (Full Table Scan):

SELECT * FROM users WHERE age > 30;

БД сканирует каждую строку в таблице. Если индекса нет на age, это будет полный просмотр.

Когда используется последовательный доступ

from sqlalchemy import create_engine, select, Column, Integer, String
from sqlalchemy.orm import Session, declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    age = Column(Integer)

engine = create_engine('postgresql://user:pass@localhost/db')

# Запрос БЕЗ индекса на age — последовательный доступ
with Session(engine) as session:
    # Full table scan — просмотр всех строк
    users_over_30 = session.query(User).filter(User.age > 30).all()
    print(users_over_30)

Производительность: полный просмотр таблицы

Если в таблице 1 миллион строк:

  • Прямой доступ по индексу: ~5-10 операций чтения диска
  • Последовательный доступ: ~100,000+ операций чтения диска

Диск — самое медленное устройство в системе. Каждое обращение к диску стоит ~1 миллисекунду. Последовательный доступ может занять секунды!

Оптимизация через индексы

from sqlalchemy import Index
from sqlalchemy.orm import declarative_base
from sqlalchemy import Column, Integer, String

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    age = Column(Integer, index=True)  # Создаём индекс на age

Base.metadata.create_all(engine)

# Теперь запрос использует индекс — прямой доступ
with Session(engine) as session:
    users_over_30 = session.query(User).filter(User.age > 30).all()

Пример: анализ плана запроса

-- БЕЗ индекса — Seq Scan (последовательный)
EXPLAIN SELECT * FROM users WHERE age > 30;

/*
 Seq Scan on users  (cost=0.00..1000.00 rows=50000)
   Filter: (age > 30)
*/

-- С индексом — Index Scan (прямой)
CREATE INDEX idx_age ON users(age);
EXPLAIN SELECT * FROM users WHERE age > 30;

/*
 Index Scan using idx_age on users  (cost=0.42..42.50 rows=50000)
   Index Cond: (age > 30)
*/

Составные индексы для оптимизации

from sqlalchemy import Index

class Order(Base):
    __tablename__ = 'orders'
    id = Column(Integer, primary_key=True)
    customer_id = Column(Integer)
    status = Column(String)
    created_at = Column(DateTime)

# Составной индекс
idx = Index('idx_customer_status', Order.customer_id, Order.status)
Base.metadata.create_all(engine)

# Это использует индекс
with Session(engine) as session:
    orders = session.query(Order).filter(
        Order.customer_id == 123,
        Order.status == 'completed'
    ).all()

Когда последовательный доступ неизбежен

# 1. Полный просмотр таблицы
with Session(engine) as session:
    all_users = session.query(User).all()  # Seq scan всегда

# 2. Агрегация без индекса
with Session(engine) as session:
    avg_age = session.query(func.avg(User.age)).scalar()  # Seq scan

# 3. LIKE с шаблоном в начале
with Session(engine) as session:
    # БЕЗ индекса — seq scan
    users = session.query(User).filter(User.name.like('%john')).all()
    # С индексом BTREE не поможет — нужен специальный индекс

Оптимизация запросов для избежания seq scan

from sqlalchemy import text
from sqlalchemy.orm import Session

# ПЛОХО: Seq scan
with Session(engine) as session:
    users = session.query(User).filter(
        User.status != 'inactive'  # Не индексируется
    ).all()

# ХОРОШО: Индекс + прямой доступ
with Session(engine) as session:
    users = session.query(User).filter(
        User.status == 'active'  # Индексируется
    ).all()

# Использование LIKE с индексом
with Session(engine) as session:
    # Создаём индекс GIN для полнотекстового поиска
    users = session.execute(
        text("""
        SELECT * FROM users 
        WHERE to_tsvector(name) @@ plainto_tsquery(:query)
        """)
        .bindparams(query='john')
    ).all()

Мониторинг и выявление seq scan

from sqlalchemy import event
from sqlalchemy.pool import Pool

@event.listens_for(Pool, "connect")
def receive_connect(dbapi_conn, connection_record):
    # Включить логирование медленных запросов
    cursor = dbapi_conn.cursor()
    cursor.execute("SET log_min_duration_statement = 1000;")  # 1 сек
    cursor.close()

engine = create_engine(
    'postgresql://user:pass@localhost/db',
    echo=True  # Выведет SQL в консоль
)

Примеры оптимизации

ПЕРЕД:

# Много seq scans
with Session(engine) as session:
    for product_id in product_ids:
        product = session.query(Product).filter(
            Product.id == product_id
        ).first()
        # N+1 problem

ПОСЛЕ:

# Один запрос с индексом
with Session(engine) as session:
    products = session.query(Product).filter(
        Product.id.in_(product_ids)
    ).all()

Лучшие практики

  • Создавай индексы на колонках, используемых в WHERE, JOIN, ORDER BY
  • ANALYZE запросы — используй EXPLAIN ANALYZE
  • Избегай функций в WHEREWHERE UPPER(name) = 'JOHN' не использует индекс
  • Composites индексы — для частых комбинаций условий
  • Мониторь seq scans — логируй медленные запросы
  • Paritioning — для больших таблиц раздели на части

Последовательный доступ в БД — это узкое место производительности. Правильное индексирование критично для быстрых приложений.

Что такое последовательный доступ в БД? | PrepBro