본문으로 건너뛰기
김신건의 로그

UNION vs OR vs IN 벤치마크 (3), 전체 데이터와 실무 권고

PostgreSQL 3버전 + MySQL 3버전의 전체 측정 데이터와, 이 결과를 실무에 어떻게 적용할지 정리한다.

· 📖 약 7분 · 1,694자/단어 #postgresql #mysql #benchmark #sql

전체 성능 데이터

PostgreSQL (전 버전)

PostgreSQL 13 / 15 / 17, OR 조건 p50 비교
세 버전 모두 동일한 패턴. PG 13에서도 BitmapOr가 안정적으로 동작한다.
PostgreSQL 전 버전 상세 테이블 (접기/펼치기)
DB방식np50 (ms)p95 (ms)I/O 블록Plan
pg13Tuple IN10.0010.0022index_scan
pg13UNION ALL10.0010.0022index_scan
pg13OR 조건10.0020.0022index_scan
pg13OR 조건30.0040.0056bitmap_heap_scan
pg13Tuple IN30.0040.0096bitmap_heap_scan
pg13UNION ALL30.0050.0066append
pg13UNION ALL50.0070.00713append
pg13OR 조건50.0080.01113bitmap_heap_scan
pg13Tuple IN50.0080.01013bitmap_heap_scan
pg13Tuple IN100.0150.02329bitmap_heap_scan
pg13UNION ALL100.0170.02329append
pg13OR 조건100.0240.04129bitmap_heap_scan
pg13OR 조건200.0320.04355bitmap_heap_scan
pg13Tuple IN200.0320.03355bitmap_heap_scan
pg13UNION ALL200.0320.03256append
pg13OR 조건500.0850.101140bitmap_heap_scan
pg13UNION ALL500.0870.103142append
pg13Tuple IN500.1020.164140bitmap_heap_scan
pg15OR 조건10.0020.0022index_scan
pg15Tuple IN10.0020.0052index_scan
pg15UNION ALL10.0020.0042index_scan
pg15Tuple IN30.0040.0056bitmap_heap_scan
pg15UNION ALL30.0040.0096append
pg15OR 조건30.0050.0086bitmap_heap_scan
pg15UNION ALL50.0070.00713append
pg15OR 조건50.0080.01113bitmap_heap_scan
pg15Tuple IN50.0080.01313bitmap_heap_scan
pg15Tuple IN100.0140.02029bitmap_heap_scan
pg15OR 조건100.0150.01729bitmap_heap_scan
pg15UNION ALL100.0150.02229append
pg15OR 조건200.0270.03255bitmap_heap_scan
pg15Tuple IN200.0280.03755bitmap_heap_scan
pg15UNION ALL200.0350.04456append
pg15Tuple IN500.0810.104140bitmap_heap_scan
pg15OR 조건500.0860.092140bitmap_heap_scan
pg15UNION ALL500.0980.106142append
pg17Tuple IN10.0020.0052index_scan
pg17UNION ALL10.0020.0032index_scan
pg17OR 조건10.0030.0062index_scan
pg17Tuple IN30.0040.0076bitmap_heap_scan
pg17OR 조건30.0050.0076bitmap_heap_scan
pg17UNION ALL30.0050.0086append
pg17OR 조건50.0070.00913bitmap_heap_scan
pg17Tuple IN50.0070.01413bitmap_heap_scan
pg17UNION ALL50.0070.01213append
pg17Tuple IN100.0130.02129bitmap_heap_scan
pg17OR 조건100.0140.02029bitmap_heap_scan
pg17UNION ALL100.0180.02929append
pg17OR 조건200.0230.02555bitmap_heap_scan
pg17Tuple IN200.0300.04455bitmap_heap_scan
pg17UNION ALL200.0360.04156append
pg17Tuple IN500.0660.088140bitmap_heap_scan
pg17OR 조건500.0770.090140bitmap_heap_scan
pg17UNION ALL500.0960.184142append

MySQL (전 버전)

