백엔드 개발자를 위한 SQL과 ORM 완전 정복

김현우·2025년 5월 14일

ORM만 쓰다가 쿼리 성능 때문에 멘붕 온 적 있나요?
SQL과 ORM은 충돌하는 기술이 아니라, 서로 보완하는 도구입니다
이 글에서는 SQL의 본질부터 ORM의 장단점, 그리고 실전에서 두 가지를 어떻게 조화롭게 사용할 수 있는지 다뤄보겠습니다


1. SQL이란 무엇인가?

SQL (Structured Query Language)
→ 관계형 데이터베이스(RDBMS)에서 데이터를 조회하고, 삽입하고, 수정하고, 삭제하기 위한 표준 언어

백엔드에서 SQL이 필요한 상황

  • 조회: 특정 조건의 사용자 정보를 가져올 때
  • 삽입: 회원가입 시 새로운 유저 데이터 DB에 넣을 때
  • 수정: 게시글 수정 시 해당 row를 업데이트
  • 성능 튜닝: 느려진 페이지의 쿼리 속도 향상
-- 예: 특정 유저의 주문 목록을 날짜순으로 정렬해서 조회
SELECT * FROM orders WHERE user_id = 123 ORDER BY created_at DESC;

SQL을 모르면 생기는 문제점

  • ORM이 자동 생성한 쿼리를 이해하지 못해 성능 병목 분석 어려움
  • 복잡한 집계나 통계 쿼리를 직접 짜지 못함
  • 디버깅 시 쿼리 로그를 읽어도 무슨 의미인지 모름

예시:
ORM만 사용하고 있던 프로젝트에서 느려지는 페이지가 생김.
→ 로그를 보니 WHERE 조건 없이 전체 테이블을 조회 중
→ SQL을 몰라서 원인을 찾는 데만 수 시간 소요됨


2. ORM(Object-Relational Mapping)이란?

ORM은 객체(Object)데이터베이스 테이블(Row)을 매핑(mapping)해주는 기술임
즉, SQL을 직접 작성하지 않고도 클래스 기반으로 DB 조작 가능하게 해줌

장점

  • 생산성: 반복되는 CRUD를 자동으로 처리
  • 유지보수성: 비즈니스 로직 코드 안에서 DB 로직 통합 가능
  • 추상화: 테이블 이름, 컬럼명보다 클래스명, 필드명을 통해 코드 작성 가능

실제 사용 예시

Java - JPA

@Entity
public class User {
  @Id
  private Long id;
  private String name;
}
User user = userRepository.findById(1L).get();

Python - SQLAlchemy

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    name = Column(String)

# 조회
user = session.query(User).filter_by(id=1).first()

3. ORM의 한계와 SQL이 필요한 순간

성능 문제

  • N+1 문제: 관계된 데이터를 반복적으로 가져오면서 수십/수백 개의 쿼리 실행됨
  • 쿼리 튜닝 불가: ORM이 자동 생성한 JOIN 쿼리가 비효율적일 수 있음

디버깅의 어려움

  • "내가 작성한 코드"는 동작하는데, 백엔드 서버의 쿼리 로그를 보면 완전 다른 내용이 실행됨
  • SQL을 모르면 ORM이 생성한 쿼리의 의미를 이해하지 못함

복잡한 비즈니스 로직

  • 3개 이상의 테이블 JOIN, 집계(Aggregation), 윈도우 함수 사용
  • 기간별 통계, 사용자별 집계, 랭킹 산정 등은 직접 SQL 작성이 훨씬 명확하고 빠름

실무 사례

  • 초기에 모든 로직을 JPA로 구성 → 통계 페이지가 5초 이상 걸림
  • 결국 @Query(nativeQuery = true) 또는 MyBatis로 전환해서 SQL 튜닝
  • 복잡한 레포트는 Stored Procedure 작성 후 호출

4. SQL과 ORM을 함께 사용하는 전략

혼합 전략

역할도구
단순 CRUDORM 사용
복잡한 통계 / 집계 / JOINSQL 직접 작성
대시보드, 리포트View, Procedure + SQL
동적 필터링Query DSL, Criteria API 등

JPQL / Native Query

// JPQL 예시
@Query("SELECT u FROM User u WHERE u.name = :name")
List<User> findByName(String name);

// Native SQL 예시
@Query(value = "SELECT * FROM users WHERE age > 30", nativeQuery = true)
List<User> findAdults();

5. 학생들에게: ORM과 SQL을 함께 공부하는 법

ORM ↔ SQL 비교 예시

SQL

SELECT * FROM users WHERE name = 'Kim';

ORM

session.query(User).filter_by(name="Kim").all()

둘 다 같은 동작을 하지만, SQL은 더 직접적이고 세부적인 제어가 가능함
ORM은 빠른 개발에 좋지만, 추상화된 만큼 성능을 통제하기 어려움

학습 순서 추천

  1. SQL 기초: SELECT, JOIN, WHERE, GROUP BY, ORDER BY, LIMIT
  2. SQL 실습: SQLite, MySQL로 직접 쿼리 날려보기
  3. ORM 도입: SQLAlchemy, JPA로 CRUD부터 연습
  4. 성능 디버깅: SQL 로그 분석, explain plan 보기
  5. 복합 전략 설계: 언제 ORM 쓰고 언제 SQL 쓰는지 판단 훈련

참고자료

  • 김영한, 『자바 ORM 표준 JPA 프로그래밍』
  • 실전 SQL 튜닝 - Oracle, MySQL, PostgreSQL 기준
  • QueryDSL, MyBatis 공식 문서
  • SQLBolt.com (SQL 온라인 튜토리얼)
  • SQL Zoo, LeetCode SQL Practice

0개의 댓글