- vừa được xem lúc

Giải quyết bài toán N+1 queries trong Django App

0 0 50

Người đăng: Nguyen Thi Huong

Theo Viblo Asia

Django framework cho phép bạn xây dựng ứng dụng web cực kỳ nhanh. Một trong những tính năng tốt nhất của nó là Object-relational mapper (ORM) - Trình ánh xạ quan hệ đối tượng, cho phép tạo ra các truy vấn đến DB mà không cần viết bất kỳ câu lênh SQL thuần nào. Django cho phép viết các truy vấn bằng python, sau đó sẽ cố gắng chuyển các câu lệnh đó thành SQL hiệu quả, nhưng đôi khi kết quả lại "kém lý tưởng".

Một trong những vấn đề phổ biến của DB là ORM có thể gây ra N+1 truy vấn - một vấn đề không còn xa lạ, nó ảnh hưởng đến hiệu suất khi làm việc với DB, bao gồm 1 truy vấn ban đầu và mỗi một dòng trong kết quả đó lại sinh ra một truy vấn khác (N). Nó thường xảy ra khi trong DB chứa các bảng có quan hệ cha-con (Select tất cả các đối tượng cha/con mong muốn, và sau đó lại lặp qua mỗi bản ghi và select đối tượng con/cha). Ứng dụng web có thể hoạt động tốt với lượng dữ liệu nhỏ, nhưng khi dữ liệu tăng lên thì lượng truy vấn cũng sẽ tăng lên và ảnh hưởng đến hiệu năng của hệ thống.

Mỗi framework sẽ có nhiều cách để hỗ trợ giải quyết bài toán này và Django cũng không ngoại lệ. Cùng đi vào chi tiết để hiểu rõ hơn.

Cài đặt Django debug Toolbar để xem truy vấn sql khi thao tác với dữ liệu bằng shell

Ví dụ: Có 2 bảng với các trường cơ bản, trong đó author và book có quan hệ 1 - n:

  • authors (id: int, first_name: varchar(50), last_name: varchar(50), date_of_birth: date)
  • books (id: int, title: varchar(255), description: text, author: int)

Thiết kế model trong Django:

# /app_name/models.py
from django.db import models class Author: first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) date_of_birth = models.DateField(null=True, blank=True) def __str__(self): return f"{self.id}: {self.name}" class Book: title = models.CharField(max_length=255) description = models.TextField() author = models.ForeignKey("Author", on_delete=models.SET_NULL, null=True) # khóa ngoại đến bảng `authors` def __str__(self): return f"{self.id}: {self.title}"

(N+1) truy vấn

Bài toán 1: In ra tên tác giả của các cuốn sách có tiêu đề bắt đầu với từ "Book", thực hiện xử lý như sau:

# Lấy danh sách books mà tiêu đề bắt đầu chuỗi "Book"
books = Book.objects.filter(title__startswith="Book") # In tên tác giả tương ứng cho mỗi sách
for book in books: print(f"Book ID: {book.id} - Author name: {book.author.first_name}, {book.author.last_name}")

Quan sát câu truy vấn sql: image.png

Có thể thấy 4 câu truy vấn: 1 câu truy vấn lấy từ bảng books theo điều kiện và 3 câu truy vấn lấy từ bảng authors với điều kiện authors.id = books.author_id tương ứng.

Bài toán 2: In ra tiêu đề sách của các tác giả có tên "Author"

authors = Author.objects.filter(first_name="Author") # 
for author in authors: books = author.book_set.all() for book in books: print(book.title)

Quan sát có 4 câu truy vấn: 1 câu truy vấn lấy từ bảng authors theo điều kiện và 3 câu truy vấn lấy từ bảng books với điều kiện books.author_id = authors.id tương ứng

Quan sát kết quả: image.png

Giải quyết bài toán

Django cung cấp 2 phương thức của Queryset: select_related()prefetch_related() để giải quyết bài toán (N+1) truy vấn. 2 phương thức này hoạt động tương tự, đều fetch model quan hệ cùng với truy vấn ban đầu.

