2008-11-11 26 views
24

Tôi đang cố gắng để thực hiện (những gì tôi nghĩ là) một mô hình dữ liệu khá đơn giản đối với một bộ đếm:Hoạt động nguyên tử ở Django?

class VisitorDayTypeCounter(models.Model): 
    visitType = models.CharField(max_length=60) 
    visitDate = models.DateField('Visit Date') 
    counter = models.IntegerField() 

Khi ai đó đi qua, nó sẽ tìm kiếm một hàng đó phù hợp với visitType và visitDate; nếu hàng này không tồn tại, nó sẽ được tạo với counter = 0.

Sau đó, chúng tôi tăng bộ đếm và lưu.

Mối quan tâm của tôi là quá trình này hoàn toàn là cuộc đua. Hai yêu cầu có thể đồng thời kiểm tra xem thực thể có ở đó không và cả hai đều có thể tạo ra nó. Giữa việc đọc bộ đếm và lưu kết quả, một yêu cầu khác có thể đi qua và tăng nó (dẫn đến số lượng mất).

Cho đến nay tôi chưa thực sự tìm thấy một cách tốt về điều này, hoặc trong tài liệu Django hoặc trong hướng dẫn (trên thực tế, có vẻ như hướng dẫn có điều kiện chủng tộc trong phần Bình chọn của nó).

Làm cách nào để thực hiện điều này một cách an toàn?

Trả lời

1

Đây là một chút hack. SQL thô sẽ làm cho mã của bạn kém di động hơn, nhưng nó sẽ loại bỏ tình trạng của cuộc đua khi tăng số lượt truy cập. Về lý thuyết, điều này sẽ tăng bộ đếm bất kỳ lúc nào bạn thực hiện truy vấn. Tôi đã không thử nghiệm này, vì vậy bạn nên chắc chắn rằng danh sách được nội suy trong truy vấn đúng cách.

class VisitorDayTypeCounterManager(models.Manager): 
    def get_query_set(self): 
     qs = super(VisitorDayTypeCounterManager, self).get_query_set() 

     from django.db import connection 
     cursor = connection.cursor() 

     pk_list = qs.values_list('id', flat=True) 
     cursor.execute('UPDATE table_name SET counter = counter + 1 WHERE id IN %s', [pk_list]) 

     return qs 

class VisitorDayTypeCounter(models.Model): 
    ... 

    objects = VisitorDayTypeCounterManager() 
+0

Vẫn không thể là cơ sở dữ liệu sẽ thực hiện truy vấn này qua hai kết nối riêng lẻ đồng thời và vẫn có điều kiện chủng tộc (xác suất thấp hơn)?Tất cả phụ thuộc vào các giao dịch ẩn xung quanh truy vấn này được lớp kết nối tạo ra nguyên tử op. –

+0

Nếu bạn xem "Tại sao tôi Ghét Django" phát biểu từ DjangoCon, kiểu truy vấn này được đưa ra như là một cách thích hợp, điều kiện chủng tộc miễn phí để thực hiện một gia tăng trong SQL (hitch là ORM của Django không thể làm điều đó cho bạn). – iconoplast

+0

Tôi sẽ xem các trang trình bày của bạn ... bạn đã xác nhận khá nhiều sự nghi ngờ rằng ORM sẽ không tự làm điều đó. Cảm ơn sự giúp đỡ! –

5

Hai gợi ý:

Thêm một unique_together để mô hình của bạn, và quấn tạo trong xử lý ngoại lệ để đón các bản sao:

class VisitorDayTypeCounter(models.Model): 
    visitType = models.CharField(max_length=60) 
    visitDate = models.DateField('Visit Date') 
    counter = models.IntegerField() 
    class Meta: 
     unique_together = (('visitType', 'visitDate')) 

Sau này, bạn stlll có thể có một tình trạng chủng tộc nhỏ trên cập nhật của bộ đếm. Nếu bạn nhận được đủ lưu lượng truy cập để được quan tâm về điều đó, tôi sẽ đề nghị xem xét giao dịch để kiểm soát cơ sở dữ liệu hạt mịn hơn. Tôi không nghĩ rằng ORM có hỗ trợ trực tiếp cho khóa/đồng bộ hóa. Tài liệu giao dịch có sẵn here.

+0

Các unique_together chắc chắn khiến tôi cảm thấy một chút thoải mái hơn. Có khả năng, sẽ không bao giờ có đủ lưu lượng truy cập vào điều này để gây ra cuộc đua để đạt được hit, nhưng kể từ khi tôi đang học Django cùng một lúc, tôi figured tôi muốn "làm điều đó đúng". Cảm ơn sự giúp đỡ! –

+0

Vâng, tôi nghe bạn. Có lẽ ai đó khác ở đây sẽ nhận thức được một tính năng ORM để xử lý việc này, hoặc có thể xóa bỏ nếu một số phần tích hợp sẵn có an toàn trong trường hợp này. –

1

Tại sao không sử dụng cơ sở dữ liệu làm lớp đồng thời? Thêm khóa chính hoặc ràng buộc duy nhất bảng vào visitType và visitDate. Nếu tôi không nhầm, django không hỗ trợ chính xác điều này trong lớp Mô hình cơ sở dữ liệu của họ hoặc ít nhất tôi đã không nhìn thấy một ví dụ.

