-
Notifications
You must be signed in to change notification settings - Fork 1
DB Optimization
ํ์ฌ ๋ฌธ์์ ์๋๋ "์ด๋ป๊ฒ ์ต์ ํํ๋๊ฐ"์ ๊ฒฐ๋ก ์ด ์๋๋ผ "๋ฌด์์ ๋ฐ๊ฒฌํ๊ณ , ์ ๊ทธ ๊ฒฐ์ ์ ๋ด๋ ธ๋๊ฐ"์ ๊ณผ์ ์ ๋๋ค. ์ค์ EXPLAIN ANALYZE ๊ฒฐ๊ณผ์ ํธ๋ ์ด๋์คํ ํ๋จ ๊ทผ๊ฑฐ๋ฅผ ๊ทธ๋๋ก ๋จ๊น๋๋ค.
์ธ ๋๊ตฌ๋ฅผ ๋ชฉ์ ์ ๋ฐ๋ผ ๊ตฌ๋ถํ์ต๋๋ค.
WAS ์ฝ์ ๋ก๊ทธ (spring.jpa.show-sql=true): Spring Boot ์คํ ์ค ํฐ๋ฏธ๋์ ์ถ๋ ฅ๋๋ SQL ๋ก๊ทธ์
๋๋ค. N+1์ฒ๋ผ ์์๋ณด๋ค ๋ง์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ๋ณต ์คํ๋๋ ๊ฒ์ ์ฒ์ ๋ฐ๊ฒฌํ ๋ ์ฌ์ฉํ์ต๋๋ค. ๋ณ๋ ์ค์ ์์ด ์ฆ์ ํ์ธํ ์ ์๋ ๊ฐ์ฅ ๋น ๋ฅธ ์๋จ์
๋๋ค.
EXPLAIN ANALYZE: "์ฟผ๋ฆฌ๊ฐ DB ๋ด๋ถ์์ ์ด๋ป๊ฒ ์คํ๋๋๊ฐ"๋ฅผ ๋ณด์ฌ์ค๋๋ค. Seq Scan(Full Scan)์ธ์ง Index Scan์ธ์ง, Sort ๋
ธ๋๊ฐ ๋ฐ์ํ๋์ง, ์ด๋์ ๋น์ฉ์ด ์ง์ค๋๋์ง ํ์ธํฉ๋๋ค. BEGIN ~ ROLLBACK ์์์ ์คํํด ์ค์ ๋ฐ์ดํฐ ๋ณ๊ฒฝ ์์ด ์ธก์ ํฉ๋๋ค.
P6Spy: SQL ์คํ ์ด๋ ฅ์ ํ์ผ๋ก ๊ธฐ๋กํฉ๋๋ค. Grafana + Prometheus์ ์ฐ๊ณํด ์๊ฐ ํ๋ฆ์ ๋ฐ๋ฅธ ์ฟผ๋ฆฌ ํจํด์ ๋ฆฌํฌํธ ํํ๋ก ๋จ๊น๋๋ค. ๊ฐ๋ฐ ๋จ๊ณ์ ์์ ์ฝ์ ๋ก๊ทธ์ ๋ฌ๋ฆฌ ์ด์ ํ๊ฒฝ ๋ชจ๋ํฐ๋ง ์ฉ๋์ ๋๋ค.
์์ฝ ๋ชฉ๋ก API๋ฅผ ํธ์ถํ๋ฉด ๋จ๊ฑด ์กฐํ ์์ฝ ์๋งํผ ์ถ๊ฐ SELECT๊ฐ ํฐ๋ฏธ๋์ ์ถ๋ ฅ๋์ต๋๋ค. Reservation 10๊ฑด์ ์กฐํํ๋ฉด Business 10๋ฒ, Menu 10๋ฒ, BusinessCategory 10๋ฒ, ์ด 30๋ฒ์ ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ์ฐ์ ๋ฐ์ํ์ต๋๋ค.
// Before โ ๊ธฐ๋ณธ findAll ์ดํ ์ฐ๊ด ์ํฐํฐ ์ ๊ทผ๋ง๋ค ์ฟผ๋ฆฌ ๋ฐ์
Page<Reservation> reservations = reservationRepository.findAll(pageable); // ์ฟผ๋ฆฌ 1๋ฒ
for (Reservation r : reservations) {
r.getBusiness().getBusinessName(); // Business ์ฟผ๋ฆฌ N๋ฒ (LAZY ๋ก๋ฉ)
r.getMenu().getServiceName(); // Menu ์ฟผ๋ฆฌ N๋ฒ
r.getMenu().getBusinessCategory(); // BusinessCategory ์ฟผ๋ฆฌ N๋ฒ
}
// ์์ฝ 1,000๊ฑด ์กฐํ โ 1 + 1,000 + 1,000 + 1,000 = 3,001๋ฒ
// ์์ฝ 10๊ฑด ๋จ์ ์ผ์ด์ค๋ โ 1 + 10 + 10 + 10 = 31๋ฒJPA์ ๊ธฐ๋ณธ ์ฐ๊ด๊ด๊ณ ์ ๋ต์ FetchType.LAZY์
๋๋ค. Reservation ์ํฐํฐ๋ฅผ ๋ก๋ํด๋ Business, Menu๋ ํ๋ก์ ์ํ๋ก ๋๊ธฐํ๋ค๊ฐ ์ค์ ํ๋์ ์ ๊ทผํ๋ ์๊ฐ ๊ฐ์ SELECT๋ฅผ ์คํํฉ๋๋ค.
// After โ ์ฐ๊ด ์ํฐํฐ๋ฅผ JOIN์ผ๋ก ํ ๋ฒ์ ๋ก๋
List<Reservation> reservations = queryFactory
.selectFrom(reservation)
.join(reservation.business, business).fetchJoin() // Business ํจ๊ป ๋ก๋
.join(reservation.menu, menu).fetchJoin() // Menu ํจ๊ป ๋ก๋
.join(menu.businessCategory, businessCategory).fetchJoin() // BusinessCategory ํจ๊ป ๋ก๋
.where(builder)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(getOrderSpecifiers(pageable.getSort()))
.fetch();
// ์์ฝ N๊ฑด ์กฐํ โ ์ฟผ๋ฆฌ 1๋ฒ (JOIN์ผ๋ก ๋ชจ๋ ํฌํจ)fetchJoin()์ ์ผ๋ฐ JOIN๊ณผ ๋ค๋ฆ
๋๋ค. ์ผ๋ฐ JOIN์ ์กฐ๊ฑด ํํฐ ์ฉ๋๋ก๋ง ์ฐ๊ณ ์ฐ๊ด ์ํฐํฐ๋ฅผ ์์์ฑ ์ปจํ
์คํธ์ ๋ก๋ํ์ง ์์ต๋๋ค. fetchJoin()์ JOIN ๊ฒฐ๊ณผ๋ฅผ ์ฐ๊ด ์ํฐํฐ๊น์ง ํจ๊ป ์์์ฑ ์ปจํ
์คํธ์ ์ฌ๋ฆฌ๋ฏ๋ก, ์ดํ r.getBusiness() ์ ๊ทผ ์ ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค.
| Before | After | |
|---|---|---|
| ์ฟผ๋ฆฌ ํ์ (์์ฝ 1,000๊ฑด) | 1 + 3,000 = 3,001๋ฒ | 1๋ฒ |
| ์ฟผ๋ฆฌ ํ์ (์์ฝ 10๊ฑด) | 31๋ฒ | 1๋ฒ |
๊ณ ๊ฐ์ฉ findMyReservationsWithFilters, ์
์ฒด์ฉ findBusinessReservationsWithFilters ๋ ์ฟผ๋ฆฌ ๋ฉ์๋ ๋ชจ๋ ๋์ผํ ํจํด์ผ๋ก ์ ์ฉํ์ต๋๋ค. ํ์ด์ง COUNT ์ฟผ๋ฆฌ๋ ์ฐ๊ด ์ํฐํฐ ๋ก๋๊ฐ ํ์ ์์ผ๋ฏ๋ก selectFrom(reservation).where(builder).fetchOne()์ผ๋ก ๋ถ๋ฆฌํด ๋ถํ์ํ JOIN์ ํ์ง ์์ต๋๋ค.
Menu ์ญ์ API๋ฅผ ํธ์ถํ์ ๋ ํฐ๋ฏธ๋์ SELECT ์ฟผ๋ฆฌ๊ฐ ๋ฃจํ๋ก ๋ฐ๋ณต ์ถ๋ ฅ๋์์ต๋๋ค. ๊ฐ ์ฌ๋กฏ๋ง๋ค "์์ฝ์ด ์๋์ง" ํ์ธํ๋ ์ฟผ๋ฆฌ๊ฐ ์ฌ๋กฏ ์๋งํผ ์คํ๋๊ณ ์์์ต๋๋ค.
// Before โ ๋ฃจํ ์์์ ์ฟผ๋ฆฌ ๋ฐ๋ณต
List<BookingSlot> slots = bookingSlotRepository.findByMenuId(menuId); // ์ฟผ๋ฆฌ 1๋ฒ
for (BookingSlot slot : slots) {
boolean hasReservation = reservationRepository.existsByBookingSlot(slot); // ์ฌ๋กฏ๋ง๋ค ์ฟผ๋ฆฌ
if (!hasReservation) {
bookingSlotRepository.delete(slot); // ์ฌ๋กฏ๋ง๋ค DELETE
}
}
// ์ฌ๋กฏ 100๊ฐ โ ์ฟผ๋ฆฌ 101๋ฒ, ์ฝ 2์ด// After โ ์งํฉ ์ฐ์ฐ์ผ๋ก ์ฌ์ค๊ณ
@Transactional(propagation = Propagation.MANDATORY)
public int deleteSlotsForMenu(UUID businessId, UUID menuId) {
// ์ฟผ๋ฆฌ 1: ์ฌ๋กฏ ID ๋ชฉ๋ก ์ ์ฒด ์กฐํ
List<BookingSlot> allSlots = bookingSlotRepository
.findByBusinessIdAndMenuId(businessId, menuId);
if (allSlots.isEmpty()) return 0;
// ์ฟผ๋ฆฌ 2: ์์ฝ์ด ์๋ ์ฌ๋กฏ ID๋ฅผ IN ์ ๋ก ํ ๋ฒ์ ์กฐํ
Set<UUID> occupiedSlotIds = new HashSet<>(
reservationRepository.findSlotIdsWithActiveReservations(
allSlots.stream().map(BookingSlot::getId).toList()
)
);
// ๋ฉ๋ชจ๋ฆฌ ํํฐ๋ง (DB I/O ์์)
List<BookingSlot> deletableSlots = allSlots.stream()
.filter(slot -> !occupiedSlotIds.contains(slot.getId()))
.toList();
// ์ฟผ๋ฆฌ 3: ์ญ์ ๊ฐ๋ฅํ ์ฌ๋กฏ ์ผ๊ด ์ญ์
bookingSlotRepository.deleteAll(deletableSlots);
return deletableSlots.size();
}| Before | After | |
|---|---|---|
| ์ฟผ๋ฆฌ ํ์ | ์ฌ๋กฏ 100๊ฐ โ 101๋ฒ | 3๋ฒ (๊ณ ์ ) |
| ์คํ ์๊ฐ | ์ฝ 2์ด | 50ms ์ดํ |
์ค๊ณ ์์น: Reservation์ ์์ฝ ์์ ์ ์ค๋ ์ท์ด๋ฏ๋ก ์๊ตฌ ๋ณด์กดํฉ๋๋ค. ์์ฝ ๋ ์ฝ๋๊ฐ ํ๋๋ผ๋ ์๋ ์ฌ๋กฏ์ ์ญ์ ํ์ง ์์ผ๋ฉฐ, FK ์ ์ฝ์กฐ๊ฑด์ด DB ๋ ๋ฒจ์์๋ ์ด๋ฅผ ๋ณด์ฅํฉ๋๋ค.
์๋ ๋ฐ์ดํฐ(300๊ฑด)๋ก ๋จผ์ EXPLAIN์ ์คํํ์ต๋๋ค.
-- ๋ ์ง๋ณ ์ฌ๋กฏ ์กฐํ: WHERE business_id = ? AND slot_date = ? AND is_available = true
-- ์ธ๋ฑ์ค ์์, ๋ฐ์ดํฐ 300๊ฑด
Sort (cost=24.41..24.41 rows=1 width=85) (actual time=0.085..0.087 rows=10 loops=1)
Sort Key: start_time
Sort Method: quicksort Memory: 26kB
-> Seq Scan on booking_slot (cost=0.00..24.40 rows=1 width=85) (actual time=0.027..0.076 rows=10 loops=1)
Filter: (is_available AND (business_id = '...') AND (slot_date = ...))
Rows Removed by Filter: 290
Planning Time: 0.091 ms
Execution Time: 0.103 ms์ธ ๊ฐ์ง ๋ฌธ์ ๋ฅผ ํ์ธํ์ต๋๋ค.
Seq Scan + Filter: 300๊ฑด ์ ์ฒด๋ฅผ ์ฝ์ ํ์ ์กฐ๊ฑด์ ๊ฒ์ฌํฉ๋๋ค. 10๊ฑด์ด ํ์ํ๋ฐ 290๊ฑด์ ์ฝ๊ณ ๋ฒ๋ ธ์ต๋๋ค(๋ญ๋น์จ 97%). ๋ฐ์ดํฐ๊ฐ ๋์ด๋ ์๋ก ์ด ๋ญ๋น๊ฐ ์ ํ์ผ๋ก ์ฆ๊ฐํฉ๋๋ค.
Sort ๋
ธ๋ ๋ฐ์: ORDER BY start_time์ ์ฒ๋ฆฌํ๊ธฐ ์ํด quicksort 26KB๋ฅผ ๋ณ๋๋ก ์ฌ์ฉํฉ๋๋ค. ์ธ๋ฑ์ค ์ปฌ๋ผ ์์์ ์ ๋ ฌ ์์๊ฐ ์ผ์นํ๋ฉด ์ด ๋
ธ๋ ์์ฒด๊ฐ ์ฌ๋ผ์ง๋๋ค.
rows=1 ์์ vs 10 ์ค์ : Planner ํต๊ณ๊ฐ ์ค๋๋์ด ์์ธก์ด ํฌ๊ฒ ๋น๋๊ฐ์ต๋๋ค. ANALYZE booking_slot ์คํ์ผ๋ก ํต๊ณ๋ฅผ ๊ฐฑ์ ํ์ต๋๋ค.
-- ์ธ๋ฑ์ค ์ถ๊ฐ ํ, ๋์ผ ์ฟผ๋ฆฌ (300๊ฑด)
Index Scan using idx_booking_slot_business_date_time on booking_slot
(cost=0.28..8.30 rows=1 width=85) (actual time=0.015..0.022 rows=10 loops=1)
Index Cond: ((business_id = '...') AND (slot_date = ...))
Filter: is_available
Buffers: shared hit=6
Planning Time: 0.349 ms
Execution Time: 0.098 msFilter๊ฐ Index Cond๋ก ์ ํ๋์๊ณ Sort ๋
ธ๋๊ฐ ์ฌ๋ผ์ก์ต๋๋ค. is_available๋ง Filter๋ก ๋จ์ ์๋๋ฐ, ์ด๋ ์ธ๋ฑ์ค์ ํฌํจํ์ง ์๊ธฐ๋ก ๊ฒฐ์ ํ์ต๋๋ค. ์ด์ : ๋๋ถ๋ถ์ ์ฌ๋กฏ์ด is_available = true ์ํ์ฌ์ ์ ํ๋๊ฐ ๋ฎ๊ณ , Filter๋ก ์ฒ๋ฆฌํด๋ ์ค๋ฒํค๋๊ฐ ๋ฏธ๋ฏธํฉ๋๋ค. ์ถ๊ฐ ์ INSERT๋ง๋ค ์ธ๋ฑ์ค ๊ฐฑ์ ๋น์ฉ์ด ๋์ด๋๋ ๊ฒ์ด ๋ ๋ถ๋ฆฌํฉ๋๋ค.
๊ธฐ๊ฐ๋ณ ์กฐํ๊ฐ Index Scan์ผ๋ก ์ ๋์ํ๋ ๊ฒ์ ํ์ธํ ๋ค, ๋ฉ๋ด๋ณ ์กฐํ๋ฅผ ์คํํ์ต๋๋ค.
-- ๋ฉ๋ด๋ณ ์ฌ๋กฏ ์กฐํ: WHERE menu_id = ? AND slot_date >= ? AND is_available = true
-- ์ธ๋ฑ์ค: (business_id, slot_date, start_time), ๋ฐ์ดํฐ 300๊ฑด
Sort (cost=20.35..20.36 rows=1 width=85) (actual time=0.276..0.288 rows=300 loops=1)
Sort Key: slot_date, start_time
Sort Method: quicksort Memory: 60kB
-> Seq Scan on booking_slot
Filter: (is_available AND (menu_id = '...') AND (slot_date >= CURRENT_DATE))
Execution Time: 0.320 ms๋ค์ Seq Scan + Sort์
๋๋ค. ์์ธ์ B-tree ์ธ๋ฑ์ค์ Leading ์ปฌ๋ผ ๊ท์น์
๋๋ค. (business_id, slot_date, start_time) ์ธ๋ฑ์ค๋ business_id ์กฐ๊ฑด ์์ด๋ ์ฌ์ฉ์ด ๋ถ๊ฐํฉ๋๋ค. ๋ฉ๋ด๋ณ ์กฐํ๋ menu_id๊ฐ ํํฐ ๊ธฐ์ค์ด๋ฏ๋ก ๊ธฐ์กด ์ธ๋ฑ์ค๊ฐ ์๋ํ์ง ์์ต๋๋ค.
์ธ๋ฑ์ค๋ฅผ ๋ ๊ฐ๋ก ๋ถ๋ฆฌํ๊ธฐ๋ก ๊ฒฐ์ ํ์ต๋๋ค.
@Table(name = "booking_slot", indexes = {
// ํจํด 1: ์
์ฒด ๊ธฐ๋ฐ ์กฐํ (๋ฌ๋ ฅ ํ๋ฉด)
// WHERE business_id = ? AND slot_date = ? or BETWEEN
// ORDER BY slot_date, start_time
@Index(name = "idx_booking_slot_business_date_time",
columnList = "business_id, slot_date, start_time"),
// ํจํด 2: ๋ฉ๋ด ๊ธฐ๋ฐ ์กฐํ (์์ฝ ๊ฐ๋ฅ ์๊ฐ ์กฐํ)
// WHERE menu_id = ? AND slot_date >= ?
// ORDER BY slot_date, start_time
@Index(name = "idx_booking_slot_menu_date_time",
columnList = "menu_id, slot_date, start_time")
})๋จ์ผ ์ธ๋ฑ์ค๋ก ํตํฉ์ด ๋ถ๊ฐํ ์ด์ : (business_id, menu_id, slot_date, start_time) ๊ฐ์ ๊ตฌ์ฑ์ business_id ์๋ ์ฟผ๋ฆฌ์์ ์ฌ์ ํ ์ฌ์ฉ ๋ถ๊ฐ์
๋๋ค. B-tree ์ธ๋ฑ์ค๋ Leading ์ปฌ๋ผ๋ถํฐ ์์๋๋ก ๋งค์นญํด์ผ ํฉ๋๋ค. ๋ ์ฟผ๋ฆฌ ํจํด์ ์ฒซ ๋ฒ์งธ ์กฐ๊ฑด์ด ๋ค๋ฅด๋ฏ๋ก ๋ถ๋ฆฌ๊ฐ ๋ง์ต๋๋ค.
ํธ๋ ์ด๋์คํ: INSERT ์ ์ธ๋ฑ์ค 2๊ฐ๋ฅผ ๊ฐฑ์ ํด์ผ ํ๋ฏ๋ก ์ฐ๊ธฐ ๋น์ฉ์ด ์ฝ 10~20% ์ฆ๊ฐํฉ๋๋ค. BookingSlot์ ์ฌ๋กฏ ์์ฑ ์ ์ผ๊ด INSERTํ๊ณ ์ดํ ์ฃผ๋ก ์ฝ๊ธฐ๋ง ๋ฐ์ํ๋ ํจํด์ด๋ฏ๋ก, ์ฝ๊ธฐ ์ฑ๋ฅ ๊ฐ์ ์ด ์ด ๋น์ฉ์ ์ถฉ๋ถํ ์ ๋นํํฉ๋๋ค.
์ธ๋ฑ์ค ์ถ๊ฐ ํ ์๋ ๊ฒ์ฆ (300๊ฑด)
-- idx_booking_slot_menu_date_time ์ถ๊ฐ ํ
Index Scan using idx_booking_slot_menu_date_time on booking_slot
(cost=0.28..8.30 rows=1 width=85) (actual time=0.026..0.307 rows=300 loops=1)
Index Cond: ((menu_id = '...') AND (slot_date >= CURRENT_DATE))
Filter: is_available
Buffers: shared hit=609
Execution Time: 0.373 msSeq Scan์ด Index Scan์ผ๋ก ์ ํ๋์์ต๋๋ค. ๊ทธ๋ฐ๋ฐ ๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ์ฌ๋ฌ ๋ฒ ์คํํ์ ์คํ ๊ณํ์ด ์ผ์ ํ์ง ์์ ํ์์ด ๋ฐ๊ฒฌ๋์์ต๋๋ค.
ํ๋ ๋ถ์์ ์ฑ ๊ด์ฐฐ (๋์ผ ์ฟผ๋ฆฌ 5ํ ์คํ)
| ์คํ | ํ๋ | Buffers | Execution Time |
|---|---|---|---|
| 1 | Index Scan | 609 | 0.278 ms |
| 2 | Bitmap + Sort | 26 | 0.842 ms |
| 3 | Bitmap + Sort | 24 | 0.244 ms |
| 4 | Bitmap + Sort | 28 | 0.557 ms |
| 5 | Bitmap + Sort | 32 | 0.473 ms |
๊ฐ์ ์ธ๋ฑ์ค๋ฅผ ์ฌ์ฉํ์ง๋ง Planner๊ฐ Index Scan๊ณผ Bitmap Index Scan์ ๋ฒ๊ฐ์ ์ ํํฉ๋๋ค. Bitmap Scan ์ ํ ์ Sort ๋ ธ๋๊ฐ ์ถ๊ฐ๋ก ๋ฐ์ํฉ๋๋ค.
์์ธ์ ๋ฉ๋ชจ๋ฆฌ ์ํ์ ๋๋ค. Index Scan์ ์ธ๋ฑ์ค ์์๋๋ก ์ฝ์ด Sort๊ฐ ๋ถํ์ํ์ง๋ง Buffers๋ฅผ 609ํ์ด์ง ์ฌ์ฉํฉ๋๋ค. Bitmap Scan์ heap ๋ฌผ๋ฆฌ ์์๋ก ์ฝ์ด Buffers๋ฅผ 24~32ํ์ด์ง๋ง ์ฌ์ฉํ์ง๋ง ์ ๋ ฌ ์์๋ฅผ ๋ณด์ฅํ์ง ์์ Sort๊ฐ ์ถ๊ฐ๋ฉ๋๋ค. Planner๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ ์ ๋ฐ๋ผ ๋ ํ๋์ ์ค๊ฐ๋๋ค.
์ฑ๋ฅ ๋ฒ์๋ 0.244~0.842ms์ ๋๋ค. ๋ชฉํ(< 100ms) ๋๋น 100๋ฐฐ ์ด์ ์ฌ์ ๊ฐ ์์ด ํ๋ ๋ถ์์ ์ฑ ์์ฒด๋ ๋ฌธ์ ๊ฐ ์๋๋๋ค. ์คํ๋ ค Planner๊ฐ ๋ฉ๋ชจ๋ฆฌ ์ํ์ ๋ฐ๋ผ ์ ์์ ์ผ๋ก ์ ํํ๊ณ ์๋ค๊ณ ๋ณผ ์ ์์ต๋๋ค.
ํ ์คํธ ๋ฐ์ดํฐ ๊ท๋ชจ ์ฐ์ :
BookingSlot = Reservation(100๋ง) รท ์์ฝ๋ฅ (35%) โ 285๋ง โ 300๋ง
๊ตฌ์ฑ: ์นดํ
๊ณ ๋ฆฌ 15 ร ๋ฉ๋ด 25 ร ์คํํ 6 ร ๊ธฐ๊ฐ 90์ผ ร ํ๋ฃจ 14์ฌ๋กฏ = 2,835,000๊ฑด
์๋์์ ํ์ธํ ์ธ๋ฑ์ค๊ฐ ์ค์ ์๋น์ค ๊ท๋ชจ์์๋ ๋์ํ๋์ง ๊ฒ์ฆํ์ต๋๋ค.
๋ ์ง๋ณ ์กฐํ (300๋ง๊ฑด ์ค 31,500๊ฑด)
-- WHERE business_id = ? AND slot_date = CURRENT_DATE + 7 days
Index Scan using idx_booking_slot_business_date_time on booking_slot
(cost=0.43..40748.90 rows=28968 width=85) (actual time=0.059..6.394 rows=31500 loops=1)
Index Cond: ((business_id = '...') AND (slot_date = ...))
Filter: booking_slot.is_available
Buffers: shared hit=565 read=2
Planning Time: 1.686 ms
Execution Time: 7.256 msIndex Scan์ด ์ ์ง๋ฉ๋๋ค. ๋ฐํ ํ์ด 31,500๊ฑด์ผ๋ก ๋ง์ Execution Time์ด 7.256ms๊น์ง ์ฌ๋ผ๊ฐ์ง๋ง, 300๋ง๊ฑด ํ ์ด๋ธ์์ Index Cond๋ก๋ง ์ฒ๋ฆฌ๋๊ณ ์์ต๋๋ค.
๊ธฐ๊ฐ๋ณ ์กฐํ (300๋ง๊ฑด ์ค 252,000๊ฑด โ Index Scan ์ ํ)
-- WHERE business_id = ? AND slot_date BETWEEN today AND today+7
Index Scan using idx_booking_slot_business_date_time on booking_slot
(cost=0.44..78210.02 rows=232768 width=85) (actual time=0.022..53.318 rows=252000 loops=1)
Index Cond: (business_id = '...' AND slot_date >= CURRENT_DATE AND slot_date <= today+7)
Filter: booking_slot.is_available
Buffers: shared hit=4450
Execution Time: 60.583 ms252,000๊ฑด์ ๋ฐํํ๋ ์ฟผ๋ฆฌ๋ผ 60ms๊ฐ ๋์์ต๋๋ค. Planner๊ฐ ๋ฐํ ํ์ด ๋ง์ ๋๋ Bitmap Heap Scan์ ์ ํํ๊ธฐ๋ ํ์ต๋๋ค.
-- ๋ค๋ฅธ ๋ ์ง๋ก ๋ฐ๋ณต ์กฐํ ์ Bitmap ์ ํ
Bitmap Heap Scan on booking_slot
Heap Blocks: exact=534
Buffers: shared hit=563 read=1
-> Bitmap Index Scan on idx_booking_slot_business_date_time
Index Cond: (business_id = '...' AND slot_date = ...)
Buffers: shared hit=29 read=1
Execution Time: 5.587 ms๊ฐ์ ์ธ๋ฑ์ค๋ฅผ ์ฌ์ฉํ์ง๋ง ๋ฐํ ํ ์์ ์บ์ ์ํ์ ๋ฐ๋ผ Index Scan๊ณผ Bitmap Index Scan์ ์ค๊ฐ๋ ๊ฒ์ ํ์ธํ์ต๋๋ค. ์ค์ํ ๊ฒ์ ๋ ๊ฒฝ์ฐ ๋ชจ๋ Seq Scan์ด ๋ฐ์ํ์ง ์์๋ค๋ ์ ์ ๋๋ค.
๋ฉ๋ด๋ณ ์กฐํ (300๋ง๊ฑด ์ค 2,520๊ฑด)
-- WHERE menu_id = ? AND slot_date >= CURRENT_DATE (idx_booking_slot_menu_date_time ์ถ๊ฐ ํ)
Sort (cost=8263.68..8269.47 rows=2315 width=85) (actual time=5.254..5.352 rows=2520 loops=1)
Sort Key: slot_date, start_time
Sort Method: quicksort Memory: 392kB
-> Bitmap Heap Scan on booking_slot
Recheck Cond: (menu_id = '...' AND slot_date >= CURRENT_DATE)
Filter: booking_slot.is_available
-> Bitmap Index Scan on idx_booking_slot_menu_date_time
Index Cond: (menu_id = '...' AND slot_date >= CURRENT_DATE)
Buffers: shared hit=7
Execution Time: 5.483 msBitmap Index Scan์ผ๋ก ์ ํ๋ฉ๋๋ค. Sort ๋ ธ๋๊ฐ ๋ค์ ๋ฑ์ฅํ๋๋ฐ, Bitmap Scan์ด ๋ฌผ๋ฆฌ์ ์ ์ฅ ์์(heap ์์)๋ก ์ฝ๊ธฐ ๋๋ฌธ์ ์ธ๋ฑ์ค ์ ๋ ฌ ์์๋ฅผ ๋ณด์ฅํ์ง ์์์์ ๋๋ค. Index Scan๊ณผ ๋ฌ๋ฆฌ Bitmap Scan์ Sort๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
ํฅํ ์กฐํ โ LIMIT + Early Termination (300๋ง๊ฑด)
-- WHERE business_id = ? AND slot_date >= CURRENT_DATE AND is_available = true
-- ORDER BY slot_date, start_time LIMIT 50
Limit (cost=0.43..6.40 rows=50 width=85) (actual time=0.050..0.063 rows=50 loops=1)
Buffers: shared hit=5
-> Index Scan using idx_booking_slot_business_date_time on booking_slot
Index Cond: ((business_id = '...') AND (slot_date >= CURRENT_DATE))
Filter: booking_slot.is_available
Buffers: shared hit=5
Planning Time: 1.111 ms
Execution Time: 0.097 ms300๋ง๊ฑด ํ
์ด๋ธ์์ 0.097ms์
๋๋ค. Limit ๋
ธ๋๊ฐ 50๊ฑด์ ์ฐพ๋ ์๊ฐ Index Scan์ ์ค๋จํฉ๋๋ค (Early Termination). ์ธ๋ฑ์ค๊ฐ ์ด๋ฏธ slot_date, start_time ์์๋ก ์ ๋ ฌ๋์ด ์์ผ๋ฏ๋ก ์์์๋ถํฐ 50๊ฑด์ ์ฐพ์ผ๋ฉด ๋ท๋ถ๋ถ์ ์ฝ์ ํ์๊ฐ ์์ต๋๋ค.
LIMIT์ ์ ๊ฑฐํ๊ณ ๋์ผ ์กฐ๊ฑด์ผ๋ก COUNT๋ฅผ ์คํํ๋ฉด ์ฐจ์ด๊ฐ ๊ทน๋ช ํฉ๋๋ค.
-- LIMIT ์์ด ์ ์ฒด ์นด์ดํธ
Finalize Aggregate ... (actual time=1043.910..1055.074 rows=1 loops=1)
Buffers: shared hit=15402 read=23748
-> Gather ...
-> Parallel Seq Scan on booking_slot
Filter: (is_available AND (business_id = '...') AND (slot_date >= CURRENT_DATE))
Rows Removed by Filter: 598500
Execution Time: 1055.186 msPlanner๊ฐ LIMIT ์์ด ๋๋ ๋ฐํ์ด ์์๋๋ ๊ฒฝ์ฐ Parallel Seq Scan์ ์ ํํฉ๋๋ค. ๊ฒฐ๊ณผ๋ 1055ms โ LIMIT 50 ์ฟผ๋ฆฌ(0.097ms)์ ์ฝ 10,000๋ฐฐ ์ฐจ์ด์ ๋๋ค. ๋์ผ ์ธ๋ฑ์ค๊ฐ ์์ด๋ LIMIT ์ ๋ฌด์ ๋ฐ๋ผ ์คํ ์ ๋ต์ด ์์ ํ ๋ฌ๋ผ์ง๋๋ค.
๋น ์ค์ผ์ผ ์์ฝ
| ์กฐํ ํจํด | ๊ฒฐ๊ณผ ํ | ์ธ๋ฑ์ค | Sort | ์คํ ์๊ฐ |
|---|---|---|---|---|
| ๋ ์ง๋ณ (= ์กฐ๊ฑด) | 31,500๊ฑด | business | ์์ | 7.256 ms |
| ๊ธฐ๊ฐ๋ณ (BETWEEN) | 252,000๊ฑด | business | ์์ | 5.587~60.583 ms |
| ๋ฉ๋ด๋ณ (>= ์กฐ๊ฑด) | 2,520๊ฑด | menu | Bitmap์ ๋ฐ์ | 5.483 ms |
| ํฅํ (>= + LIMIT 50) | 50๊ฑด | business | ์์ | 0.097 ms |
-- WHERE business_id = ? AND status = 'PENDING' ORDER BY reservation_date DESC
-- ๋ฐ์ดํฐ 500๊ฑด
Index Scan Backward using idx_reservation_business_date_time on reservation
Index Cond: (business_id = '...')
Filter: ((status)::text = 'PENDING'::text)
Rows Removed by Filter: 40
Buffers: shared hit=1004
Planning Time: 0.195 ms
Execution Time: 0.755 msstatus๊ฐ Filter๋ก ์ฒ๋ฆฌ๋์ด 40๊ฑด์ ๋ฒ๋ฆฝ๋๋ค. ์ธ๋ฑ์ค์ ์ถ๊ฐํ๋ฉด Filter๋ฅผ ์ ๊ฑฐํ ์ ์์ต๋๋ค.
API ํจํด์ ๋ถ์ํ์ต๋๋ค.
-- ํจํด 1: ํน์ ์ํ ์กฐํ (์: PENDING๋ง)
WHERE business_id = ? AND status = 'PENDING'
-- ํจํด 2: ์ฌ๋ฌ ์ํ IN ์กฐ๊ฑด
WHERE business_id = ? AND status IN ('PENDING', 'CONFIRMED')
-- ํจํด 3: ์ํ ๋ฌด๊ด ์ ์ฒด ์กฐํ
WHERE business_id = ?| ํญ๋ชฉ | 3์ปฌ๋ผ ํ์ฌ | 4์ปฌ๋ผ status ์ถ๊ฐ |
|---|---|---|
| ๋จ์ผ ์ํ ์กฐํ | 0.35ms (Filter 40๊ฑด) | 0.33ms (6% ๊ฐ์ ) |
| IN ์กฐ๊ฑด ์กฐํ | 0.35ms | 0.45ms (29% ์ ํ, Sort ๋ฐ์) |
| ์ ์ฒด ์กฐํ | 0.30ms | 0.32ms (7% ์ ํ) |
| INSERT ์ฑ๋ฅ | ๊ธฐ์ค | ~10% ์ ํ |
| UPDATE status ์ | ๋น ๋ฆ | ์ธ๋ฑ์ค ์ฌ๊ตฌ์ฑ ํ์ |
| ์ธ๋ฑ์ค ํฌ๊ธฐ | ๊ธฐ์ค | 71% ์ฆ๊ฐ (varchar ์ถ๊ฐ) |
IN ์กฐ๊ฑด์์ ์คํ๋ ค ๋๋ ค์ง๋ ์ด์ : status IN ('PENDING', 'CONFIRMED')๋ ์ธ๋ฑ์ค๋ฅผ PENDING ๋ฒ์, CONFIRMED ๋ฒ์ ๊ฐ๊ฐ ์ค์บํ ๋ค ๋ณํฉํฉ๋๋ค. Bitmap Index Scan + Bitmap OR ์ฐ์ฐ์ด ๋ฐ์ํ๋ฉฐ, ์ด ๊ณผ์ ์์ ์ธ๋ฑ์ค ์ ๋ ฌ ์์๊ฐ ๊นจ์ ธ Sort ๋
ธ๋๊ฐ ์ถ๊ฐ๋ฉ๋๋ค.
status ๋ณ๊ฒฝ ๋น๋ ๋ฌธ์ : Reservation ์ํ๋ ์๋น์ค ์์ ์ฃผ๊ธฐ ์ ๋ฐ์ ๊ฑธ์ณ ๋ณ๊ฒฝ๋ฉ๋๋ค. PENDING โ CONFIRMED โ COMPLETED ๋๋ PENDING โ CANCELLED ๋ฑ ์ฌ๋ฌ ์ ์ด๊ฐ ๋ฐ์ํ๋ฉฐ, ์ํ ๋ณ๊ฒฝ๋ง๋ค 4์ปฌ๋ผ ์ธ๋ฑ์ค๋ฅผ ์ฌ๊ตฌ์ฑํด์ผ ํฉ๋๋ค.
๊ฒฐ๋ก : 0.04ms(Filter 40๊ฑด ๋น์ฉ)๋ฅผ ์๋ผ๊ธฐ ์ํด IN ์กฐ๊ฑด ์ฑ๋ฅ ์ ํ, ์ฐ๊ธฐ ๋น์ฉ ์ฆ๊ฐ, ์ธ๋ฑ์ค ํฌ๊ธฐ 71% ์ฆ๊ฐ๋ฅผ ๊ฐ์ํ๋ ๊ฒ์ ์ด๋์ด ์์ต๋๋ค. ์ค์ API๊ฐ IN ์กฐ๊ฑด์ผ๋ก ์ฌ๋ฌ ์ํ๋ฅผ ๋์์ ์กฐํํ๋ ๊ฒฝ์ฐ๋ฅผ ์ง์ํด์ผ ํ๋ฏ๋ก ํ์ฌ ์ธ๋ฑ์ค๋ฅผ ์ ์งํ๊ธฐ๋ก ๊ฒฐ์ ํ์ต๋๋ค.
@Table(name = "reservation", indexes = {
// status ์ธ๋ฑ์ค ๋ฏธ์ถ๊ฐ ๊ฒฐ์
// ์ด์ : IN ์กฐ๊ฑด ์ฑ๋ฅ ์ ํ, ์ํ ์ ์ด ๋น๋ฒ, 0.04ms ์ ๊ฐ์ผ๋ก ์ ๋นํ ๋ถ๊ฐ
@Index(name = "idx_reservation_customer_date_time",
columnList = "customer_id, reservation_date DESC, reservation_time DESC"),
@Index(name = "idx_reservation_business_date_time",
columnList = "business_id, reservation_date DESC, reservation_time DESC")
})DESC ๋ช
์: ORDER BY reservation_date DESC์ ์ธ๋ฑ์ค ๋ฐฉํฅ์ด ์ผ์นํ๋ฉด Index Scan Backward๋ก ์ญ์ ์ค์บ๋ง์ผ๋ก ์ ๋ ฌ์ด ์๋ฃ๋ฉ๋๋ค. ๋ณ๋ Sort ๋
ธ๋๊ฐ ์๊ธฐ์ง ์์ต๋๋ค.
-- ๊ณ ๊ฐ ์์ฝ ๋ชฉ๋ก: WHERE customer_id = ? ORDER BY reservation_date DESC (100๋ง๊ฑด)
Sort (cost=389.87..390.12 rows=100 width=182) (actual time=0.308..0.314 rows=100 loops=1)
Sort Key: reservation_date DESC, reservation_time DESC
Sort Method: quicksort Memory: 41kB
Buffers: shared hit=103
-> Bitmap Heap Scan on reservation
Recheck Cond: (customer_id = '...')
Heap Blocks: exact=100
Buffers: shared hit=103
-> Bitmap Index Scan on idx_reservation_customer_date_time
Index Cond: (customer_id = '...')
Buffers: shared hit=3
Execution Time: 0.336 msBitmap Scan + Sort ์กฐํฉ์ด ๋์์ต๋๋ค. ์๋ ์ด์์์๋ Index Scan Backward๋ก Sort๊ฐ ์์๋๋ฐ, 100๋ง๊ฑด ๊ท๋ชจ์์ Planner๊ฐ Bitmap์ผ๋ก ์ ํํ๋ฉด์ Sort๊ฐ ์ถ๊ฐ๋์์ต๋๋ค.
Sort๊ฐ ๋ฐ์ํ์์๋ 0.336ms์ ๋๋ค. Bitmap Scan์ด ํค๋์ heap ์ ๊ทผ์ ์ต์ํํ ๋๋ถ์ ์ ์ฒด ๋น์ฉ์ด ๋ฎ๊ฒ ์ ์ง๋ฉ๋๋ค. Sort ๋ฉ๋ชจ๋ฆฌ๋ 41kB๋ก ์์ต๋๋ค.
๋ฒ์ ์กฐ๊ฑด ์ฟผ๋ฆฌ์์๋ Index Scan์ด ์ ํ๋ฉ๋๋ค.
-- ์ต๊ทผ 30์ผ ์์ฝ: WHERE customer_id = ? AND reservation_date >= CURRENT_DATE - 30
Limit (cost=0.43..16.49 rows=3 width=21) (actual time=0.014..0.024 rows=10 loops=1)
Buffers: shared hit=13
-> Index Scan using idx_reservation_customer_date_time on reservation
Index Cond: ((customer_id = '...') AND (reservation_date >= CURRENT_DATE - 30))
Buffers: shared hit=13
Execution Time: 0.038 ms๋ ์ง ๋ฒ์๋ฅผ ์ถ๊ฐํ๋ฉด ์ธ๋ฑ์ค ํ์ ๊ตฌ๊ฐ ์์ฒด๊ฐ ์ค์ด Index Scan์ด ์ ํ๋๊ณ 0.038ms๊ฐ ๋ฉ๋๋ค. ์ ์ฒด์ธก PENDING ์์ฝ ์กฐํ(๋น ์ค์ผ์ผ)๋ ํ์ธํ์ต๋๋ค.
-- ์
์ฒด PENDING ์์ฝ ๋ชฉ๋ก: WHERE business_id = ? AND status = 'PENDING'
Bitmap Heap Scan on reservation
Recheck Cond: (business_id = '...')
Filter: ((status)::text = 'PENDING'::text)
Heap Blocks: exact=2500
Buffers: shared hit=2506
-> Bitmap Index Scan on idx_reservation_business_date_time
Index Cond: (business_id = '...')
Buffers: shared hit=6
Execution Time: 5.646 msstatus๋ ์๋๊ณผ ๋์ผํ๊ฒ Filter๋ก ์ฒ๋ฆฌ๋ฉ๋๋ค. 100๋ง๊ฑด ๊ท๋ชจ์์๋ Bitmap Index Scan + Filter ๊ตฌ์กฐ๊ฐ ์ ์ง๋๋ฉฐ, 5.646ms๋ ์ด ์กฐ๊ฑด(์ ์ฒด์ ๋ชจ๋ ์์ฝ ์ค PENDING ํํฐ)์์ ํฉ๋ฆฌ์ ์ธ ์์น์ ๋๋ค.
์ ๊ณผ์ ์์ ํ์ธํ ์์น์ ๋๋ค.
1์์: ๋ฑํธ(=) ์กฐ๊ฑด ์ปฌ๋ผ โ ํ์ ์์์ , Leading ์ปฌ๋ผ
2์์: ๋ฒ์(BETWEEN/>=) ์กฐ๊ฑด โ 1์์๋ก ์ขํ ๊ตฌ๊ฐ์์ ๋ฒ์ ํํฐ
3์์: ORDER BY ์ปฌ๋ผ โ ์ธ๋ฑ์ค ์์์ ์ผ์นํ๋ฉด Sort ๋
ธ๋ ์ ๊ฑฐ
โป Leading ์ปฌ๋ผ ์์ด๋ ์ธ๋ฑ์ค ์ฌ์ฉ ๋ถ๊ฐ
โป Bitmap Scan ์ ํ ์ Sort ๋
ธ๋๋ ๋ฐ์ํ ์ ์์
์ธ๋ฑ์ค๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ค๊ณํ๋ ค๋ฉด ์ค์ ์คํ ๊ณํ์ ๋ฐ๋ณต์ ์ผ๋ก ์ธก์ ํด์ผ ํฉ๋๋ค. ๋ฌธ์ ๋ ์ธก์ ๊ณผ์ ์์ฒด์ ๋ง์ฐฐ์ด ๋์ผ๋ฉด ์ธก์ ์ ๊ฑด๋๋ฐ๊ฒ ๋๋ค๋ ๊ฒ์ ๋๋ค. EXPLAIN ANALYZE ๋์ ์ฟผ๋ฆฌ๋ฅผ ๋งค๋ฒ ์์ผ๋ก ๋ถ์ฌ๋ฃ๊ณ , ๋ก๊ทธ์์ ์ง์ ์ฐพ์ ์ ๋ฆฌํ๋ ์์ ์ด ๋ฐ๋ณต๋ ์๋ก ์ธก์ ๋น๋๊ฐ ์ค์ด๋ญ๋๋ค.
์ด ๋ง์ฐฐ์ ์ค์ด๊ธฐ ์ํด "์ธก์ โ ์ถ์ถ โ ๋ถ์" ์ธ ๋จ๊ณ๋ฅผ ๋๊ตฌ๋ก ์๋ํํ์ต๋๋ค.
scripts/query-logging/
โโโ explain-analyze.ps1 โ P6Spy ๋ก๊ทธ์์ SQL ์๋ ์ถ์ถ
โโโ config.json โ ์ถ์ถ ๊ท์น ์ค์ (์ฝ๋ ์์ ์์ด ๋ณ๊ฒฝ ๊ฐ๋ฅ)
โโโ extracted-queries.txt โ ์ถ์ถ ๊ฒฐ๊ณผ (EXPLAIN ANALYZE ๋ฐ๋ก ์คํ ๊ฐ๋ฅ)
โโโ Readme.md
์ธ ๋จ๊ณ ์ํฌํ๋ก์ฐ:
โ ์ธก์ : Integration Test ์คํ
./gradlew test --tests "*IntegrationTest"
โ P6Spy๊ฐ ์คํ๋ SQL ์ ์ฒด๋ฅผ logs/dev/application.log์ ๊ธฐ๋ก
โก ์ถ์ถ: PowerShell ์คํฌ๋ฆฝํธ ์คํ
cd scripts/query-logging
.\explain-analyze.ps1
โ application.log์์ SQL๋ง ํ์ฑ โ extracted-queries.txt ์์ฑ
โข ๋ถ์: IntelliJ / DBeaver์์ EXPLAIN ANALYZE ์คํ
extracted-queries.txt์ ๊ฐ ์ฟผ๋ฆฌ ์์ EXPLAIN ANALYZE ๋ถ์ฌ ์คํ
โ Query Explain Report Template์ผ๋ก Claude์๊ฒ ๋ถ์ ์์ฒญ
{
"extraction": {
"sqlPatterns": [
{ "name": "SELECT", "regex": "SELECT\\s+.*?FROM", "enabled": true },
{ "name": "INSERT", "regex": "INSERT\\s+INTO", "enabled": true }
],
"cleanupRules": [
{ "description": "P6Spy prefix ์ ๊ฑฐ",
"pattern": "^.*?\\|\\s*statement\\s*\\|", "replace": "" },
{ "description": "Hibernate prefix ์ ๊ฑฐ",
"pattern": "^.*?Hibernate:\\s*", "replace": "" }
]
},
"processing": {
"removeDuplicates": true
},
"output": {
"format": {
"includeQueryNumber": true,
"includeQueryType": true
}
}
}์คํฌ๋ฆฝํธ ์ฝ๋๋ฅผ ๊ฑด๋๋ฆฌ์ง ์๊ณ config๋ง ์์ ํด SQL ํจํด ํ์ง ๊ท์น ์ถ๊ฐ, ์ค๋ณต ์ ๊ฑฐ on/off, ์ถ๋ ฅ ํ์ ๋ณ๊ฒฝ์ด ๊ฐ๋ฅํฉ๋๋ค. ์๋ก์ด ๋ก๊ทธ ํฌ๋งท์ด ์ถ๊ฐ๋์ ๋๋ cleanupRules์ ํ ์ค ์ถ๊ฐ๋ก ๋์ํฉ๋๋ค.
EXPLAIN ANALYZE ๊ฒฐ๊ณผ๋ฅผ Claude์๊ฒ ๋ถ์์ํค๋ ํ๋กฌํํธ ํ
ํ๋ฆฟ์ Query_Explain_Report_Template.md๋ก ๊ด๋ฆฌํฉ๋๋ค.
ํต์ฌ ์์น:
- EXPLAIN ๊ฒฐ๊ณผ ๊ฐ ์ค๋ง๋ค ๋ฐ๋ก ์๋์ ํด์ (์ฅํฉํ ํ ๊ธ์ง)
- ๋ฌธ์ ์ 3~5๊ฐ๋ง ๋ช
ํํ ์๋ณ
- ๊ฐ์ ์์ ์คํ ๊ฐ๋ฅํ ์ฝ๋(@Index ์ด๋
ธํ
์ด์
)๋ก ์ ์
์ค์ ์ฌ์ฉ ์์:
[์ฌ์ฉ์] EXPLAIN ANALYZE ๊ฒฐ๊ณผ๋ฅผ ๋ถ์ฌ๋ฃ๊ณ ํ
ํ๋ฆฟ ์์ฒญ
โ Seq Scan ๋ฐ์, Rows Removed ๋น์จ, Sort ๋
ธ๋ ๋ฐ์ ์ฌ๋ถ ์๋ณ
โ ์ธ๋ฑ์ค ์ปฌ๋ผ ์์ ์ ์ + @Index ์ฝ๋ ์ ์
โ ์์ Before/After ์๊ฐ ๋น๊ต ํ
์ด๋ธ
[์ฌ์ฉ์] ์ธ๋ฑ์ค ์ ์ฉ ํ ์ฌ์ธก์ ๊ฒฐ๊ณผ๋ฅผ ๋ค์ ๋ถ์ฌ๋ฃ๊ธฐ
โ ๊ฐ์ ํ์ธ ๋๋ ์ถ๊ฐ ํ๋ ํฌ์ธํธ ๋์ถ
์ด ๋ฌธ์ 3~5์ ์ ์ธ๋ฑ์ค ์ค๊ณ ๊ณผ์ โ Seq Scan ๋ฐ๊ฒฌ, ์๋ ๊ฒ์ฆ, ๋น ์ค์ผ์ผ ๊ฒ์ฆ, ํ๋ ๋ถ์์ ์ฑ ๋ถ์ โ ์ ๋ชจ๋ ์ด ์ํฌํ๋ก์ฐ๋ฅผ ๋ฐ๋ณต ์คํํ ๊ฒฐ๊ณผ์ ๋๋ค.
์์คํ
๋ฐฑ์๋ ํฌํธํด๋ฆฌ์ค