select_related() prefetch_related()
Trả về QuerySet QuerySet
Quan hệ one-to-one (1-1), one-to-many (1-n) one-to-one (1-1), one-to-many (1-n), many-to-many (n-n), many-to-one (n-1)
Truy vấn 1 truy vấn: tạo ra câu SQL join và bao gồm các fields của đối tượng liên quan trong câu SELECT 2 truy vấn riêng biệt cho từng đối lượng

==> Cụ thể với từng bài toán đã nêu ở trên, chúng giải quyết như sau:

Bài toán 1: sử dụng được cả 2 phương thức vì fetch author - 1 từ book - n

  • Phương thức select_related()
books = Book.objects.filter(title__startswith="Book").select_related("author")
for book in books: print(f"Book ID: {book.id} - Author name: {book.author.first_name}, {book.author.last_name}")

Truy vấn: CHỈ sử dụng 1 câu truy vấn duy nhất với kỹ thuật LEFT OUTER JOIN image.png

  • Phương thức prefetch_related()
books = Book.objects.filter(title__startswith="Book").prefetch_related("author")
for book in books: print(f"Book ID: {book.id} - Author name: {book.author.first_name}, {book.author.last_name}")

Truy vấn: sử dụng 2 câu truy vấn, 1 câu truy vấn tìm books, 1 câu truy vấn trong bảng authors dùng kỹ thuật WHERE ... IN () image.png

Bài toán 2: Chỉ sử dụng được phương thức prefetch_related() vì fetch book - n từ author - 1

  • Phương thức prefetch_related()
authors = Author.objects.filter(first_name="Author").prefetch_related("book_set")
for author in authors: books = author.book_set.all() for book in books: print(book.title)

Truy vấn: image.png

Vậy tùy từng bài toán cụ thể mà lựa chọn dùng 2 phương thức trên, hoặc kết hợp cả 2 cho phù hợp 😀

Tham khảo: https://docs.djangoproject.com/en/3.2/ref/models/querysets/#select-related

Bình luận

Bài viết tương tự

- vừa được xem lúc

Thao tác với File trong Python

Python cung cấp các chức năng cơ bản và phương thức cần thiết để thao tác các file. Bài viết này tôi xin giới thiệu những thao tác cơ bản nhất với file trong Python.

0 0 63

- vừa được xem lúc

Tập tành crawl dữ liệu với Scrapy Framework

Lời mở đầu. Chào mọi người, mấy hôm nay mình có tìm hiểu được 1 chút về Scrapy nên muốn viết vài dòng để xem mình đã học được những gì và làm 1 demo nho nhỏ.

0 0 166

- vừa được xem lúc

Sử dụng Misoca API (oauth2) với Python

Với bài viết này giúp chúng ta có thể nắm được. ・Tìm hiểu cách xử lý API misoca bằng Python.

0 0 49

- vừa được xem lúc

[Series Pandas DataFrame] Phân tích dữ liệu cùng Pandas (Phần 3)

Tiếp tục phần 2 của series Pandas DataFrame nào. Let's go!!. Ở phần trước, các bạn đã biết được cách lấy dữ liệu một row hoặc column trong Pandas DataFame rồi phải không nào. 6 Hoc.

0 0 63

- vừa được xem lúc

Lập trình socket bằng Python

Socket là gì. Một chức năng khác của socket là giúp các tầng TCP hoặc TCP Layer định danh ứng dụng mà dữ liệu sẽ được gửi tới thông qua sự ràng buộc với một cổng port (thể hiện là một con số cụ thể), từ đó tiến hành kết nối giữa client và server.

0 0 79

- vừa được xem lúc

[Series Pandas DataFrame] Phân tích dữ liệu cùng Pandas (Phần 2)

Nào, chúng ta cùng đến với phần 2 của series Pandas DataFrame. Truy xuất Labels và Data. Bạn đã biết cách khởi tạo 1 DataFrame của mình, và giờ bạn có thể truy xuất thông tin từ đó. Với Pandas, bạn có thể thực hiện các thao tác sau:.

0 0 95