2011-07-05 30 views
18

Trong ứng dụng Django của tôi rất thường xuyên, tôi cần phải làm một cái gì đó tương tự như get_or_create(). Ví dụ:Django: làm thế nào để làm get_or_create() theo một cách an toàn?

Người dùng gửi thẻ. Cần xem nếu thẻ đó đã có trong cơ sở dữ liệu chưa. Nếu không, hãy tạo một bản ghi mới cho nó. Nếu , chỉ cần cập nhật bản ghi hiện tại .

Nhưng nhìn vào tài liệu cho get_or_create() có vẻ như nó không an toàn. Thread A kiểm tra và tìm Record X không tồn tại. Sau đó, Thread B kiểm tra và thấy rằng Record X không tồn tại. Bây giờ cả Chủ đề A và Chủ đề B sẽ tạo một Bản ghi mới X.

Đây phải là một tình huống rất phổ biến. Làm cách nào để xử lý nó theo cách an toàn?

+1

Một trong hai chủ đề sẽ nhận được lỗi bản ghi trùng lặp và ngoại lệ. Sẽ không có dữ liệu trùng lặp. –

Trả lời

10

Đây phải là tình huống rất phổ biến. Làm cách nào để xử lý nó theo cách an toàn?

Có.

Giải pháp "chuẩn" trong SQL đơn giản là cố tạo bản ghi. Nếu nó hoạt động, đó là tốt. Tiếp tục đi.

Nếu cố gắng tạo bản ghi nhận ngoại lệ "trùng lặp" từ RDBMS, thì hãy thực hiện lệnh SELECT và tiếp tục.

Django, tuy nhiên, có lớp ORM, với bộ nhớ cache riêng. Vì vậy, logic được đảo ngược để làm cho các trường hợp phổ biến làm việc trực tiếp và nhanh chóng và trường hợp không phổ biến (bản sao) tăng một ngoại lệ hiếm.

+0

Tôi đã gặp các mục trùng lặp trong cơ sở dữ liệu postgres nên đã có duy nhất khi tôi đang sử dụng 'get_or_create' trong phương thức xem nhận được yêu cầu đồng thời, tôi nghĩ đây là mối quan tâm hợp lệ. –

+1

@A Lee: Với các ràng buộc chỉ mục duy nhất được xác định chính xác, không thể sao chép được một bản sao. Làm thế nào bạn có thể phá vỡ ràng buộc chỉ số duy nhất? –

+0

Ah, điều đó sẽ khắc phục vấn đề ngay bây giờ mà tôi nghĩ về nó rõ ràng hơn. 'Get_or_create' đã sử dụng nhiều trường và tôi đã chuyển nó sang một đường dẫn thực thi khác thay vì để nó trong khung nhìn và thêm một ràng buộc duy nhất trên nhiều trường mô hình. –

3

thử transaction.commit_on_success trang trí cho callable nơi bạn đang cố gắng get_or_create (** kwargs)

"Sử dụng trang trí commit_on_success sử dụng một giao dịch duy nhất cho tất cả các công việc thực hiện trong một function.If hàm trả về thành công, sau đó Django sẽ cam kết tất cả các công việc thực hiện trong chức năng tại thời điểm đó. Nếu chức năng làm tăng một ngoại lệ, mặc dù, Django sẽ quay trở lại giao dịch. "

ngoài nó, trong cuộc gọi đồng thời tới get_or_create, cả hai chủ đề cố gắng lấy đối tượng với đối số được truyền cho nó (ngoại trừ "mặc định" arg là dict được sử dụng trong khi tạo cuộc gọi trong trường hợp get() không truy xuất được bất kỳ đối tượng nào). trong trường hợp thất bại cả hai chủ đề cố gắng tạo đối tượng dẫn đến nhiều đối tượng trùng lặp trừ khi một số duy nhất/duy nhất cùng nhau được thực hiện ở cấp cơ sở dữ liệu với (các) trường được sử dụng trong lệnh gọi get().

nó cũng tương tự như bài này How do I deal with this race condition in django?

+1

Điều này không thực sự cần thiết, hãy xem các câu trả lời khác của tôi để có cách xử lý tốt hơn. –

27

Từ 2013 hoặc lâu hơn, get_or_create là nguyên tử, vì vậy nó xử lý đồng thời độc đáo:

Phương pháp này là nguyên tử giả sử dụng đúng, chính xác cơ sở dữ liệu cấu hình, và hành vi chính xác của cơ sở dữ liệu bên dưới. Tuy nhiên, nếu tính duy nhất không được thực thi ở cấp cơ sở dữ liệu cho các sốkwarg được sử dụng trong lệnh gọi get_or_create (xem duy nhất hoặc duy nhất_together), phương pháp này dễ bị điều kiện chủng tộc có thể dẫn đến nhiều hàng . được chèn đồng thời.

Nếu bạn đang sử dụng MySQL, hãy chắc chắn để sử dụng ĐỌC CAM KẾT mức cô lập hơn là đọc lặp lại (mặc định), nếu không bạn có thể thấy trường hợp get_or_create sẽ nâng cao một IntegrityError nhưng đối tượng sẽ không xuất hiện trong một cuộc gọi get() tiếp theo.

Từ: https://docs.djangoproject.com/en/dev/ref/models/querysets/#get-or-create

Dưới đây là một ví dụ về cách bạn có thể làm điều đó:

Xác định một mô hình với một trong hai độc đáo = True:

class MyModel(models.Model): 
    slug = models.SlugField(max_length=255, unique=True) 
    name = models.CharField(max_length=255) 

MyModel.objects.get_or_create(slug=<user_slug_here>, defaults={"name": <user_name_here>}) 

... hoặc bằng cách sử dụng unique_togheter :

class MyModel(models.Model): 
    prefix = models.CharField(max_length=3) 
    slug = models.SlugField(max_length=255) 
    name = models.CharField(max_length=255) 

    class Meta: 
     unique_together = ("prefix", "slug") 

MyModel.objects.get_or_create(prefix=<user_prefix_here>, slug=<user_slug_here>, defaults={"name": <user_name_here>}) 

Lưu ý cách các trường không phải là duy nhất nằm trong dict mặc định, KHÔNG nằm trong số các trường duy nhất trong get_or_create. Điều này sẽ đảm bảo tạo của bạn là nguyên tử.

Đây là cách nó được triển khai trong Django: https://github.com/django/django/blob/fd60e6c8878986a102f0125d9cdf61c717605cf1/django/db/models/query.py#L466 - Thử tạo đối tượng, nắm bắt một IntegrityError cuối cùng và trả lại bản sao trong trường hợp đó. Nói cách khác: xử lý nguyên tử trong cơ sở dữ liệu.

+2

Nhờ bất cứ ai đã bình chọn cho câu trả lời này, tôi đã thêm một số ví dụ để làm cho nó dễ hiểu hơn. –

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