๋ฐ˜์‘ํ˜•

 

๋งค์ผ๋ฉ”์ผ ๋ฐฑ์—”๋“œ ์งˆ๋ฌธ์„ ์ฐธ๊ณ ํ•ด ๊ฐœ์ธ์ ์œผ๋กœ ํ•™์Šตํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.
์˜ค๋ฅ˜๊ฐ€ ์žˆ๋‹ค๋ฉด ์–ธ์ œ๋“  ํ”ผ๋“œ๋ฐฑ ์ฃผ์‹œ๋ฉด ๋ฐ”๋กœ ๋ฐ˜์˜ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค..!

 

์˜ˆ์‹œ ์ฟผ๋ฆฌ

 

SELECT p
FROM Post p
WHERE p.id NOT IN :postIds

 

์œ„ ์ฟผ๋ฆฌ๋Š” ํŠน์ • ID ๋ชฉ๋ก(:postIds)์— ํฌํ•จ๋˜์ง€ ์•Š์€ ๊ฒŒ์‹œ๊ธ€(Post)๋ฅผ ์กฐํšŒํ•˜๋Š” ์•„์ฃผ ์ง๊ด€์ ์ธ ๋ฐฉ์‹์ž„

 

๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์ 

 

1. ์ „์ฒด ํ…Œ์ด๋ธ” ์Šค์บ” (Full Table Scan), ์ธ๋ฑ์Šค ํ’€ ์Šค์บ”(Full Index Scan)

 

NOT IN์€ ๋ถ€์ • ์กฐ๊ฑด์ด๋ผ์„œ DB ์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ ํšจ์œจ์ ์ธ ์‹คํ–‰ ๊ณ„ํš์„ ์„ธ์šฐ๊ธฐ ์–ด๋ ค์›€

 

=> ์ „์ฒด ํ…Œ์ด๋ธ” ์Šค์บ”์ด๋‚˜ ์ธ๋ฑ์Šค ํ’€ ์Šค์บ”์„ ์œ ๋ฐœํ•จ

 

DB ์˜ตํ‹ฐ๋งˆ์ด์ €๋Š” SQL ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ, ๊ฐ€์žฅ ๋น ๋ฅด๊ณ  ํšจ์œจ์ ์ธ ์‹คํ–‰ ๊ณ„ํš์„ ์„ธ์šฐ๋Š” DB์˜ ๋‘๋‡Œ๊ฐ™์€ ์—ญํ• ์ž„

DB๋Š” ๋‹จ์ˆœํžˆ ์ฟผ๋ฆฌ๋ฅผ ๊ทธ๋Œ€๋กœ ์‹คํ–‰ํ•˜์ง€ ์•Š๊ณ  ๋‚ด๋ถ€์ ์œผ๋กœ ์ด๊ฑธ ์–ด๋–ค ์ˆœ์„œ๋กœ, ์–ด๋–ค ์ธ๋ฑ์Šค๋ฅผ ์จ์„œ ์‹คํ–‰ํ• ๊นŒ๋ฅผ ๊ณ„์‚ฐํ•˜๋Š”๋ฐ 
์ด ๊ณผ์ •์„ Query Optimizer๊ฐ€ ์ˆ˜ํ–‰ํ•จ

NOT IN์€ ์ „์ฒด ๋ฐ์ดํ„ฐ๋‚˜ ํ…Œ์ด๋ธ”์„ ์Šค์บ”ํ•œ ํ›„ ์กฐ๊ฑด์— ๋งž์ง€ ์•Š๋Š” ๋ ˆ์ฝ”๋“œ๋ฅผ ํ•„ํ„ฐ๋ง ํ•˜๋Š” ๋ถ€์ • ์กฐ๊ฑด์ด๊ธฐ์— DB ์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ ํšจ์œจ์ ์ธ ์‹คํ–‰ ๊ณ„ํš์„ ์„ธ์šฐ๊ธฐ ์–ด๋ ค์›€

 

2. ์ธ๋ฑ์Šค ํ™œ์šฉ ์–ด๋ ค์›€

