본문 바로가기
CS/Database

[MySQL] SELECT 절의 처리 순서

by clolee 2025. 4. 1.

SELECT 절의 처리 순서

MySQL에서 SELECT 쿼리는 우리가 작성한 순서대로 실행되지 않고, 내부적으로는 다음과 같은 순서로 처리됩니다.

SELECT 절의 처리 순서 (논리적 처리 순서, Logical Query Processing Order)

처리 순서 설명
1 FROM 테이블/뷰를 로딩
2 ON 조인 조건을 적용 (JOIN 사용 시)
3 JOIN 실제 조인 수행
4 WHERE 로우(행) 필터링 – 그룹 이전
5 GROUP BY 지정된 컬럼 기준으로 그룹핑
6 WITH CUBE / ROLLUP 집계 연산 보조 옵션 (선택 사항)
7 HAVING 그룹핑 결과 필터링
8 SELECT 반환할 컬럼 및 표현식 선택
9 DISTINCT 중복 제거
10 ORDER BY 결과 정렬
11 LIMIT 결과 개수 제한 (OFFSET 포함 가능)

1. 🔍 SELECT 절의 처리 순서와 에러의 관계

  • SELECT보다 WHERE이 먼저 처리되므로, WHERE에서는 SELECT에서 만든 별칭(AS alias)을 사용할 수 없음
    → 반면, HAVING에서는 SELECT 이후 처리되므로 별칭 사용 가능

  
SELECT salary * 2 AS double_salary
FROM employees
WHERE double_salary > 10000; -- ❌ 에러
SELECT salary * 2 AS double_salary
FROM employees
HAVING double_salary > 10000; -- ✅ 가능

2. 🧠 실제 쿼리 실행 최적화 순서와는 다름

  • 논리적 처리 순서 : SQL 언어의 규칙에 따라 쿼리가 해석되는 순서
  • MySQL의 쿼리 최적화기(Query Optimizer)는 논리적 순서와 무관하게 최적의 실행 계획을 선택
  • 하지만 개발자(사용자)가 작성한 SQL은 항상 위의 논리적 처리 순서에 따라 해석됨 (→ 사용자 입장에서는 이 순서를 이해하는 게 중요)

ex)
실제 실행 순서 (MySQL 엔진 내부 최적화 순서 - 예시)
MySQL은 WHERE 조건에 인덱스가 있다면, 먼저 WHERE hire_date > '2020-01-01' 조건에 맞는 데이터만 인덱스를 이용해서 빠르게 조회하고, 그 다음에 GROUP BY를 실행할 수 있어요.
또한 LIMIT 10이 붙어 있으니까, 전체 정렬을 하지 않고 상위 10개만 빠르게 찾아내는 방법을 사용할 수도 있어요.

➡ 즉, MySQL은 내부적으로 실행 계획을 분석해서 "무슨 작업을 언제 먼저 하면 가장 빠를까?"를 따져서 순서를 최적화해서 실행합니다.

💡 SELECT 처리 순서 (논리적 처리 순서)는

우리가 SQL을 작성할 때 MySQL은 항상 아래와 같은 순서로 해석

개발자(사용자)가 SQL을 쓸 때 이해하고 써야 하는 순서.

🔸 실제 실행 순서 (물리적 실행 순서, 실제 동작 순서)

MySQL은 내부적으로 SQL을 실행할 때, 위 순서대로 하나하나 "무식하게" 실행하는 게 아니라,

가장 빠르게 결과를 낼 수 있는 최적의 순서로 실행 계획을 바꿔서 실행.

즉, 우리가 작성한 순서나 해석 순서랑 다른 방식으로 실행할 수도 있다.

 

📌 요약

항목  설명
🔤 논리적 처리 순서 SQL 문법상 해석되는 순서 (항상 고정)
⚙️ 실제 실행 순서 MySQL이 내부에서 가장 효율적으로 실행하기 위해 순서를 최적화하는 것
📣 중요한 점 개발자는 논리적 처리 순서로 SQL을 정확히 써야 하고, 실제 실행 순서는 MySQL에게 맡기면 됨
🧪 실행 계획 보기  EXPLAIN 키워드로 실제 실행 계획 확인 가능

3. 📌 WITH (CTE, Common Table Expression) 설명

  • CTE는 논리적 처리 순서에는 포함되지 않음 (사전 정의 역할)
  • CTE(Common Table Expression)는 SQL 쿼리의 메인 SELECT 절이 해석되기 전에 실행됩니다.
  • WITH 절이 있는 경우, 내부적으로는 서브쿼리처럼 미리 평가되며 FROM 이전 단계에서 처리
  • MySQL 8.0 이상부터 지원됨
  • WITH 절은 FROM 절 내부 서브쿼리와 동일한 역할을 하지만, 가독성 향상, 재사용, 재귀 등 확장성 면에서 더 유리합니다.

서브쿼리 사용


  
SELECT department, COUNT(*)
FROM (
SELECT * FROM employees WHERE hire_date > '2020-01-01'
) AS recent
GROUP BY department;

 

CTE 사용


  
WITH recent_employees AS (
SELECT * FROM employees WHERE hire_date > '2020-01-01'
)
SELECT department, COUNT(*)
FROM recent_employees
GROUP BY department;

위 쿼리에서 실제로는 먼저 recent_employees라는 가상 테이블을 생성한 후,
이후 본 쿼리의 FROM 절에 사용됩니다.

즉, 실제로는 이런 구조라고 생각하면 됩니다:


  
-- (비공식) 내부 처리 순서:
1. WITH (CTE) 평가 → recent_employees 생성
2. FROM recent_employees
3. GROUP BY ...
4. SELECT ...

🎯 공식 처리 순서에는 안 들어가는 이유?

SELECT 절 처리 순서는 “하나의 SELECT 문 내부에서 논리적으로 해석되는 절의 순서”를 말합니다.

CTE는 **SELECT 바깥에 존재하는 ‘보조 구문’**이기 때문에 일반적으로 이 처리 순서 리스트에는 포함되지 않지만:

💡 "FROM 절 이전에 먼저 평가되는 서브쿼리"라는 점에서 SELECT 처리 순서와 밀접한 관계가 있습니다.

4. 📈 실무 팁: SELECT 처리 순서를 활용한 성능 튜닝

  • WHERE는 GROUP 전에 필터링되므로 WHERE에서 최대한 레코드를 줄이는 것이 성능상 유리
  • HAVING을 남용하면 모든 그룹이 만들어진 뒤 필터링되므로 비효율적일 수 있음
  • DISTINCT는 정렬/해시 연산이 필요하므로 ORDER BY와 같이 쓰면 성능 저하될 수 있음

📌 최종 요약 도식


  
1. FROM
2. ON
3. JOIN
4. WHERE
5. GROUP BY
6. WITH ROLLUP / CUBE
7. HAVING
8. SELECT
9. DISTINCT
10. ORDER BY
11. LIMIT (OFFSET)

✅ 예시 (모든 절 포함)


  
WITH recent AS (
SELECT * FROM employees WHERE hire_date > '2020-01-01'
)
SELECT department, COUNT(*) AS cnt
FROM recent
WHERE salary > 3000
GROUP BY department
HAVING cnt > 5
ORDER BY cnt DESC
LIMIT 10;

댓글