Một khi bạn đã thêm các hạn chế/chìa khóa để bảng, sau đó tất cả các bạn phải làm là:

  1. kiểm tra nếu hàng là ở đó. nếu có, hãy lấy nó.
  2. chèn hàng. nếu không có lỗi, bạn sẽ ổn và có thể tiếp tục.
  3. nếu có lỗi (tức là điều kiện chủng tộc), tìm nạp lại hàng. nếu không có hàng, thì đó là lỗi chính hãng. Nếu không, bạn ổn.

Thật khó chịu khi thực hiện theo cách này, nhưng dường như đủ nhanh và sẽ bao gồm hầu hết các trường hợp.

+0

Nó không xử lý các trường hợp mà hai người đi để cập nhật các truy cập cùng một lúc. –

0

Bạn nên sử dụng giao dịch cơ sở dữ liệu để tránh loại tình trạng chủng tộc này. Giao dịch cho phép bạn thực hiện toàn bộ hoạt động tạo, đọc, tăng và lưu bộ đếm trên cơ sở "tất cả hoặc không có gì". Nếu bất cứ điều gì sai, nó sẽ quay trở lại toàn bộ điều và bạn có thể thử lại.

Kiểm tra Django docs. Có một mặt hàng trung gian giao dịch hoặc bạn có thể sử dụng trang trí xung quanh lượt xem hoặc phương pháp để tạo giao dịch.

+0

Tôi đồng ý rằng các giao dịch có vẻ như câu trả lời ở đây, nhưng không rõ là chức năng có thực sự giải quyết được vấn đề tăng hay không - lệnh SELECT để có được hàng vẫn sẽ thành công, và UPDATE để thay đổi giá trị của bộ đếm sẽ vẫn thành công. Nếu tôi sai, một ví dụ sẽ là tuyệt vời. –

+0

Bạn sẽ cần phải khóa bàn trong khi chọn để làm theo cách này, và như đã đề cập bởi Sam sẽ kéo hiệu suất của bạn xuống. Đây là cách tốt nhất nếu bạn không làm hỏng quầy thường xuyên. –

12

Nếu bạn thực sự muốn bộ đếm chính xác, bạn có thể sử dụng giao dịch nhưng số tiền đồng thời yêu cầu sẽ thực sự kéo ứng dụng và cơ sở dữ liệu của bạn xuống dưới bất kỳ tải trọng đáng kể nào.Thay vào đó, hãy nghĩ đến việc tiếp cận với một cách tiếp cận phong cách nhắn tin hơn và chỉ giữ các bản ghi bán phá giá vào một bảng cho mỗi lần truy cập mà bạn muốn tăng bộ đếm. Sau đó, khi bạn muốn tổng số lượt truy cập thực hiện đếm trên bảng lượt truy cập. Bạn cũng có thể có một quy trình nền chạy bất kỳ số lần nào trong một ngày có tổng số lượt truy cập và sau đó lưu trữ nó trong bảng cha. Để tiết kiệm không gian, nó cũng sẽ xóa bất kỳ bản ghi nào từ bảng truy cập con mà nó tóm tắt. Bạn sẽ cắt giảm chi phí đồng thời của bạn một số tiền rất lớn nếu bạn không có nhiều đại lý cạnh tranh cho các nguồn tài nguyên tương tự (bộ đếm).

+0

Xin chào! Tôi đã làm công việc App Engine chủ yếu, và tôi bị treo lên trên "giao dịch chỉ hành động trên một mục" và "làm chức năng tổng hợp là rất tốn kém". Đó là một cách thực sự đơn giản để giải quyết vấn đề. Cảm ơn! –

+0

Tôi cho rằng nó phụ thuộc vào việc liệu quá trình này sẽ được đọc nặng hay viết nặng. Số lượng sẽ được đọc nhiều hơn thường xuyên hơn chúng sẽ được tăng lên trong hệ thống của tôi, do đó, đối với vấn đề như đã nêu, đây có thể không phải là kế hoạch tốt nhất. Tuy nhiên, nó giải quyết các mối quan tâm khác tôi đã có, vì vậy cảm ơn! –

+0

Tùy thuộc vào cách đếm số lượng cũ có thể được phép, bạn có thể có một quá trình nền tổng hợp chúng thường xuyên như vậy. Sau đó, bạn sẽ không thực hiện tổng hợp theo yêu cầu. –

6

Bạn có thể sử dụng bản vá từ http://code.djangoproject.com/ticket/2705 để khóa cấp cơ sở dữ liệu hỗ trợ.

Với vá mã này sẽ là nguyên tử:

visitors = VisitorDayTypeCounter.objects.get(day=curday).for_update() 
visitors.counter += 1 
visitors.save() 
+0

Điều đó thật tuyệt vời. Tôi không thấy điều đó khi tôi hỏi câu hỏi đầu tiên (3 năm trước!) –

26

Tính đến Django 1.1 bạn có thể sử dụng biểu thức của ORM F().

from django.db.models import F 
product = Product.objects.get(name='Venezuelan Beaver Cheese') 
product.number_sold = F('number_sold') + 1 
product.save() 

Để biết thêm chi tiết hãy xem tài liệu:

https://docs.djangoproject.com/en/1.8/ref/models/instances/#updating-attributes-based-on-existing-fields

https://docs.djangoproject.com/en/1.8/ref/models/expressions/#django.db.models.F

+0

Thật tuyệt! Đó là một cách tiếp cận tuyệt vời. Nếu chỉ có điều đó đã có khi tôi đang làm việc trên dự án này. –

+0

Thực sự rất thú vị, cảm ơn thông tin! –

+0

Đối với các cài đặt Django hiện đại, đây là câu trả lời đúng và cần được phản ánh như vậy bởi OP. – claymation

Các vấn đề liên quan