Django ORM cơ bản: truy vấn dữ liệu mà không cần viết SQL

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Django nổi tiếng với ORM mạnh và dễ dùng: bạn làm việc với class Python, còn Django lo việc sinh câu SQL. Bài này đi qua những thao tác truy vấn cơ bản và những cạm bẫy hiệu năng mà người mới hay gặp.

Định nghĩa model

from django.db import modelsclass Category(models.Model):    name = models.CharField(max_length=100)class Article(models.Model):    title = models.CharField(max_length=200)    body = models.TextField()    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="articles")    published = models.BooleanField(default=False)    created_at = models.DateTimeField(auto_now_add=True)

Sau khi khai báo, chạy python manage.py makemigrations rồi python manage.py migrate để tạo bảng.

Các thao tác cơ bản

Article.objects.create(title="Bài 1", body="...", category=cat)Article.objects.all()Article.objects.filter(published=True)Article.objects.exclude(category__name="Nháp")Article.objects.get(pk=1)          # lỗi nếu không có hoặc nhiều hơn mộtArticle.objects.filter(pk=1).first()  # trả None nếu không cóArticle.objects.filter(pk=1).update(published=True)Article.objects.filter(published=False).delete()

Kết quả của filter là QuerySet, một đối tượng được tính toán lười: câu SQL chỉ chạy khi bạn thực sự cần dữ liệu (duyệt, ép sang list, gọi len...). Nhờ vậy bạn có thể nối nhiều lệnh một cách tự nhiên.

Lọc nâng cao và sắp xếp

from django.db.models import Q, CountArticle.objects.filter(title__icontains="python")Article.objects.filter(created_at__year=2024)Article.objects.filter(Q(published=True) | Q(category__name="Tin tức"))Article.objects.order_by("-created_at")[:10]Category.objects.annotate(total=Count("articles")).filter(total__gt=5)

Cú pháp hai dấu gạch dưới (__) dùng để chọn toán tử (icontains, gt, in) hoặc đi qua quan hệ (category__name). annotate thêm cột tính toán như đếm, tổng, trung bình.

Lỗi N+1 và cách khắc phục

Giống mọi ORM, Django dễ dính N+1. Đoạn sau chạy một truy vấn cho bài viết và thêm một truy vấn cho mỗi category:

for a in Article.objects.all():    print(a.category.name)  # mỗi vòng thêm một query

Cách sửa:

# quan hệ khóa ngoại / một-một: dùng JOINArticle.objects.select_related("category")# quan hệ ngược / nhiều-nhiều: dùng truy vấn riêng rồi ghépCategory.objects.prefetch_related("articles")

Quy tắc: select_related cho quan hệ trỏ tới một đối tượng, prefetch_related cho quan hệ trỏ tới nhiều đối tượng.

Lấy ít dữ liệu hơn

  • values("id", "title") trả về từ điển thay vì đối tượng đầy đủ.
  • only() và defer() chọn hoặc bỏ cột.
  • exists() thay cho len(queryset) > 0 khi chỉ cần biết có tồn tại hay không.
  • count() thay cho len() khi chỉ cần đếm.
  • Phân trang bằng Paginator hoặc cắt lát thay vì lấy toàn bộ bảng.

Giao dịch và cập nhật an toàn

Khi nhiều thao tác phải cùng thành công hoặc cùng thất bại, bọc chúng trong transaction.atomic(). Để tránh xung đột khi cập nhật số lượng, hãy cập nhật ngay trong database bằng biểu thức F thay vì đọc rồi ghi lại trong Python:

from django.db import transactionfrom django.db.models import Fwith transaction.atomic():    Product.objects.filter(pk=1).update(stock=F("stock") - 1)

Quản lý migration trong Django

Mỗi khi sửa model, Django có thể sinh migration tự động bằng makemigrations. Hãy đọc file được sinh ra trước khi áp dụng: đôi khi Django hiểu việc đổi tên trường là xóa một cột rồi tạo cột mới, đồng nghĩa mất dữ liệu. Bạn có thể chỉnh lại thủ công để dùng thao tác đổi tên. Commit các file migration vào Git cùng với code model để đồng đội và server production áp dụng cùng một lịch sử.

Khi cần chèn dữ liệu ban đầu hoặc chuyển đổi dữ liệu, dùng migration dữ liệu với RunPython và nhớ cung cấp hàm đảo ngược để migration có thể hoàn tác. Với bảng rất lớn, hãy cân nhắc tác động của việc thêm cột hoặc chỉ mục lên thời gian khóa bảng, và thử trước trên bản sao dữ liệu.

Công cụ gỡ lỗi truy vấn

Django Debug Toolbar hiển thị số lượng truy vấn, thời gian và câu SQL cho từng trang, giúp bạn phát hiện N+1 ngay lúc phát triển. Bạn cũng có thể dùng queryset.explain() để xem kế hoạch thực thi mà database dùng, từ đó biết truy vấn có tận dụng được chỉ mục hay không. Thói quen nhìn vào SQL thật sự được sinh ra là điểm khác biệt giữa người dùng ORM tốt và người chỉ viết cho chạy được.

Gợi ý thực hành

Hãy tạo hai model Category và Article như trên, nạp vài trăm bản ghi bằng một script, rồi viết một view liệt kê bài viết kèm tên danh mục. Bật Debug Toolbar và đếm số truy vấn khi có và không có select_related. Bạn sẽ thấy con số nhảy từ hàng trăm xuống còn một, và đó là bài học trực quan nhất về hiệu năng ORM mà bạn có thể có. Sau đó thử thêm phân trang và lọc theo danh mục để làm quen với QuerySet nối chuỗi.

Kết luận

ORM của Django giúp bạn làm việc nhanh, nhưng đừng quên nhìn vào câu SQL nó sinh ra (qua print(queryset.query) hoặc Django Debug Toolbar). Nắm chắc QuerySet lười, select_related và prefetch_related là đã giải quyết được phần lớn vấn đề hiệu năng thường gặp.

Nếu muốn luyện tập cùng bài tập thực tế, hãy ghé các khóa học tại codebem.com khi bạn sẵn sàng.

author_photo
Nguyễn Đình Cường

0 comment