Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое Full Scan
Full Scan (полное сканирование таблицы, TABLE SCAN) — это операция в базе данных, при которой СУБД последовательно читает все строки таблицы с начала до конца, без использования индексов. Это самый неэффективный способ поиска данных, особенно для больших таблиц.
Когда происходит Full Scan
// SQL пример 1: Поиск без индекса
SELECT * FROM users WHERE age > 30;
// БД читает ВСЕ строки и проверяет условие age > 30 для каждой
// SQL пример 2: Функция на индексированном поле
SELECT * FROM users WHERE UPPER(name) = 'JOHN';
// Индекс на name не может быть использован из-за функции UPPER
// БД читает все строки
// SQL пример 3: Отрицание
SELECT * FROM users WHERE status != 'inactive';
// Индекс на status менее полезен
Full Scan vs Index Scan — сравнение
┌─────────────────────────────────────┐
│ ТАБЛИЦА USERS (1 млн строк) │
│ │
│ Index на ID: есть ✓ │
│ Index на name: есть ✓ │
│ Index на age: НЕТ │
└─────────────────────────────────────┘
Запрос 1: SELECT * FROM users WHERE id = 5
-> ЭФФЕКТИВНО: Index Scan (прямой доступ по индексу за O(log n))
Запрос 2: SELECT * FROM users WHERE name = 'John'
-> ЭФФЕКТИВНО: Index Scan на name (за O(log n))
Запрос 3: SELECT * FROM users WHERE age > 30
-> НЕЭФФЕКТИВНО: Full Scan (читает 1 млн строк за O(n))
Как это влияет на производительность
// Пример с цифрами
// Full Scan — читает 1,000,000 строк
// Время: ~10-15 секунд (зависит от размера строки, скорости диска)
SELECT COUNT(*) FROM orders WHERE status = 'pending';
// Index Scan — проходит по индексу (может быть 20,000 строк)
// Время: ~50-100 ms
SELECT COUNT(*) FROM orders WHERE order_id = 12345;
// (если order_id — primary key)
Почему СУБД выбирает Full Scan
Оптимизатор запросов (Query Planner) решает использовать Full Scan когда:
1. Нет подходящего индекса
// Таблица users: индексы только на id и email
SELECT * FROM users WHERE age > 25; // age НЕ индексирована
// Full Scan
// Решение: создать индекс
CREATE INDEX idx_users_age ON users(age);
SELECT * FROM users WHERE age > 25; // Теперь будет Index Scan
2. Индекс не применим из-за функции
// Индекс на name НЕ может быть использован
SELECT * FROM users WHERE UPPER(name) = 'JOHN';
// Full Scan
// Решение 1: Хранить данные в нужном формате
SELECT * FROM users WHERE name = 'JOHN'; // Точное совпадение, индекс работает
// Решение 2: Функциональный индекс (если БД поддерживает)
CREATE INDEX idx_users_name_upper ON users(UPPER(name));
3. Слишком большой процент строк совпадает
// В таблице users 1 млн строк
// 999,000 из них имеют status = 'active' (99.9%)
SELECT * FROM users WHERE status = 'active';
// Оптимизатор решает:
// - Index Scan: прочитать 999,000 записей через индекс
// - Full Scan: прочитать все 1,000,000 строк
// -> ВЫБИРАЕТ Full Scan (примерно одинаково, но проще)
4. Запрос не может быть оптимизирован
SELECT * FROM users;
// Нужны все строки -> Full Scan (это нормально)
SELECT * FROM users WHERE id != 5;
// Исключение одной строки из 1млн -> Full Scan (выгоднее)
Как обнаружить Full Scan
PostgreSQL — EXPLAIN
EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND created_at > '2024-01-01';
// Результат:
// Seq Scan on orders (Sequential Scan = Full Scan)
// Filter: (user_id = 123) AND (created_at > '2024-01-01')
// Rows: 50000 out of 1000000 — вот это медленно!
EXPLAIN SELECT * FROM orders WHERE order_id = 12345;
// Index Scan using orders_pkey
// Rows: 1 — это быстро
MySQL — EXPLAIN
EXPLAIN SELECT * FROM users WHERE age > 30;
// type: ALL — это Full Scan
// rows: 1000000 — все строки
// Extra: Using where — фильтр применяется к каждой строке
EXPLAIN SELECT * FROM users WHERE id = 5;
// type: const — прямой доступ
// rows: 1 — одна строка
Практические сценарии и решения
Сценарий 1: Поиск по дате
// Плохо — Full Scan
SELECT * FROM orders WHERE DATE(created_at) = '2024-03-23';
// created_at — индексирована, но функция DATE разрушает индекс
// Хорошо — Index Range Scan
SELECT * FROM orders
WHERE created_at >= '2024-03-23' AND created_at < '2024-03-24';
// Индекс используется, только нужные строки
Сценарий 2: Поиск по подстроке
// Плохо — Full Scan
SELECT * FROM products WHERE name LIKE '%phone%';
// Индекс на name не помогает для поиска внутри строки
// Решения:
// 1. Full-text search индекс
SELECT * FROM products WHERE MATCH(name) AGAINST('phone' IN BOOLEAN MODE);
// 2. Специальный индекс (например, GIN индекс в PostgreSQL)
CREATE INDEX idx_products_name_gin ON products USING GIN(to_tsvector('english', name));
Сценарий 3: Фильтрация по нескольким полям
// Плохо — может быть Full Scan
SELECT * FROM orders WHERE status = 'completed' AND total > 1000;
// Если индекс только на status
// Хорошо — составной индекс
CREATE INDEX idx_orders_status_total ON orders(status, total);
SELECT * FROM orders WHERE status = 'completed' AND total > 1000;
// Индекс покрывает оба условия
Java разработчик и Full Scan
// В Java коде (например, с Spring Data JPA)
@Entity
@Table(name = "users", indexes = {
@Index(name = "idx_email", columnList = "email"),
@Index(name = "idx_age", columnList = "age")
})
public class User {
@Id
private Long id;
@Column(indexed = true)
private String email;
private int age; // Индексирован выше через @Index
}
// Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// ХОРОШО — использует индекс
List<User> findByEmail(String email);
// ПЛОХО — может быть Full Scan
List<User> findByAge(int age);
// Почему? Потому что оптимизатор может решить, что Full Scan выгоднее
// ХОРОШО — явное указание
@Query("SELECT u FROM User u WHERE u.age > :age")
@QueryHints(@QueryHint(name = "org.hibernate.cacheable", value = "true"))
List<User> findByAgeGreaterThan(@Param("age") int age);
}
Мониторинг в производстве
// Логирование медленных запросов
// MySQL: slow_query_log, slow_query_log_file
// В логе видим Full Scan:
Query_time: 8.234567 Lock_time: 0.000123 Rows_sent: 50000 Rows_examined: 1000000
// Rows_examined >> Rows_sent = Full Scan!
// PostgreSQL: log_min_duration_statement
query_time: 8234ms
planning_time: 0.234ms
execution_time: 8000ms
Заключение
Full Scan — это признак неэффективного запроса или отсутствия нужных индексов. Как Java разработчик, вы должны:
- Выбирать индексы — индексировать поля, по которым фильтруете
- Анализировать запросы — использовать EXPLAIN для проверки
- Писать правильный SQL — избегать функций на индексированных полях
- Тестировать производительность — на реальных данных и нагрузке
- Мониторить — отслеживать медленные запросы в production
Оптимизация запросов — это постоянная работа для высоконагруженных систем.