IN์€ ์ธ๋ฑ์Šค Range Scan์„ ํ†ตํ•ด ๋น ๋ฅด๊ฒŒ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, NOT IN์€ ์ธ๋ฑ์Šค ํ™œ์šฉ๋„๊ฐ€ ํ˜„์ €ํžˆ ๋–จ์–ด์ง
(NOT IN์€ ์ธ๋ฑ์Šค ๋ณด๋‹ค ๋А๋ฆฐ Full Scan์ด๋‚˜ Full Index Scan์„ ์œ ๋ฐœํ•˜๊ธฐ ๋•Œ๋ฌธ)

 

3. NULL ํฌํ•จ ์‹œ ์˜๋„์น˜ ์•Š์€ ๊ฒฐ๊ณผ

 

NULL ๊ฐ’ ์ฒ˜๋ฆฌ ๋กœ์ง์œผ๋กœ ์ธํ•œ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ

 

=> NULL ๊ณผ์˜ ๋น„๊ต๋Š” UNKNOWN์ด ๋˜์–ด ๋ถˆ์ผ์น˜๋กœ ๊ฐ„์ฃผ๋˜๊ธฐ ๋•Œ๋ฌธ์— column NOT IN (1, 2, NULL) ์€ ํ•ญ์ƒ ๋นˆ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•จ

 

4. ๋งŽ์€ ๊ฐ’์ด ๋“ค์–ด์˜ฌ ๋•Œ ์˜ค๋ฒ„ํ—ค๋“œ

 

์œ„์˜ ์˜ˆ์‹œ ์ฟผ๋ฆฌ์—์„œ :postIds์— ์ˆ˜์ฒœ ๊ฐœ์˜ ID๊ฐ€ ๋“ค์–ด์˜ค๋ฉด ํŒŒ์‹ฑ, ์ตœ์ ํ™”, ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ์ธก๋ฉด์—์„œ ๋ถ€๋‹ด์ด ์ปค์ง


(๋Œ€๋Ÿ‰์˜ ๊ฐ’์„ IN ์ ˆ์— ๋„ฃ์œผ๋ฉด ์‹คํ–‰ ๊ณ„ํš ์ƒ์„ฑ์ด ๋Š˜์–ด๋‚˜๊ณ , ํŒŒ์‹ฑ ๋ฐ ์ตœ์ ํ˜ธ ๋‹จ๊ณ„์—์„œ ์ถ”๊ฐ€์ ์ธ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋ฐœ์ƒํ•จ)

 

์ตœ์ ํ™” ๋ฐฉ์•ˆ

 

NOT EXISTS ์‚ฌ์šฉ

 

SELECT p 
FROM Post p
WHERE NOT EXISTS (
    SELECT 1 
    FROM Post temp
    WHERE temp.id = p.id AND temp.id IN :postIds
)

 

NOT EXISTS๋Š” ํ–‰ ๋‹จ์œ„๋กœ ์กฐ๊ฑด์„ ๊ฒ€์‚ฌํ•˜๊ณ , ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋ฉด(๋งค์นญ๋˜๋Š” ์ฒซ ํ–‰์„ ์ฐพ์ž๋งˆ์ž) ๋ฐ”๋กœ exitํ•จ

=> DBMS๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Œ ์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ํŠน๋ณ„ํžˆ ์ตœ์ ํ™”๋œ ๋ฐฉ์‹์ž„

 

์ด๋Š” ์ธ๋ฑ์Šค ํ™œ์šฉ์ด ๋” ์œ ๋ฆฌํ•˜๊ณ , ํ™•์žฅ์„ฑ ์ข‹์€ ์ฟผ๋ฆฌ์ด๋ฉฐ NULL ๊ฐ’์ด ํฌํ•จ๋˜์–ด๋„ ์ •ํ™•ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๋จ
(๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ์…‹์—์„œ ๊ฐ€์žฅ ์•ˆ์ •์ ์ด๊ณ  ํ™•์žฅ์„ฑ ์žˆ๋Š” ์„ฑ๋Šฅ์„ ์ œ๊ณตํ•จ)

 

LEFT JOIN + IS NULL ์‚ฌ์šฉ

 

SELECT p 
FROM Post p 
LEFT JOIN (
    SELECT temp.id 
    FROM Post temp 
    WHERE temp.id IN :postIds
) filtered ON p.id = filtered.id
WHERE filtered.id IS NULL

 

