Django ORM: tối ưu truy vấn để đạt hiệu năng tối đa
Hướng dẫn đầy đủ về tối ưu truy vấn Django ORM. select_related, prefetch_related, chỉ mục, phân tích vấn đề N+1 và các kỹ thuật nâng cao cho ứng dụng hiệu năng cao.

Django ORM cung cấp một lớp trừu tượng tinh tế cho việc tương tác với cơ sở dữ liệu, nhưng sự đơn giản đó có thể che giấu những vấn đề hiệu năng nghiêm trọng. Một ứng dụng Django thiếu tối ưu có thể tạo ra hàng trăm truy vấn trong khi chỉ một truy vấn là đủ. Bài viết này trình bày các kỹ thuật cần thiết để nhận diện và xử lý những vấn đề đó.
Trước khi tối ưu, cần đo lường. Việc dùng django-debug-toolbar trong môi trường phát triển cho phép quan sát từng truy vấn SQL được tạo ra và nhanh chóng nhận diện nút thắt cổ chai.
Hiểu rõ vấn đề N+1
Vấn đề N+1 là cái bẫy phổ biến nhất khi làm việc với ORM. Nó xảy ra khi truy vấn đầu tiên lấy về N đối tượng, sau đó N truy vấn bổ sung được thực thi để truy cập quan hệ của từng đối tượng. Sự nhân lên của truy vấn này khiến hiệu năng giảm sút đáng kể.
# models.py
from django.db import models
class Author(models.Model):
"""Mô hình đại diện cho tác giả của một quyển sách."""
name = models.CharField(max_length=200)
email = models.EmailField(unique=True)
bio = models.TextField(blank=True)
def __str__(self):
return self.name
class Book(models.Model):
"""Mô hình đại diện cho quyển sách kèm tác giả."""
title = models.CharField(max_length=300)
author = models.ForeignKey(
Author,
on_delete=models.CASCADE,
related_name='books'
)
published_date = models.DateField()
isbn = models.CharField(max_length=13, unique=True)
def __str__(self):
return self.titleNhững mô hình đơn giản này giúp minh họa vấn đề N+1 một cách cụ thể.
# views.py - Ví dụ có vấn đề
def list_books_bad(request):
"""❌ View này tạo ra N+1 truy vấn."""
books = Book.objects.all() # 1 truy vấn cho danh sách sách
for book in books:
# Mỗi lần truy cập book.author kích hoạt một truy vấn SQL
print(f"{book.title} bởi {book.author.name}")
# Với 100 cuốn sách = 101 truy vấn SQL!
return render(request, 'books/list.html', {'books': books})Đoạn mã trông vô hại nhưng lại tạo ra một truy vấn cho mỗi cuốn sách để lấy tác giả.
Xử lý N+1 bằng select_related
Phương thức select_related thực hiện một SQL JOIN và lấy dữ liệu liên quan trong duy nhất một truy vấn. Nó hoạt động cho các quan hệ ForeignKey và OneToOneField.
# views.py - Giải pháp tối ưu với select_related
def list_books_optimized(request):
"""✅ View này tạo ra duy nhất một truy vấn có JOIN."""
# select_related thực hiện một SQL JOIN
books = Book.objects.select_related('author').all()
for book in books:
# Không có truy vấn bổ sung: tác giả đã được nạp sẵn
print(f"{book.title} bởi {book.author.name}")
# Tổng cộng: 1 truy vấn SQL duy nhất bất kể số lượng sách
return render(request, 'books/list.html', {'books': books})Truy vấn SQL được sinh ra dùng LEFT OUTER JOIN để lấy tác giả cùng lúc với sách.
# Liên kết select_related cho quan hệ lồng nhau
# models.py
class Publisher(models.Model):
name = models.CharField(max_length=200)
country = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=300)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
# views.py
def list_books_with_details(request):
"""Lấy sách, tác giả và nhà xuất bản chỉ với một truy vấn."""
books = Book.objects.select_related(
'author', # ForeignKey tới Author
'publisher' # ForeignKey tới Publisher
).all()
return render(request, 'books/list.html', {'books': books})Nhiều quan hệ có thể được tối ưu cùng lúc bằng cách liệt kê chúng trong select_related.
Tối ưu quan hệ ManyToMany với prefetch_related
Đối với các quan hệ ManyToMany hoặc quan hệ ngược (ForeignKey nhìn từ phía bên kia), prefetch_related thực hiện các truy vấn tách rời nhưng được tối ưu, tránh JOIN khổng lồ.
# models.py
class Tag(models.Model):
"""Thẻ dùng để phân loại sách."""
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=300)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag, related_name='books')Quan hệ ManyToMany giữa Book và Tag yêu cầu prefetch_related để tối ưu hiệu quả.
# views.py - Tối ưu ManyToMany
def list_books_with_tags(request):
"""✅ Lấy sách và thẻ của chúng một cách hiệu quả."""
books = Book.objects.prefetch_related('tags').all()
for book in books:
# Thẻ đã được nạp sẵn, không có truy vấn bổ sung
tag_names = [tag.name for tag in book.tags.all()]
print(f"{book.title}: {', '.join(tag_names)}")
# Tổng cộng: 2 truy vấn (sách + thẻ) bất kể khối lượng
return render(request, 'books/list.html', {'books': books})prefetch_related thực hiện một truy vấn riêng cho thẻ rồi ghép dữ liệu lại trong Python.
Nên dùng select_related cho ForeignKey và OneToOne (SQL JOIN). Sử dụng prefetch_related cho ManyToMany và quan hệ ngược (truy vấn riêng). Cả hai có thể kết hợp trên cùng một QuerySet.
Tùy biến tải sẵn dữ liệu với đối tượng Prefetch
Đối tượng Prefetch cho phép kiểm soát chi tiết dữ liệu được tải sẵn: lọc, sắp xếp và thậm chí giới hạn số kết quả.
# views.py
from django.db.models import Prefetch
def list_authors_with_recent_books(request):
"""Lấy tác giả chỉ kèm theo các cuốn sách gần đây."""
# Prefetch tùy biến: chỉ sách từ năm 2025
recent_books_prefetch = Prefetch(
'books',
queryset=Book.objects.filter(
published_date__year__gte=2025
).order_by('-published_date'),
to_attr='recent_books' # Lưu vào thuộc tính tùy biến
)
authors = Author.objects.prefetch_related(
recent_books_prefetch
).all()
for author in authors:
# Truy cập qua thuộc tính tùy biến
for book in author.recent_books:
print(f"{author.name}: {book.title}")
return render(request, 'authors/list.html', {'authors': authors})Thuộc tính to_attr lưu kết quả vào một list Python thay vì manager thông thường.
# Kết hợp nâng cao: select_related + Prefetch
def list_authors_complete(request):
"""Ví dụ trọn vẹn về tối ưu nhiều cấp."""
authors = Author.objects.prefetch_related(
Prefetch(
'books',
queryset=Book.objects.select_related(
'publisher' # Tối ưu cả nhà xuất bản của từng cuốn
).prefetch_related(
'tags' # Và thẻ của từng cuốn
).filter(published_date__year=2026)
)
).all()
return render(request, 'authors/complete.html', {'authors': authors})Phương pháp này giảm mạnh số lượng truy vấn cho các cấu trúc dữ liệu phức tạp.
Sẵn sàng chinh phục phỏng vấn Django?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
Giới hạn cột với only() và defer()
Mặc định Django lấy toàn bộ cột của bảng. Với mô hình có nhiều trường hoặc trường lớn, việc giới hạn cột giúp tăng hiệu năng.
# views.py
def list_books_minimal(request):
"""Chỉ lấy những cột cần thiết."""
# only() chỉ định các cột được lấy
books = Book.objects.only(
'id',
'title',
'published_date'
).select_related('author')
# Lưu ý: truy cập trường không nằm trong only sẽ kích hoạt truy vấn
for book in books:
print(book.title) # OK, đã có
# print(book.isbn) # Sẽ tạo thêm một truy vấn
return render(request, 'books/list.html', {'books': books})Phương thức only() tạo ra đối tượng "deferred" chỉ nạp những cột được chỉ định.
# defer() để loại bỏ cột cụ thể
def list_authors_without_bio(request):
"""Loại bỏ trường lớn ít khi sử dụng."""
# defer() loại bỏ những cột được liệt kê
authors = Author.objects.defer(
'bio' # TextField không được nạp
).all()
for author in authors:
print(author.name) # OK
print(author.email) # OK
# author.bio sẽ được nạp khi cần
return render(request, 'authors/list.html', {'authors': authors})defer() là nghịch đảo của only(): các cột liệt kê không được nạp ngay từ đầu.
Tối ưu với values() và values_list()
Khi chỉ cần một số giá trị mà không cần đối tượng mô hình đầy đủ, values() và values_list() trả về dictionary hoặc tuple nhẹ hơn.
# views.py
def get_book_titles(request):
"""Chỉ lấy tiêu đề dưới dạng danh sách."""
# values_list trả về tuple
titles = Book.objects.values_list('title', flat=True)
# Kết quả: ['Sách 1', 'Sách 2', ...]
# values trả về dictionary
book_data = Book.objects.values('title', 'published_date')
# Kết quả: [{'title': 'Sách 1', 'published_date': ...}, ...]
return render(request, 'books/titles.html', {'titles': titles})Các phương thức này tránh khởi tạo đối tượng mô hình, qua đó giảm tiêu thụ bộ nhớ.
# Kết hợp với phép tổng hợp
from django.db.models import Count, Avg
def get_author_statistics(request):
"""Thống kê theo tác giả mà không nạp đối tượng."""
stats = Author.objects.values('name').annotate(
book_count=Count('books'),
avg_year=Avg('books__published_date__year')
).order_by('-book_count')
# Kết quả: [{'name': 'Tác giả', 'book_count': 5, 'avg_year': 2024}, ...]
return render(request, 'authors/stats.html', {'stats': stats})Annotation cho phép thực hiện tính toán ngay trong cơ sở dữ liệu.
Tạo chỉ mục để tăng tốc truy vấn
Chỉ mục cơ sở dữ liệu giúp tìm kiếm nhanh hơn rất nhiều. Django cho phép định nghĩa chỉ mục trực tiếp trong mô hình.
# models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=300, db_index=True)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
published_date = models.DateField()
isbn = models.CharField(max_length=13, unique=True)
status = models.CharField(max_length=20, default='available')
class Meta:
# Chỉ mục ghép cho các truy vấn thường gặp
indexes = [
# Chỉ mục theo ngày xuất bản (sắp xếp thường xuyên)
models.Index(
fields=['published_date'],
name='book_pub_date_idx'
),
# Chỉ mục ghép cho lọc theo tác giả + trạng thái
models.Index(
fields=['author', 'status'],
name='book_author_status_idx'
),
# Chỉ mục một phần: chỉ những cuốn còn sẵn
models.Index(
fields=['published_date'],
name='book_available_idx',
condition=models.Q(status='available')
),
]
# Sắp xếp mặc định tận dụng chỉ mục
ordering = ['-published_date']Những chỉ mục này nâng cao hiệu năng cho truy vấn lọc theo các cột tương ứng.
Chỉ mục giúp đọc nhanh hơn nhưng làm chậm chút ít các thao tác ghi (INSERT, UPDATE). Chỉ tạo chỉ mục trên các cột thường được dùng trong WHERE, ORDER BY hoặc JOIN.
Sử dụng truy vấn SQL thô khi cần
Đối với truy vấn phức tạp hoặc tối ưu đặc thù cho cơ sở dữ liệu, truy vấn SQL thô đem lại quyền kiểm soát hoàn toàn.
# views.py
from django.db import connection
def get_books_with_raw_sql(request):
"""Truy vấn thô cho trường hợp đặc biệt."""
# Cách 1: raw() để lấy đối tượng mô hình
books = Book.objects.raw('''
SELECT b.*, a.name as author_name
FROM library_book b
INNER JOIN library_author a ON b.author_id = a.id
WHERE b.published_date > %s
ORDER BY b.published_date DESC
''', ['2025-01-01'])
return render(request, 'books/list.html', {'books': books})
def execute_custom_query(request):
"""Thực thi trực tiếp cho truy vấn không phải SELECT."""
with connection.cursor() as cursor:
# Truy vấn với phép tổng hợp phức tạp
cursor.execute('''
SELECT
a.name,
COUNT(b.id) as book_count,
AVG(EXTRACT(YEAR FROM b.published_date)) as avg_year
FROM library_author a
LEFT JOIN library_book b ON b.author_id = a.id
GROUP BY a.id, a.name
HAVING COUNT(b.id) > 2
ORDER BY book_count DESC
''')
results = cursor.fetchall()
return render(request, 'stats.html', {'results': results})Truy vấn thô bỏ qua ORM nhưng đánh đổi tính khả chuyển giữa các cơ sở dữ liệu.
Phân tích truy vấn với django-debug-toolbar
Công cụ django-debug-toolbar cho phép xem mọi truy vấn SQL được sinh ra bởi một view Django.
# settings.py - Cấu hình debug toolbar
INSTALLED_APPS = [
# ... các ứng dụng khác
'debug_toolbar',
]
MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
# ... các middleware khác
]
# Hiển thị toolbar cho yêu cầu cục bộ
INTERNAL_IPS = ['127.0.0.1']
DEBUG_TOOLBAR_PANELS = [
'debug_toolbar.panels.sql.SQLPanel', # Truy vấn SQL
'debug_toolbar.panels.timer.TimerPanel', # Thời gian thực thi
'debug_toolbar.panels.cache.CachePanel', # Cache
]Cấu hình này kích hoạt các panel hữu ích nhất cho việc tối ưu.
# Ghi log truy vấn SQL trong môi trường phát triển
# settings.py
LOGGING = {
'version': 1,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'level': 'DEBUG',
'handlers': ['console'],
},
},
}Log này hiển thị từng truy vấn SQL trên console, hữu ích cho việc phát hiện vấn đề N+1.
Cache QuerySet
Với dữ liệu được truy cập thường xuyên nhưng ít thay đổi, cache giúp tránh lặp lại truy vấn.
# views.py
from django.core.cache import cache
from django.views.decorators.cache import cache_page
def list_featured_books(request):
"""Lấy sách nổi bật với cache."""
cache_key = 'featured_books_list'
# Thử lấy từ cache
books = cache.get(cache_key)
if books is None:
# Cache miss: thực thi truy vấn
books = list(
Book.objects.select_related('author')
.filter(featured=True)
.order_by('-published_date')[:10]
)
# Lưu cache trong 5 phút
cache.set(cache_key, books, timeout=300)
return render(request, 'books/featured.html', {'books': books})
# Decorator cache cho toàn bộ view
@cache_page(60 * 15) # Cache 15 phút
def list_all_tags(request):
"""Liệt kê toàn bộ thẻ (dữ liệu ít thay đổi)."""
tags = Tag.objects.annotate(
book_count=Count('books')
).order_by('-book_count')
return render(request, 'tags/list.html', {'tags': tags})Cache giảm tải cho cơ sở dữ liệu với những dữ liệu tĩnh.
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Kết luận
Việc tối ưu truy vấn Django ORM dựa trên một số nguyên tắc cốt lõi:
✅ Đo lường trước khi tối ưu: dùng django-debug-toolbar để phát hiện vấn đề thực sự
✅ Loại bỏ vấn đề N+1: select_related cho ForeignKey, prefetch_related cho ManyToMany
✅ Giới hạn dữ liệu: only(), defer(), values() chỉ nạp những gì cần
✅ Đánh chỉ mục thông minh: tạo chỉ mục cho cột thường lọc hoặc sắp xếp
✅ Cache có chiến lược: dữ liệu ít thay đổi sẽ được hưởng lợi từ cache của Django
✅ Truy vấn thô: chỉ là phương án cuối cho tối ưu đặc thù theo cơ sở dữ liệu
Khi áp dụng có phương pháp, những kỹ thuật này biến một ứng dụng Django chậm thành hệ thống hiệu quả, có khả năng xử lý lượng dữ liệu lớn. Django ORM vẫn là công cụ mạnh mẽ một khi các tinh tế của nó được làm chủ.
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Thẻ
Chia sẻ
Bài viết liên quan

Cau hoi phong van Django va Python: Top 25 nam 2026
25 cau hoi phong van Django va Python thuong gap nhat. ORM, views, middleware, DRF, signals va toi uu hoa voi dap an chi tiet va vi du code.

Django 5: Xây Dụng REST API Với Django REST Framework
Hướng dẫn đầy đủ xây dụng REST API chuyên nghiệp với Django 5 và DRF. Serializers, ViewSets, xác thực JWT và các phương pháp tốt nhất được giải thích chi tiết.

Câu hỏi phỏng vấn Django: ORM, Middleware và DRF chi tiết
Câu hỏi phỏng vấn Django bao gồm tối ưu hóa ORM với select_related và prefetch_related, kiến trúc middleware, hiệu suất serializer Django REST Framework, permissions và các pattern pagination.