Что такое низкая селективность в базе данных?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Селективность в базе данных (Selectivity)
Селективность (Selectivity) — это степень, в какой индекс или запрос сокращает объем данных, подлежащих обработке. Низкая селективность означает, что индекс или запрос возвращает много строк относительно всего объема данных в таблице.
Основной концепт
Селективность выражается как отношение количества возвращаемых строк к общему количеству строк в таблице:
Селективность = (количество возвращаемых строк) / (общее количество строк) * 100%
Примеры:
- Высокая селективность: 1-5% (индекс фильтрует хорошо)
- Низкая селективность: 30-100% (индекс малоэффективен)
Примеры низкой селективности
Пример 1: Индекс на булевом поле
// Таблица с 1 миллионом пользователей
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(100),
is_active BOOLEAN, // индекс на этом поле
email VARCHAR(100)
);
CREATE INDEX idx_is_active ON users(is_active);
// Запрос с низкой селективностью
SELECT * FROM users WHERE is_active = true; // возвращает ~500k строк (50%)
// Индекс малоэффективен:
// - Всего строк: 1,000,000
// - Возвращаемых строк: 500,000
// - Селективность: 50% (очень низкая)
// - БД может вообще не использовать индекс, а сделать полное сканирование
Пример 2: Индекс на статусе заказа
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
customer_id BIGINT,
status VARCHAR(20), -- 'completed', 'pending', 'cancelled'
amount DECIMAL(10,2)
);
CREATE INDEX idx_status ON orders(status);
-- 1 млн заказов
-- Распределение: 60% 'completed', 30% 'pending', 10% 'cancelled'
SELECT * FROM orders WHERE status = 'completed';
-- Возвращает 600,000 строк из 1,000,000
-- Селективность: 60% (низкая)
-- Индекс может быть неэффективен
Пример 3: Индекс на городе в большой таблице
CREATE TABLE customers (
id BIGINT PRIMARY KEY,
name VARCHAR(100),
city VARCHAR(50), -- мало уникальных значений
country VARCHAR(50)
);
CREATE INDEX idx_city ON customers(city);
-- 10 млн клиентов
-- В стране всего 50 городов
-- Каждый город: ~200k клиентов
SELECT * FROM customers WHERE city = 'Moscow';
-- Возвращает 200,000 строк из 10,000,000
-- Селективность: 2% (кажется неплохой)
-- Но все равно много записей!
Высокая селективность (для сравнения)
CREATE TABLE users (
id BIGINT PRIMARY KEY,
email VARCHAR(100) UNIQUE, -- индекс (уникальный)
name VARCHAR(100)
);
-- Всего: 1,000,000 пользователей
SELECT * FROM users WHERE email = 'john@example.com';
-- Возвращает 1 строку из 1,000,000
-- Селективность: 0.0001% (отличная!)
-- Индекс очень эффективен
Как низкая селективность влияет на производительность
При низкой селективности:
-
Индекс может не использоваться вообще
- Оптимизатор решит, что полное сканирование быстрее
- EXPLAIN показывает Sequential Scan, а не Index Scan
-
Больше I/O операций
- Нужно прочитать много блоков данных с диска
- Больше обращений в память
-
Больше обработки в памяти
- Фильтрование большого количества строк
- Сортировка, группировка медленнее
// Пример на Java
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// Низкая селективность - медленно
@Query("SELECT o FROM Order o WHERE o.status = 'COMPLETED'")
List<Order> findCompletedOrders(); // может вернуть 60% заказов
// Высокая селективность - быстро
@Query("SELECT o FROM Order o WHERE o.id = :orderId")
Optional<Order> findById(@Param("orderId") Long orderId); // вернет 1 строку
}
Как оценить селективность
PostgreSQL:
-- Смотреть EXPLAIN
EXPLAIN (ANALYZE) SELECT * FROM orders WHERE status = 'completed';
-- Результат:
-- Seq Scan on orders (cost=0.00..15000.00 rows=600000) <- низкая селективность
-- Filter: (status = 'completed')
-- Vs.
EXPLAIN (ANALYZE) SELECT * FROM users WHERE id = 1;
-- Index Scan using users_pkey (cost=0.29..8.30 rows=1) <- высокая селективность
MySQL:
EXPLAIN SELECT * FROM orders WHERE status = 'completed'\G
-- Результат показывает количество examined rows vs returned rows
Решения для низкой селективности
1. Составной индекс (Composite Index)
-- Плохо: индекс на одном поле
CREATE INDEX idx_status ON orders(status);
SELECT * FROM orders WHERE status = 'completed' AND amount > 1000;
-- Хорошо: индекс на двух полях
CREATE INDEX idx_status_amount ON orders(status, amount);
-- Теперь фильтр более селективен
2. Условный индекс (Partial Index)
-- Индекс только для активных заказов (более селективный)
CREATE INDEX idx_completed_orders ON orders(id)
WHERE status = 'completed';
-- Индекс будет меньше и быстрее
3. Денормализация (Denormalization)
// Вместо частого запроса:
SELECT COUNT(*) FROM orders WHERE status = 'completed';
// Хранить счетчик в отдельной таблице
@Entity
public class OrderStats {
@Id
private String status;
private Long count; // обновляется триггером
}
// Запрос теперь очень быстрый (высокая селективность)
4. Фильтрование на уровне приложения (иногда)
// Плохо: низкая селективность
List<Order> orders = orderRepository.findByStatus("completed");
// Хорошо: более специфичный запрос
List<Order> orders = orderRepository.findByStatusAndAmountGreaterThan(
"completed", BigDecimal.valueOf(1000)
);
5. Партиционирование таблицы
-- Разбить большую таблицу по статусу
CREATE TABLE orders_completed PARTITION OF orders
FOR VALUES IN ('completed');
CREATE TABLE orders_pending PARTITION OF orders
FOR VALUES IN ('pending');
-- Теперь запрос ищет только в нужной партиции
Практический пример анализа
@Service
public class OrderAnalyticsService {
@Autowired
private OrderRepository orderRepository;
// Низкая селективность - может быть медленно
public List<Order> getCompletedOrders() {
// ОБЪЯСНЕНИЕ: статус есть у 60% заказов
// Селективность: 60% - очень низкая
// Возможно, БД вообще не использует индекс
return orderRepository.findByStatus("COMPLETED");
}
// Высокая селективность - быстро
public Order getOrderById(Long id) {
// Селективность: 0.0001%
// БД точно использует PRIMARY KEY индекс
return orderRepository.findById(id).orElse(null);
}
// Средняя селективность - хорошо
public List<Order> getRecentCompletedOrders(LocalDateTime since) {
// Статус = COMPLETED (60%)
// + Дата > since (может быть 10%)
// Итоговая селективность: ~6%
return orderRepository.findByStatusAndCreatedAtAfter(
"COMPLETED", since
);
}
}
Итого
Низкая селективность означает, что индекс или запрос возвращает много строк (30-100%) из общего объема данных. Это снижает эффективность индексов, и БД может вообще их не использовать. Решения: составные индексы, условные индексы, более специфичные запросы, партиционирование. Хорошая селективность — это 1-5% возвращаемых строк.