blog

๐Ÿ“˜ Django ORM VS SQLAlchemy ๋ฌด์—‡์ด ๋‹ค๋ฅผ๊นŒ?

๋‚ ์งœ: 2025-06-08

๋ชฉ๋ก์œผ๋กœ


๋“ค์–ด๊ฐ€๋ฉฐ

Python ์ƒํƒœ๊ณ„์—์„œ ORM(Object-Relational Mapping) ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๋Œ€ํ‘œ๋˜๋Š” ๋‘ ๊ฐ€์ง€๋Š” ๋ฐ”๋กœ Django ORM๊ณผ SQLAlchemy์ž…๋‹ˆ๋‹ค.

๋‘ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋ชจ๋‘ RDB์™€ ๊ฐ์ฒด์ง€ํ–ฅ ๋ชจ๋ธ ๊ฐ„์˜ ๋งคํ•‘์„ ์ œ๊ณตํ•˜์ง€๋งŒ, ์ฟผ๋ฆฌ ์ž‘์„ฑ ๋ฐฉ์‹๊ณผ ์ถ”์ƒํ™” ์ˆ˜์ค€์— ์žˆ์–ด ๊ทน๋ช…ํ•œ ์ฐจ์ด๋ฅผ ๋ณด์ž…๋‹ˆ๋‹ค.

ํ•„์ž๋Š” Django ๊ฒฝ๋ ฅ๋งŒ ๊ฑฐ์˜ 7๋…„ ๋„˜๊ฒŒ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ƒํ™ฉ์—์„œ SQLAlchemy ๊ธฐ๋ฐ˜์˜ FastAPI ๊ฐœ๋ฐœ์„ ํ•ด์•ผํ•˜๋Š” ์ƒํ™ฉ์ด ๋˜๋‹ˆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋ณธ์งˆ์  ์ฐจ์ด์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ด์งˆ๊ฐ์œผ๋กœ ์ดˆ๊ธฐ์— ๊ณ ์ƒ์œผ๋กœ ์ข€ ํ•˜๊ณ  ์žˆ๋Š”๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ใ…‹ใ…‹ใ…‹

๊ทธ๋ž˜์„œ ์ด ๊ธ€์—์„œ๋Š” SQLAlchemy ์‹ค๋ฌด๋ฅผ ์œ„ํ•œ Django ๋Œ€๋น„ ์ฃผ์š” Tip ์†Œ๊ฐœ์™€ ๋ณต์žกํ•œ ์กฐ๊ฑด ํ•„ํ„ฐ + ๊ด€๊ณ„ ๋กœ๋”ฉ + ์ •๋ ฌ์ด ์„ž์ธ ์‹ค์ „ ์˜ˆ์ œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ, Django ORM๊ณผ SQLAlchemy๊ฐ€ ์–ด๋–ป๊ฒŒ ์ ‘๊ทผ ๋ฐฉ์‹์ด ๋‹ค๋ฅธ์ง€ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๐Ÿ’ฃ SQLAlchemy ์‹ค๋ฌด ์ ์šฉ ์‹œ ๋ฏธ๋ฆฌ ์•Œ๊ณ  ์žˆ์œผ๋ฉด ์ข‹์€ ํ•จ์ • ํฌ์ธํŠธ 5๊ฐ€์ง€

1. N:N ๊ด€๊ณ„๋Š” ํ•ญ์ƒ ์ง์ ‘ join ํ•ด์•ผ ํ•œ๋‹ค

โœ… ์‹ค์ „ ์˜ˆ์‹œ:

stmt = (
    select(Post)
    .join(post_tag_table, Post.id == post_tag_table.c.post_id)
    .join(Tag, Tag.id == post_tag_table.c.tag_id)
    .where(Tag.name == "ํŒŒ์ด์ฌ")
)

๐Ÿ“Œ ์ค‘๊ฐ„ ํ…Œ์ด๋ธ” ์—†์ด join(Tag) ํ•˜๋ฉด ๋ฌด์กฐ๊ฑด ์—๋Ÿฌ ๋‚ฉ๋‹ˆ๋‹ค


2. ๊ด€๊ณ„ ์กฐํšŒ๋Š” .join()์ด ์•„๋‹ˆ๋ผ .options()

โœ… ์‹ค์ „ ์˜ˆ์‹œ:

select(Post).options(selectinload(Post.author))

๐Ÿ“Œ join()์œผ๋กœ๋Š” relationship์ด ์ž๋™ ๋กœ๋”ฉ๋˜์ง€ ์•Š์Œ. ๋‹จ์ˆœ ์กฐ์ธ๋งŒ.

3. ํ•„ํ„ฐ ์กฐ๊ฑด์€ ํ•ญ์ƒ join()์„ ๋จผ์ € ์„ ์–ธํ•˜๊ณ  ๋‚˜์„œ ์จ์•ผ ํ•œ๋‹ค