์ž‘๋™ ๋ฐฉ์‹์„ ์‚ดํŽด๋ณด๋ฉด Post ํ…Œ์ด๋ธ”์—์„œ ์กฐ๊ฑด์— ๋งž๋Š” ID๋“ค๋งŒ ์ถ”๋ฆฐ ์„œ๋ธŒ์ฟผ๋ฆฌ filtered๋ฅผ ๋งŒ๋“ค๊ณ  LEFT JOINํ•จ

 

์ด ๋•Œ p.id๊ฐ€ :postIds์— ํฌํ•จ๋˜๋ฉด ๋งค์นญ๋˜๋Š” filtered.id๊ฐ€ ์žˆ๊ณ  NULL์ด ์•„๋‹ˆ์ง€๋งŒ ๋งŒ์•ฝ p.id๊ฐ€ :postIds์— ์—†์œผ๋ฉด ๋งค์นญ์ด ๋˜์ง€ ์•Š๊ณ  filtered.id๊ฐ€ NULL์ด ๋  ๊ฒƒ์ž„

 

=> filtered.id์— IS NULL ์กฐ๊ฑด์„ ์‚ฌ์šฉํ•˜์—ฌ :postIds์— ํฌํ•จ๋˜์ง€ ์•Š์€ p.id๋งŒ ๋‚จ๊น€

 

 

์„œ๋ธŒ์ฟผ๋ฆฌ๊ฐ€ ์ž‘์„ ๋•Œ ์•„์ฃผ ๋น ๋ฅด๊ณ 
(JOIN ๋Œ€์ƒ์ด ์ž‘์•„์ง€๋ฉด JOIN ์—ฐ์‚ฐ ์‹œ ๋น„๊ตํ•  row ์ˆ˜๊ฐ€ ์ค„์–ด๋“ค์–ด ์ฒ˜๋ฆฌ ์†๋„๊ฐ€ ๋นจ๋ผ์ง)

 

JOIN์— PK ์ธ๋ฑ์Šค๊ฐ€ ์žˆ์œผ๋ฉด ์ตœ์ ํ™” ํšจ๊ณผ๊ฐ€ ํผ

 

PK๋Š” ๋ฌด์กฐ๊ฑด ์œ ์ผํ•œ ๊ฐ’์ด 1๊ฐœ์ด๋ฏ€๋กœ JOIN, WHERE ์กฐ๊ฑด์—์„œ ์ •ํ™•ํžˆ 1๊ฑด(์ตœ๋Œ€ 1๊ฐœ)๋งŒ ์ฐพ์œผ๋ฉด ๋๋‚จ
=> ์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ ๊ฐ€์žฅ ๋น ๋ฅธ ์•Œ๊ณ ๋ฆฌ์ฆ˜(Index Seek) ์„ ํƒ ๊ฐ€๋Šฅ

์ผ๋ฐ˜ ์ธ๋ฑ์Šค๋Š” ์ค‘๋ณต ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์–ด์„œ ๊ฐ™์€ ์กฐ๊ฑด์— ํ•ด๋‹นํ•˜๋Š” ๋ ˆ์ฝ”๋“œ๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ์ฐพ์•„์•ผ ํ•  ์ˆ˜ ๋„ ์žˆ์Œ
=> ์˜ตํ‹ฐ๋งˆ์ด์ €๋Š” ์ถ”๊ฐ€๋กœ ํ…Œ์ด๋ธ” ์ ‘๊ทผ(lookup) ํ•„์š”

 

๋งŒ์•ฝ postIds๊ฐ€ ๋งŽ๋‹ค๋ฉด?

 

  • SQL ์ฟผ๋ฆฌ ๊ธธ์ด ์ œํ•œ ๋ฌธ์ œ

  • IN ์•ˆ์— ์ˆ˜ ์ฒœ ๊ฐœ ๊ฐ’์ด ๋“ค์–ด๊ฐ€๋ฉด ์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ ์ฒ˜๋ฆฌ ์ „๋žต์„ ์ •ํ•˜๊ธฐ ํž˜๋“ค์–ด์ง€๋ฏ€๋กœ Full Table Scan์„ ์„ ํƒํ•˜๊ฑฐ๋‚˜ ํŒŒ์‹ฑ ์†๋„ ๋ฐ ๋„คํŠธ์›Œํฌ ํŠธ๋ž˜ํ”ฝ ์ฆ๊ฐ€๊ฐ€ ์ƒ๊น€

  • Hibernate๋Š” IN์ ˆ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์œผ๋ฉด ์˜ˆ์™ธ ๋ฐœ์ƒํ•จ
    (QueryParameterBindingException)

 

