Quản lý cơ sở dữ liệu là một trong những khía cạnh quan trọng nhất của phát triển phụ trợ. Cơ sở dữ liệu được tối ưu hóa đúng cách có thể giúp giảm thời gian phản hồi và do đó dẫn đến trải nghiệm người dùng tốt hơn.
Trong bài viết này, chúng tôi sẽ thảo luận về các cách để tối ưu hóa cơ sở dữ liệu cho tốc độ trong các ứng dụng Django. Mặc dù, chúng tôi sẽ không đi sâu vào từng khái niệm riêng lẻ, do đó, vui lòng tham khảo tài liệu chính thức của Django để biết đầy đủ chi tiết.
Hiểu các tập truy vấn trong Django là chìa khóa để tối ưu hóa, do đó, hãy nhớ những điều sau:
len()
, count()
, v.v. Hãy đảm bảo rằng bạn sử dụng chúng tốt nhất.
Lập chỉ mục cơ sở dữ liệu là một kỹ thuật để tăng tốc các truy vấn trong khi lấy các bản ghi từ cơ sở dữ liệu. Khi ứng dụng tăng kích thước, nó có thể chạy chậm lại và người dùng sẽ nhận thấy vì về cơ bản sẽ mất nhiều thời gian hơn để có được dữ liệu cần thiết. Do đó, lập chỉ mục là một hoạt động không thể thương lượng khi làm việc với cơ sở dữ liệu lớn tạo ra một khối lượng lớn dữ liệu.
Lập chỉ mục là một phương pháp sắp xếp một số lượng lớn dữ liệu dựa trên các trường khác nhau. Khi bạn tạo chỉ mục trên một trường trong cơ sở dữ liệu, bạn tạo một cấu trúc dữ liệu khác có chứa giá trị trường cũng như một con trỏ đến bản ghi có liên quan đến nó. Cấu trúc chỉ mục này sau đó được sắp xếp, làm cho các Tìm kiếm Nhị phân có thể thực hiện được.
Ví dụ: đây là một mô hình Django có tên là Bán:
# models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, ) charged_amount = models.PositiveIntegerField()
Lập chỉ mục cơ sở dữ liệu có thể được thêm vào một trường cụ thể trong khi xác định mô hình Django như sau:
# models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, db_index=True, #DB Indexing ) charged_amount = models.PositiveIntegerField()
Nếu bạn chạy di chuyển cho mô hình này, Django sẽ tạo chỉ mục cơ sở dữ liệu trên bảng Bán hàng và nó sẽ bị khóa cho đến khi hoàn thành chỉ mục. Trên thiết lập phát triển cục bộ, với một lượng nhỏ dữ liệu và rất ít kết nối, quá trình di chuyển này có thể xảy ra tức thì, nhưng khi chúng ta nói về môi trường sản xuất, có những bộ dữ liệu lớn với nhiều kết nối đồng thời có thể gây ra thời gian chết vì khóa và tạo một chỉ mục cơ sở dữ liệu có thể mất rất nhiều thời gian.
Bạn cũng có thể tạo một chỉ mục duy nhất cho hai trường như hình dưới đây:
# models.py from django.db import models class Sale(models.Model): sold_at = models.DateTimeField( auto_now_add=True, db_index=True, #DB Indexing ) charged_amount = models.PositiveIntegerField() class Meta: indexes = [ ["sold_at", "charged_amount"]]
Bộ nhớ đệm cơ sở dữ liệu là một trong những cách tiếp cận tốt nhất để nhận được phản hồi nhanh từ cơ sở dữ liệu. Nó đảm bảo rằng ít cuộc gọi hơn được thực hiện đến cơ sở dữ liệu, ngăn ngừa quá tải. Một hoạt động bộ nhớ đệm tiêu chuẩn tuân theo cấu trúc dưới đây:
Django cung cấp một cơ chế bộ nhớ đệm có thể sử dụng các phụ trợ bộ nhớ đệm khác nhau như Memcached và Redis cho phép bạn tránh chạy cùng một truy vấn nhiều lần.
Memcached là một hệ thống trong bộ nhớ mã nguồn mở đảm bảo cung cấp các kết quả được lưu trong bộ nhớ cache trong vòng chưa đầy một phần nghìn giây. Nó rất đơn giản để thiết lập và mở rộng quy mô. Mặt khác, Redis là một giải pháp bộ nhớ đệm mã nguồn mở có các đặc điểm tương tự như Memcached. Hầu hết các ứng dụng ngoại tuyến sử dụng dữ liệu đã lưu trong bộ nhớ cache trước đó, điều này ngụ ý rằng phần lớn các truy vấn không bao giờ đến được cơ sở dữ liệu.
Các phiên của người dùng phải được lưu trong bộ nhớ cache trong ứng dụng Django của bạn và vì Redis duy trì dữ liệu trên đĩa, tất cả các phiên cho người dùng đã đăng nhập bắt nguồn từ bộ nhớ cache chứ không phải cơ sở dữ liệu.
Để sử dụng Memcache với Django, chúng ta cần xác định những điều sau:
ip:port
trong đó ip
là địa chỉ IP của daemon Memcached và port
là cổng mà Memcached đang chạy hoặc URL trỏ đến phiên bản Redis của bạn, sử dụng lược đồ thích hợp.
Để bật bộ nhớ đệm cơ sở dữ liệu với Memcached, hãy cài đặt pymemcache
bằng pip bằng lệnh sau:
pip install pymemcache
Sau đó, bạn có thể định cấu hình cài đặt bộ nhớ cache trong settings.py
của mình như sau:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache', 'LOCATION': '127.0.0.1:11211', } }
Trong ví dụ trên, Memcached đang chạy trên localhost (127.0.0.1) cổng 11211, sử dụng liên kết pymemcache
:
Tương tự, để bật bộ nhớ đệm cơ sở dữ liệu bằng Redis, hãy cài đặt Redis bằng pip bằng lệnh dưới đây:
pip install redis
Sau đó, định cấu hình cài đặt bộ nhớ cache trong settings.py
của bạn bằng cách thêm mã sau:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.redis.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379', } }
Memcached và Redis cũng có thể được sử dụng để lưu trữ mã thông báo xác thực người dùng. Bởi vì mọi người đăng nhập phải cung cấp mã thông báo, tất cả các thủ tục này có thể dẫn đến chi phí cơ sở dữ liệu đáng kể. Sử dụng mã thông báo được lưu trong bộ nhớ cache sẽ giúp truy cập cơ sở dữ liệu nhanh hơn đáng kể.
Thông thường, một bộ truy vấn trong Django sẽ lưu vào bộ nhớ cache kết quả của nó khi quá trình đánh giá xảy ra và đối với bất kỳ hoạt động nào tiếp theo với bộ truy vấn đó, trước tiên nó sẽ kiểm tra xem có kết quả được lưu trong bộ nhớ cache hay không. Tuy nhiên, khi bạn sử dụng iterator()
, nó không kiểm tra bộ nhớ cache và đọc kết quả trực tiếp từ cơ sở dữ liệu, cũng không lưu kết quả vào bộ truy vấn.
Bây giờ, bạn phải tự hỏi làm thế nào điều này là hữu ích. Hãy xem xét một bộ truy vấn trả về một số lượng lớn các đối tượng với nhiều bộ nhớ để lưu vào bộ nhớ cache nhưng chỉ được sử dụng một lần, trong trường hợp này, bạn nên sử dụng một iterator()
.
Ví dụ, trong đoạn mã sau, tất cả các bản ghi sẽ được tìm nạp từ cơ sở dữ liệu và sau đó được tải vào bộ nhớ và sau đó chúng tôi sẽ lặp lại từng bản ghi:
queryset = Product.objects.all() for each in queryset: do_something(each)
Trong khi nếu chúng ta sử dụng iterator()
, Django sẽ giữ kết nối SQL mở và đọc từng bản ghi và gọi do_something()
trước khi đọc bản ghi tiếp theo:
queryset = Product.objects.all().iterator() for each in queryset: do_something(each)
Django tạo một kết nối cơ sở dữ liệu mới cho mỗi yêu cầu và đóng nó sau khi yêu cầu hoàn tất. Hành vi này do CONN_MAX_AGE
gây ra, có giá trị mặc định là 0. Nhưng nó phải được đặt thành bao lâu? Điều đó được xác định bởi khối lượng lưu lượng truy cập trên trang web của bạn; âm lượng càng cao thì càng cần nhiều giây để duy trì kết nối. Bạn nên bắt đầu bằng một số thấp, chẳng hạn như 60.
Bạn cần bao gồm các tùy chọn bổ sung của mình trong OPTIONS
, như được trình bày chi tiết trong
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dashboard', 'USER': 'root', 'PASSWORD': 'root', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'CONN_MAX_AGE': '60', } } }
Biểu thức truy vấn xác định một giá trị hoặc một phép tính có thể được sử dụng trong một thao tác cập nhật, tạo, lọc, sắp xếp theo, chú thích hoặc tổng hợp.
Biểu thức truy vấn dựng sẵn thường được sử dụng trong Django là biểu thức F. Hãy xem nó hoạt động như thế nào và có thể hữu ích như thế nào.
Lưu ý: Các biểu thức này được định nghĩa trong django.db.models.expressions
và django.db.models.aggregates
, nhưng để thuận tiện, chúng có sẵn và thường được nhập từ django.db.models
.
Trong API truy vấn Django, biểu thức F()
được sử dụng để tham chiếu trực tiếp đến các giá trị trường mô hình. Nó cho phép bạn tham chiếu đến các giá trị trường mô hình và thực hiện các hành động cơ sở dữ liệu trên chúng mà không cần phải tìm nạp chúng từ cơ sở dữ liệu và vào bộ nhớ Python. Thay vào đó, Django sử dụng đối tượng F()
để tạo ra một cụm từ SQL xác định hoạt động cơ sở dữ liệu cần thiết.
Ví dụ: giả sử chúng tôi muốn tăng giá của tất cả các sản phẩm lên 20%, thì mã sẽ giống như sau:
products = Product.objects.all() for product in products: product.price *= 1.2 product.save()
Tuy nhiên, nếu chúng ta sử dụng F()
, chúng ta có thể thực hiện việc này trong một truy vấn duy nhất như sau:
from django.db.models import F Product.objects.update(price=F('price') * 1.2)
select_related()
và prefetch_related()
Django cung cấp các đối số select_related()
và prefetch_related()
để tối ưu hóa các tập truy vấn của bạn bằng cách giảm thiểu số lượng yêu cầu cơ sở dữ liệu.
Theo Tài liệu Django chính thức:
select_related()
"theo sau" các mối quan hệ khóa ngoại, chọn dữ liệu đối tượng có liên quan bổ sung khi nó thực hiện truy vấn của nó.
prefetch_related()
thực hiện tìm kiếm riêng biệt cho từng mối quan hệ và thực hiện "tham gia" trong Python.
select_related()
Chúng tôi sử dụng select_related()
khi mục được chọn là một đối tượng duy nhất có nghĩa là chuyển tiếp trường ForeignKey
, OneToOne
và OneToOne
ngược.
Bạn có thể sử dụng select_related()
để tạo một truy vấn duy nhất trả về tất cả các đối tượng liên quan cho một trường hợp duy nhất cho các kết nối một-nhiều và một-một. Khi truy vấn được thực hiện, select_related()
truy xuất bất kỳ dữ liệu đối tượng liên quan bổ sung nào từ các mối quan hệ khóa ngoại.
select_related()
hoạt động bằng cách tạo một phép nối SQL và bao gồm các cột của đối tượng liên quan trong biểu thức SELECT
. Kết quả là select_related()
trả về các mục có liên quan trong cùng một truy vấn cơ sở dữ liệu.
Mặc dù select_related()
tạo ra một truy vấn phức tạp hơn, dữ liệu thu được được lưu vào bộ nhớ đệm, do đó việc xử lý dữ liệu thu được không cần bất kỳ yêu cầu cơ sở dữ liệu bổ sung nào.
Cú pháp đơn giản như sau:
queryset = Tweet.objects.select_related('owner').all()
prefetch_related()
Ngược lại, prefetch_related()
được sử dụng cho các kết nối nhiều-nhiều và nhiều-một. Nó tạo ra một truy vấn duy nhất bao gồm tất cả các mô hình và bộ lọc được đưa ra trong truy vấn.
Cú pháp đơn giản như sau:
Book.objects.prefetch_related('author').get(id=1).author.first_name
LƯU Ý: Không nên xử lý mối quan hệ ManyToMany bằng SQL vì nhiều vấn đề về hiệu suất có thể xuất hiện khi xử lý các bảng lớn. Đó là lý do tại sao phương thức liên quan đến prefetch_ liên kết các bảng bên trong Python để tránh tạo các phép nối SQL lớn.
Đọc chi tiết về sự khác biệt giữa select_related()
và prefetch_related()
tại đây .
bulk_create()
và bulk_update()
bulk_create()
là một phương thức tạo danh sách các đối tượng được cung cấp vào cơ sở dữ liệu bằng một truy vấn. Tương tự, bulk_update()
là một phương thức cập nhật các trường nhất định trên các cá thể mô hình được cung cấp bằng một truy vấn.
Ví dụ: nếu chúng ta có một mô hình bài viết như hình dưới đây:
class Post(models.Model): title = models.CharField(max_length=300, unique=True) time = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title
Bây giờ, giả sử chúng ta muốn thêm nhiều bản ghi dữ liệu vào mô hình này, sau đó chúng ta có thể sử dụng bulk_create()
như sau:
#articles articles = [Post(title="Hello python"), Post(title="Hello django"), Post(title="Hello bulk")] #insert data Post.objects.bulk_create(articles)
Và đầu ra sẽ như thế này:
>>> Post.objects.all() <QuerySet [<Post: Hello python>, <Post: Hello django>, <Post: Hello bulk>]>
Và nếu chúng ta muốn cập nhật dữ liệu, thì chúng ta có thể sử dụng bulk_update()
như sau:
update_queries = [] a = Post.objects.get(id=14) b = Post.objects.get(id=15) c = Post.objects.get(id=16) #set update value a.title="Hello python updated" b.title="Hello django updated" c.title="Hello bulk updated" #append update_queries.extend((a, b, c)) Post.objects.bulk_update(update_queries, ['title'])
Và đầu ra sẽ như thế này:
>>> Post.objects.all() <QuerySet [<Post: Hello python updated>, <Post: Hello django updated>, <Post: Hello bulk updated>]>
Trong bài viết này, chúng tôi đã đề cập đến các mẹo để tối ưu hóa hiệu suất của cơ sở dữ liệu, giảm tắc nghẽn và tiết kiệm tài nguyên trong ứng dụng Django.
Tôi hy vọng bạn thấy nó hữu ích. Hãy đọc tiếp!