โœ… ์ž˜๋ชป๋œ ์˜ˆ:

select(Post).where(Tag.name == "ํŒŒ์ด์ฌ")  # โŒ join์ด ์—†์œผ๋ฉด ์‹คํ–‰ ์—๋Ÿฌ

โœ… ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ:

select(Post).join(Post.tags).where(Tag.name == "ํŒŒ์ด์ฌ")

4. .options()๋Š” ๋กœ๋”ฉ ์ „๋žต์ผ ๋ฟ, ํ•„ํ„ฐ๋ง์—๋Š” ์•„๋ฌด ํšจ๊ณผ ์—†์Œ

ํ—ท๊ฐˆ๋ฆฌ๋Š” ํŒจํ„ด

select(Post).options(selectinload(Post.tags)).where(Tag.name == "ํŒŒ์ด์ฌ")  # โŒ ์—๋Ÿฌ

๐Ÿ“Œ options()๋Š” ์ฟผ๋ฆฌ ์กฐ๊ฑด์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์Œ

โ†’ ์‹ค์ œ ์กฐ์ธ์€ join()์œผ๋กœ, ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์€ options()๋กœ ๋”ฐ๋กœ ์จ์•ผ ํ•จ

5. select() ๊ธฐ๋ฐ˜ ์ฒด์ด๋‹์ด ๋งŽ์•„์งˆ์ˆ˜๋ก ์ฝ”๋“œ๊ฐ€ ์ˆ˜์ง์œผ๋กœ ๊ธธ์–ด์ง„๋‹ค

โœ… ์˜ˆ์‹œ:

select(Post)
.join(User)
.where(and_(..., ...))
.order_by(...)
.options(selectinload(...), selectinload(...))

๐Ÿ“Œ ํ•œ ์ค„์”ฉ ์ •๋ฆฌํ•˜๋Š” ์ฝ”๋“œ ์ปจ๋ฒค์…˜์„ ํŒ€ ๋‚ด์—์„œ ์ •ํ•ด๋‘๋Š” ๊ฒŒ ์‹ค๋ฌด์—์„œ ์ค‘์š”

โœ๏ธ ์ •๋ฆฌํ•˜๋ฉด

ํฌ์ธํŠธ Django์—์„œ๋Š”โ€ฆ SQLAlchemy์—์„œ๋Š”โ€ฆ
๊ด€๊ณ„ ์กฐ์ธ ์ž๋™ ์ถ”๋ก  ๋ช…์‹œ์  join ํ•„์š”
๊ด€๊ณ„ ๋กœ๋”ฉ select_related options(selectinload)
N:N ๊ด€๊ณ„ ์•Œ์•„์„œ ์ฒ˜๋ฆฌ ์ค‘๊ฐ„ ํ…Œ์ด๋ธ” ์ˆ˜๋™ ๋ช…์‹œ
์กฐ๊ฑด ํ•„ํ„ฐ ๊ด€๊ณ„ ํ•„๋“œ ๊ฒฝ๋กœ๋กœ ๊ฐ€๋Šฅ join + where ์กฐ๊ฑด ์กฐํ•ฉ ํ•„์š”
๊ฐ€๋…์„ฑ ์ˆ˜ํ‰์  chain ์ˆ˜์ง์  ์ฒด์ด๋‹ ๊ตฌ์กฐ

๐Ÿš€ ๋ฏธ๋ฆฌ ์•Œ๋ฉด ์‹ค๋ฌด๊ฐ€ ์‰ฌ์›Œ์ง€๋Š” ํŒ

๊ทน๋ช…ํ•œ ์ฟผ๋ฆฌ ์ž‘์„ฑ ์ฒด๊ณ„์˜ ์ฐจ์ด์  ๋ถ„์„

์˜ˆ์ œ ์‹œ๋‚˜๋ฆฌ์˜ค

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ชจ๋ธ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค.

์กฐ๊ฑด:

1. Django ORM ๋ฐฉ์‹

Post.objects.select_related("user") \
    .prefetch_related("tags") \
    .filter(
        title__icontains="FastAPI",
        published_at__isnull=False,
        tags__name="ํŒŒ์ด์ฌ"
    ) \
    .order_by("user__name")

โœ… ํŠน์ง•

2. SQLAlchemy ๋ฐฉ์‹

stmt = (
    select(Post)
    .join(Post.user)
    .join(post_tag_table, post_tag_table.c.post_id == Post.id)
    .join(Tag, Tag.id == post_tag_table.c.tag_id)
    .where(
        and_(
            Post.title.ilike("%FastAPI%"),
            Post.published_at.is_not(None),
            Tag.name == "ํŒŒ์ด์ฌ"
        )
    )
    .order_by(User.name.asc())
    .options(
        selectinload(Post.user),
        selectinload(Post.tags)
    )
)
results = session.exec(stmt).all()

