SELECT * FROM PLAYER WHERE NAME = ?
CREATE INDEX PLAYER_NAME_INDEX ON USER(NAME);
NAME
컬럼에 대한 Index가 존재한다면, 예시 쿼리를 수행할 때 테이블 전체를 탐색하지 않고 해당 Index를 바탕으로 원하는 데이터의 위치를 빠르게 검색한다.@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(indexes = {@Index(name = "idx_customer_order", columnList = "customerId, createDate DESC")})
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long customerId;
@Column(nullable = false)
private LocalDateTime createDate;
}
@Table
어노테이션은 Order
엔티티가 데이터베이스 내에서 어떤 테이블과 매핑될 것인지를 지정한다.indexes
속성을 통해, customerId
와 createDate
컬럼을 기준으로 내림차순 정렬된 인덱스 idx_customer_order
를 생성하도록 한다.@SpringBootTest
class OrderRepositoryTest {
@Autowired
private OrderRepository orderRepository;
@BeforeAll
static void setUp(@Autowired OrderRepository orderRepository) {
Random random = new Random();
// 주문 데이터 100만 건 생성 및 저장
for (int i = 0; i < 1000000; i++) {
Long customerId = (long) (random.nextInt(100) + 1); // 1부터 100 사이의 고객 ID
LocalDateTime createDate = LocalDateTime.now().minusDays(random.nextInt(365 * 2)); // 지난 2년간 랜덤 날짜
orderRepository.save(
Order.builder()
.customerId(customerId)
.createDate(createDate)
.build()
);
}
}
@Test
void performanceTestWithIndex() {
StopWatch stopWatch = new StopWatch();
// 특정 고객의 최근 주문 조회
Long targetCustomerId = 100L;
stopWatch.start();
orderRepository.findByCustomerIdOrderByCreateDateDesc(targetCustomerId);
stopWatch.stop();
System.out.println("Query time with index: " + stopWatch.getTotalTimeSeconds() + " seconds");
System.out.println(stopWatch.prettyPrint());
}
}
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(indexes = {@Index(name = "idx_customer_order", columnList = "customerId")})
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long customerId;
@Column(nullable = false)
private LocalDateTime createDate;
}
@SpringBootTest
class OrderRepositoryTest2 {
@Autowired
private OrderRepository orderRepository;
@BeforeAll
static void setUp(@Autowired OrderRepository orderRepository) {
Random random = new Random();
// 주문 데이터 10만 건 생성 및 저장
for (int i = 0; i < 100000; i++) {
LocalDateTime orderDate = LocalDateTime.now().minusDays(random.nextInt(365 * 2)); // 지난 2년간 랜덤 날짜
orderRepository.save(
Order.builder()
.customerId((long) (i))
.createDate(orderDate)
.build()
);
}
}
@Test
void performanceTestWithIndex() {
StopWatch stopWatch = new StopWatch();
// 특정 고객의 최근 주문 조회
Long targetCustomerId = 100L;
stopWatch.start();
orderRepository.findByCustomerId(targetCustomerId);
stopWatch.stop();
System.out.println("Query time with index: " + stopWatch.getTotalTimeSeconds() + " seconds");
System.out.println(stopWatch.prettyPrint());
}
}
인덱스는 항상 최신의 정렬된 상태를 유지해야 한다. 이를 위해 DBMS는 인덱스에 대한 삽입, 삭제, 갱신 연산 시 추가 작업을 수행해야 하며, 이 과정에서 오버헤드가 발생한다.
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long customerId;
@Column(nullable = false)
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createDate;
@Column(nullable = false)
private Double amount;
@Column(nullable = false, length = 20)
private String status;
}
@Index(name = "idx_customerId", columnList = "customerId")
SELECT * FROM Order WHERE customerId = :customerId;
@Index(name = "idx_createDate", columnList = "createDate DESC")
SELECT * FROM Order ORDER BY orderDate DESC;
@Index(name = "idx_customerId", columnList = "customerId")
SELECT Order.*, Customer.name FROM Order JOIN Customer ON Order.customerId = Customer.id;
@Id
어노테이션을 통해 이미 유니크 인덱스가 자동으로 생성된다.SELECT * FROM Order WHERE id = :orderId;
@Index(name = "idx_status", columnList = "status")
SELECT * FROM Order WHERE status = 'pending';