Если создан составной индекс по трем полям, будет ли он использоваться при выполнении запроса, который включает только второе поле из этого индекса
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Использование составного индекса при запросе только ко второму полю
Это классический вопрос о внутреннем устройстве индексов базы данных. Ответ зависит от левого префикса индекса (left prefix rule), который применяется в большинстве SQL БД.
Краткий ответ: НЕ будет использован
Если создан составной индекс по полям (A, B, C), то индекс будет использован в следующих случаях:
- Поиск по A
- Поиск по A и B
- Поиск по A, B и C
- Поиск по A с ORDER BY B
Однако индекс НЕ будет использован для:
- Поиска только по B
- Поиска только по C
- Поиска по B и C (без A)
Почему работает левый префикс?
Составной индекс в базе данных устроен как B-tree структура, где сначала сортируются по первому полю, потом внутри каждого значения первого поля - по второму, и так далее:
Индекс (user_id, status, created_at):
user_id=1
├─ status='active'
│ ├─ created_at='2024-01-01'
│ ├─ created_at='2024-01-02'
│ └─ created_at='2024-01-03'
└─ status='inactive'
├─ created_at='2024-02-01'
└─ created_at='2024-02-02'
user_id=2
├─ status='active'
└─ status='inactive'
Примеры с SQL
-- Создаём составной индекс
CREATE INDEX idx_user_status_date ON orders (user_id, status, created_at);
-- БУДЕТ использован индекс (поиск по user_id)
SELECT * FROM orders WHERE user_id = 5;
-- ✓ Index seek/scan по user_id
-- БУДЕТ использован индекс (поиск по user_id и status)
SELECT * FROM orders WHERE user_id = 5 AND status = 'active';
-- ✓ Index seek по user_id, потом фильтр по status
-- НЕ будет использован индекс (поиск только по status)
SELECT * FROM orders WHERE status = 'active';
-- ✗ Full table scan (индекс не помогает)
-- НЕ будет использован индекс (поиск по 2-му и 3-му полю)
SELECT * FROM orders WHERE status = 'active' AND created_at > '2024-01-01';
-- ✗ Full table scan
-- БУДЕТ использован индекс (поиск по первому полю с сортировкой)
SELECT * FROM orders WHERE user_id = 5 ORDER BY status, created_at;
-- ✓ Index seek + используем индекс для сортировки
Примеры с Java/JPA
@Entity
@Table(name = "orders")
@Index(name = "idx_user_status_date", columnList = "user_id, status, created_at")
public class Order {
@Id
private Long id;
@Column(name = "user_id")
private Long userId;
@Column(name = "status")
private String status;
@Column(name = "created_at")
private LocalDateTime createdAt;
}
// Repository с методами
public interface OrderRepository extends JpaRepository<Order, Long> {
// ✓ Использует индекс (LEFT PREFIX)
List<Order> findByUserId(Long userId);
// ✓ Использует индекс
List<Order> findByUserIdAndStatus(Long userId, String status);
// ✓ Использует индекс
List<Order> findByUserIdAndStatusAndCreatedAtAfter(
Long userId, String status, LocalDateTime createdAt);
// ✗ НЕ использует индекс (нарушает левый префикс)
List<Order> findByStatus(String status);
// ✗ НЕ использует индекс
List<Order> findByStatusAndCreatedAtAfter(String status, LocalDateTime createdAt);
}
Проверка в реальной БД (PostgreSQL)
// Получение плана запроса
public class QueryPlanAnalyzer {
public void analyzeQueryPlan(DataSource dataSource) throws SQLException {
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement()) {
// Запрос, который использует индекс
String query1 = "EXPLAIN SELECT * FROM orders WHERE user_id = 5";
ResultSet rs1 = stmt.executeQuery(query1);
System.out.println("Query 1 (с user_id):";
while (rs1.next()) {
System.out.println(rs1.getString(1)); // Index Scan или Seq Scan
}
// Запрос, который НЕ использует индекс
String query2 = "EXPLAIN SELECT * FROM orders WHERE status = 'active'";
ResultSet rs2 = stmt.executeQuery(query2);
System.out.println("\nQuery 2 (только status):";
while (rs2.next()) {
System.out.println(rs2.getString(1)); // Seq Scan (full table)
}
}
}
}
Практические следствия
public class OrderService {
@Autowired
private OrderRepository orderRepository;
// ХОРОШО - индекс работает
public List<Order> getActiveOrders(Long userId) {
return orderRepository.findByUserIdAndStatus(userId, "active");
}
// ПЛОХО - индекс не работает, нужен отдельный индекс по status
public List<Order> getAllActiveOrders() {
return orderRepository.findByStatus("active");
}
// ХОРОШО - если часто ищешь по status, создай отдельный индекс
// @Index(name = "idx_status", columnList = "status")
}
Оптимизация
Если часто ищешь только по второму полю, нужно создать отдельный индекс:
-- Исходный составной индекс
CREATE INDEX idx_user_status_date ON orders (user_id, status, created_at);
-- Дополнительный индекс для поиска по status
CREATE INDEX idx_status ON orders (status);
-- Теперь оба запроса используют индексы
SELECT * FROM orders WHERE user_id = 5; -- ✓ idx_user_status_date
SELECT * FROM orders WHERE status = 'active'; -- ✓ idx_status
Итоговая таблица
| Составной индекс | Запрос | Используется? | Примечание |
|---|---|---|---|
| (A, B, C) | WHERE A | ✓ | LEFT PREFIX |
| (A, B, C) | WHERE A AND B | ✓ | LEFT PREFIX |
| (A, B, C) | WHERE A AND B AND C | ✓ | LEFT PREFIX |
| (A, B, C) | WHERE B | ✗ | Нарушен префикс |
| (A, B, C) | WHERE B AND C | ✗ | Нарушен префикс |
| (A, B, C) | WHERE C | ✗ | Нарушен префикс |
| (A, B, C) | WHERE A AND C | ⚠️ | Частично (только по A) |
Вывод
Составной индекс работает по принципу левого префикса: для его использования запрос ДОЛЖЕН содержать условия, начиная с первого поля индекса. Если запрос содержит только второе поле, индекс не будет использован и произойдёт полный scan таблицы. При разработке индексной стратегии важно понимать, какие запросы будут наиболее часто выполняться, и создавать индексы с учётом этого.