โœ… ํŠน์ง•

๋น„๊ต ์š”์•ฝ

ํ•ญ๋ชฉ Django ORM SQLAlchemy
์ถ”์ƒํ™” ์ˆ˜์ค€ ๋งค์šฐ ๋†’์Œ (๋ชจ๋ธ ๊ธฐ๋ฐ˜ DSL) ๋‚ฎ์Œ (SQL ๊ธฐ๋ฐ˜ ์กฐํ•ฉ)
๊ด€๊ณ„ ๋กœ๋”ฉ select_related / prefetch_related selectinload / joinedload (options)
N:N ๊ด€๊ณ„ ์ฒ˜๋ฆฌ ORM์ด ์ž๋™ ์กฐ์ธ ์ค‘๊ฐ„ ํ…Œ์ด๋ธ” ์ง์ ‘ ๋ช…์‹œ ํ•„์š”
์ฟผ๋ฆฌ ๊ฐ€๋…์„ฑ ๊ฐ„๊ฒฐ, ์˜๋„ ์ค‘์‹ฌ ๋ช…์‹œ์ , ๊ตฌ์กฐ ์ค‘์‹ฌ
์ฟผ๋ฆฌ ์œ ์—ฐ์„ฑ ์ œํ•œ์  ๋งค์šฐ ๋†’์Œ
ํ•™์Šต ๋‚œ์ด๋„ ์ง„์ž… ์žฅ๋ฒฝ ๋‚ฎ์Œ ์ง„์ž… ์žฅ๋ฒฝ ๋†’์Œ, ์ •๋ฐ€ ์ œ์–ด ๊ฐ€๋Šฅ

์™œ ์ด๋Ÿฐ ์ฐจ์ด๊ฐ€ ์ƒ๊ฒผ์„๊นŒ?

์–ด๋–ค ์ƒํ™ฉ์— ์–ด๋–ค ORM์ด ์œ ๋ฆฌํ• ๊นŒ?

์ƒํ™ฉ ์ถ”์ฒœ ORM
๋น ๋ฅธ CRUD ์›น ์„œ๋น„์Šค ๊ตฌ์ถ• โœ… Django ORM
๋ณต์žกํ•œ ์กฐ์ธ, ์„ฑ๋Šฅ ์ตœ์ ํ™”, SQL ์ œ์–ด โœ… SQLAlchemy
๋ช…ํ™•ํ•œ ๊ด€๊ณ„ ์ •์˜ + ์ž๋™ํ™”๋œ ๋กœ๋”ฉ โœ… Django ORM
ORM์„ ๋„˜๋‚˜๋“œ๋Š” SQL ์กฐํ•ฉ, ์„œ๋ธŒ์ฟผ๋ฆฌ, Union ๋“ฑ โœ… SQLAlchemy

๋งˆ๋ฌด๋ฆฌ

๋‘ ORM์€ ์„œ๋กœ ๋‹ค๋ฅธ ์ฒ ํ•™์„ ๊ฐ–๊ณ  ์žˆ๊ณ , ์‚ฌ์šฉํ•˜๋Š” ๊ฐœ๋ฐœ์ž์—๊ฒŒ๋„ ๋‹ค๋ฅธ ๊ธฐ๋Œ€์™€ ์ฑ…์ž„์„ ์š”๊ตฌํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์ˆœํžˆ โ€œ์–ด๋–ค ๊ฒŒ ๋” ๋‚ซ๋‹คโ€๊ฐ€ ์•„๋‹ˆ๋ผ, **โ€œ์–ด๋–ค ์ƒํ™ฉ์— ์–ด๋–ค ๋„๊ตฌ๊ฐ€ ์ ํ•ฉํ•œ๊ฐ€โ€**๋ฅผ ์ดํ•ดํ•˜๊ณ  ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

ํ•„์ž๋Š” Django ORM์˜ ๊ฐ„๊ฒฐํ•จ์— ์ต์ˆ™ํ•ด ์žˆ์—ˆ์ง€๋งŒ, SQLAlchemy๋ฅผ ์ ‘ํ•˜๋ฉด์„œ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ๋ณด๋‹ค ๋ช…ํ™•ํ•˜๊ฒŒ ํ†ต์ œํ•  ์ˆ˜ ์žˆ๋Š” ๋งค๋ ฅ์„ ๋А๊ผˆ์Šต๋‹ˆ๋‹ค.

ORM์„ ๋„˜์–ด์„œ SQL๊นŒ์ง€ ์ดํ•ดํ•˜๊ณ  ์‹ถ์€ ๊ฐœ๋ฐœ์ž๋ผ๋ฉด SQLAlchemy๋„ ํ•œ ๋ฒˆ ๋„์ „ํ•ด๋ณด์‹œ๊ธธ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.


๋ชฉ๋ก์œผ๋กœ