Khắc phục vấn đề truy vấn N+1 trong Django
By khoanc, at: 10:52 Ngày 01 tháng 6 năm 2024
Thời gian đọc ước tính: __READING_TIME__ minutes


Là một nhà phát triển dày dặn kinh nghiệm, tôi hiểu sự khó chịu của vấn đề truy vấn N+1. Đó là kẻ giết hiệu năng âm thầm, nơi mã của bạn chạy một truy vấn cho một danh sách và sau đó là các truy vấn bổ sung cho mỗi mục trong danh sách đó. Hãy cùng tìm hiểu cách bạn có thể phát hiện và khắc phục điều này trong Django, giúp ứng dụng của bạn chạy mượt mà và nhanh hơn.
Hiểu về Truy vấn N+1
Hãy tưởng tượng bạn có một danh sách các phường, mỗi phường thuộc về một quận. Việc lấy dữ liệu phường và thông tin quận của chúng có thể trông như thế này:
wards = Ward.objects.all()
for ward in wards:
name = ward.district.name
Điều này dẫn đến một truy vấn cho các phường và một truy vấn cho mỗi tên quận của phường, dẫn đến vấn đề N+1.
Phát hiện Truy vấn N+1
Các công cụ như Django Debug Toolbar, Sentry và Scout APM có thể giúp phát hiện các vấn đề này.
Django Debug Toolbar, ví dụ, làm nổi bật các truy vấn quá mức trên các trang của bạn. Đội ngũ Glinteco của chúng tôi đã chỉ ra nhiều gói Django hữu ích trong bài viết này.
Giải quyết với select_related()
Đối với các mối quan hệ khóa ngoại, hãy sử dụng select_related()
để thực hiện nối SQL và lấy các đối tượng liên quan trong một truy vấn duy nhất:
wards = Ward.objects.select_related('district').all()
for ward in wards:
print(ward.district.name)
Điều này tải trước trường district
cho mỗi phường, giảm số lượng truy vấn.
Giải quyết với prefetch_related()
Đối với các mối quan hệ nhiều-đối-nhiều và khóa ngoại ngược, prefetch_related()
là người bạn của bạn. Nó thực hiện một tìm kiếm riêng biệt và thực hiện 'kết nối' trong Python:
districts = District.objects.prefetch_related('wards').all()
for district in district:
wards = district.wards.all()
Điều này lấy tất cả các phường cho tất cả các quận trong hai truy vấn, bất kể số lượng quận.
Ví dụ thực tế
Giả sử bạn có các tác giả và sách của họ. Dưới đây là cách lấy chúng một cách hiệu quả:
authors = Author.objects.prefetch_related('book_set').all()
for author in authors:
books = author.book_set.all()
Kết luận
Bằng cách sử dụng select_related()
và prefetch_related()
, bạn có thể giảm thiểu vấn đề truy vấn N+1 và cải thiện đáng kể hiệu năng của ứng dụng Django. Hãy nhớ rằng, các công cụ như Django Debug Toolbar, Sentry, và Scout APM vô cùng quý giá trong việc phát hiện các vấn đề này.