Что такое последовательный доступ в БД?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Последовательный доступ в базе данных
Последовательный доступ (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
- Избегай функций в WHERE —
WHERE UPPER(name) = 'JOHN'не использует индекс - Composites индексы — для частых комбинаций условий
- Мониторь seq scans — логируй медленные запросы
- Paritioning — для больших таблиц раздели на части
Последовательный доступ в БД — это узкое место производительности. Правильное индексирование критично для быстрых приложений.