MySQL 전 버전, UNION ALL vs OR (p50, log scale)
모든 버전에서 UNION ALL(점선)만 인덱스 활용. OR(실선)은 Full Scan.
MySQL 전 버전 상세 테이블 (접기/펼치기)
DB방식np50 (ms)p95 (ms)I/O 블록Plan판정
mysql57Tuple IN10.0840.1059ref
mysql57UNION ALL10.1180.1299ref
mysql57OR 조건10.1280.1669ref
mysql57UNION ALL30.1110.17426ref
mysql57Tuple IN3549.0559.6850,026ALL
mysql57OR 조건3552.1556.4850,026ALL
mysql57UNION ALL50.1950.22168ref
mysql57OR 조건5557.0563.5850,026ALL
mysql57Tuple IN5560.3569.1850,026ALL
mysql57UNION ALL100.4591.0162ref
mysql57Tuple IN10579.4623.5850,026ALL
mysql57OR 조건10619.4754.0850,026ALL
mysql57UNION ALL200.7300.948306ref
mysql57OR 조건20603.6611.2850,026ALL
mysql57Tuple IN20680.4710.8850,026ALL
mysql57UNION ALL501.31.4782ref
mysql57OR 조건50691.3700.3850,026ALL
mysql57Tuple IN50693.6697.7850,026ALL
mysql80UNION ALL10.0020.002119ref
mysql80OR 조건10.0020.002119ref
mysql80Tuple IN10.0020.003119ref
mysql80UNION ALL30.0060.006153ref
mysql80OR 조건337.237.91,700,153ALL
mysql80Tuple IN337.538.01,700,153ALL
mysql80UNION ALL50.0140.017238ref
mysql80Tuple IN541.944.61,700,153ALL
mysql80OR 조건543.346.81,700,153ALL
mysql80UNION ALL100.0280.042425ref
mysql80OR 조건1051.860.01,700,153ALL
mysql80Tuple IN1052.453.31,700,153ALL
mysql80UNION ALL200.0550.067714ref
mysql80Tuple IN2071.272.11,700,153ALL
mysql80OR 조건2072.177.01,700,153ALL
mysql80UNION ALL500.1580.1781,666ref
mysql80Tuple IN50131.0134.11,700,153ALL
mysql80OR 조건50133.0138.81,700,153ALL
mysql84UNION ALL10.0020.002221ref
mysql84Tuple IN10.0020.002221ref
mysql84OR 조건10.0020.002221ref
mysql84UNION ALL30.0060.008255ref
mysql84Tuple IN340.341.11,700,255ALL
mysql84OR 조건340.441.11,700,255ALL
mysql84UNION ALL50.0130.014340ref
mysql84OR 조건544.545.61,700,255ALL
mysql84Tuple IN545.146.51,700,255ALL
mysql84UNION ALL100.0280.034527ref
mysql84Tuple IN1056.057.01,700,255ALL
mysql84OR 조건1056.159.01,700,255ALL
mysql84UNION ALL200.0550.064816ref
mysql84Tuple IN2077.579.81,700,255ALL
mysql84OR 조건2080.581.51,700,255ALL
mysql84UNION ALL500.1580.1741,768ref
mysql84OR 조건50147.0149.01,700,255ALL
mysql84Tuple IN50148.0148.61,700,255ALL

실무 권고

PostgreSQL 사용자

  • 편한 문법을 쓰면 된다. 플래너가 BitmapOr로 최적화한다.
  • “OR은 느리니 UNION으로 분리해라”는 조언을 맹목적으로 따를 필요 없다.
  • work_mem이 너무 작으면 Lossy 비트맵이 많아져 Recheck 비용이 증가할 수 있다. 대규모 쿼리에서 EXPLAIN (ANALYZE, BUFFERS)Heap Blocks: lossy=를 확인하자.
  • PG 13 이상이면 모두 동일하게 동작한다.

MySQL 사용자

  • 서로 다른 컬럼에 대한 OR/IN이 2개 이상이면 반드시 UNION ALL로 분리.
  • 이 제약은 MySQL 5.7 → 8.0 → 8.4에서 변하지 않았다.
  • ORM 생성 쿼리를 확인하라. 자동으로 OR을 생성하고 있다면 raw query로 교체.
  • IN이 OR보다 안전하다는 생각도 버리자. 둘 다 동일하게 퇴화한다.

책의 조언은 틀렸나?

“SQL 레벨업”의 UNION 권장은 범용적으로 안전한 조언이다. MySQL에서는 여전히 유효하고, Oracle에서도 비슷한 제약이 있을 수 있다.

다만 PostgreSQL이라면 이미 자유롭다. 불필요하게 UNION ALL로 쿼리를 복잡하게 만들 필요 없다. 쓰는 DB의 옵티마이저를 이해하고, EXPLAIN으로 확인하는 습관이 가장 중요하다.


참고 자료

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기