์œ„์™€ ๊ฐ™์€ ๋ฌธ์ œ๋“ค์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ์—

 

 

์ž„์‹œ ํ…Œ์ด๋ธ”์— ์ €์žฅ + JOIN

 

-- 1. postIds๋ฅผ ์ž„์‹œ ํ…Œ์ด๋ธ”(PostIdsTemp)์— insert
-- 2. JOIN์œผ๋กœ ์ฒ˜๋ฆฌ
SELECT p.*
FROM Post p
JOIN PostIdsTemp pid ON p.id = pid.id

 

์•„์˜ˆ ์ž„์‹œ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•˜๊ณ  JOIN์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๋” ์•ˆ์ „ํ•  ์ˆ˜ ์žˆ์Œ

(JOIN์€ ์ธ๋ฑ์Šค๋ฅผ ํƒˆ ์ˆ˜ ์žˆ์–ด ๋น ๋ฅด๊ณ  postIds ๊ฐœ์ˆ˜์— ์ œํ•œ์ด ์—†์Œ)

 

๋ฐฐ์น˜ ์ฒ˜๋ฆฌ

 

// postIds๊ฐ€ 5000๊ฐœ๋ฉด โ†’ 1000๊ฐœ์”ฉ 5๋ฒˆ ๋‚˜๋ˆ ์„œ 5๋ฒˆ ์ฟผ๋ฆฌ ์‹คํ–‰
for (List<Long> batch : batchSplit(postIds, 1000)) {
    query.setParameter("postIds", batch).getResultList();
}

 

Hibernate/JPA์—์„œ๋Š” IN/NOT IN ์‚ฌ์šฉ ์‹œ ์ปฌ๋ ‰์…˜์ด ๋„ˆ๋ฌด ํฌ๋ฉด ์ฟผ๋ฆฌ๋ฅผ ๋‚˜๋ˆ ์„œ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ๋ฅผ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์Œ

 

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฟผ๋ฆฌ ๊ธธ์ด ์ œํ•œ์„ ์šฐํšŒํ•  ์ˆ˜ ์žˆ๊ณ  Hibernate ๋‚ด๋ถ€ ์ œ์•ฝ๋„ ํ”ผํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ ํด๋ผ์ด์–ธํŠธ-์„œ๋ฒ„ ๊ฐ„ ํ†ต์‹  ํšŸ์ˆ˜๊ฐ€ ๋Š˜์–ด๋‚˜๋ฏ€๋กœ ์ด ์‘๋‹ต ์‹œ๊ฐ„์— ์ฃผ์˜ํ•ด์•ผํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์ง์ ‘ merge/์ค‘๋ณต ์ œ๊ฑฐ ํ•ด์•ผ๋  ์ˆ˜ ๋„ ์žˆ์Œ

 

Native SQL๋กœ ์ง์ ‘ ์ฒ˜๋ฆฌ

 

@Query(value = "SELECT * FROM post p WHERE p.id IN (SELECT id FROM temp_ids)", nativeQuery = true)

 

 

JPA์˜ JPQL์€ ๋‚ด๋ถ€์ ์œผ๋กœ ์•ˆ์ „์žฅ์น˜๊ฐ€ ๋งŽ๊ณ , ๋ณต์žกํ•œ ์ตœ์ ํ™”๋ฅผ ํ•˜๊ธฐ ์–ด๋ ต๊ธฐ์— ์„ฑ๋Šฅ์ด ์ค‘์š”ํ•œ ์ฟผ๋ฆฌ๋Š” ์•„์˜ˆ Native SQL๋กœ ์ง์ ‘ ์ž‘์„ฑํ•˜์—ฌ ์ตœ์ ํ™”ํ•˜๋Š” ๊ฒƒ๋„ ๊ณ ๋ คํ•ด ๋ณด๋Š”๊ฒŒ ์ข‹์Œ

๋ฐ˜์‘ํ˜•