2013-10-30 21 views
8

Vấn đề của tôi như sau:Bàn phím Django ORM và bảng khóa

Tôi có đại lý ô tô A và bảng db có tên sold_cars. Khi một chiếc xe đang được bán, tôi tạo mục nhập trong bảng này.

Bảng có cột số nguyên có tên order_no. Nó phải là duy nhất trong xe ô tô được bán bởi đại lý.

Vì vậy, nếu đại lý A bán xe hơi a, b and c, thì cột này phải là 1, 2, 3. Tôi phải sử dụng cột này và không phải là khóa chính vì tôi không muốn có bất kỳ lỗ nào trong số của tôi - đại lý A và B (có thể được thêm sau) phải có số thứ tự 1, 2, 3 và không phải là A : 1, 3, 5, và B: 2, 4, 6. Vì vậy, ... tôi chọn order_no lớn nhất cuối cùng cho đại lý nhất định, tăng nó bằng 1 và tiết kiệm.

Vấn đề là hai người đã mua xe từ đại lý A trong cùng một phần nghìn giây và cả hai đơn hàng đều có cùng thứ tự order_no. Lời khuyên nào? Tôi đã nghĩ đến việc đóng quá trình này trong một khối giao dịch và khóa bảng này cho đến khi giao dịch hoàn tất, nhưng không thể tìm thấy bất kỳ thông tin nào về cách thực hiện điều đó.

Trả lời

6

Tôi nghĩ đoạn mã này đáp ứng nhu cầu của bạn, giả sử bạn đang sử dụng MySQL. Nếu không, bạn có thể cần phải tinh chỉnh cú pháp một chút, nhưng ý tưởng vẫn nên hoạt động.

Nguồn: Locking tables

class LockingManager(models.Manager): 
    """ Add lock/unlock functionality to manager. 

    Example:: 

     class Job(models.Model): 

      manager = LockingManager() 

      counter = models.IntegerField(null=True, default=0) 

      @staticmethod 
      def do_atomic_update(job_id) 
       ''' Updates job integer, keeping it below 5 ''' 
       try: 
        # Ensure only one HTTP request can do this update at once. 
        Job.objects.lock() 

        job = Job.object.get(id=job_id) 
        # If we don't lock the tables two simultanous 
        # requests might both increase the counter 
        # going over 5 
        if job.counter < 5: 
         job.counter += 1           
         job.save() 

       finally: 
        Job.objects.unlock() 


    """  

    def lock(self): 
     """ Lock table. 

     Locks the object model table so that atomic update is possible. 
     Simulatenous database access request pend until the lock is unlock()'ed. 

     Note: If you need to lock multiple tables, you need to do lock them 
     all in one SQL clause and this function is not enough. To avoid 
     dead lock, all tables must be locked in the same order. 

     See http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html 
     """ 
     cursor = connection.cursor() 
     table = self.model._meta.db_table 
     logger.debug("Locking table %s" % table) 
     cursor.execute("LOCK TABLES %s WRITE" % table) 
     row = cursor.fetchone() 
     return row 

    def unlock(self): 
     """ Unlock the table. """ 
     cursor = connection.cursor() 
     table = self.model._meta.db_table 
     cursor.execute("UNLOCK TABLES") 
     row = cursor.fetchone() 
     return row 
11

Tôi biết câu hỏi này là lớn hơn một chút, nhưng tôi chỉ có cùng một vấn đề và muốn chia sẻ bài học của tôi.

Tôi đã không hoàn toàn hài lòng với câu trả lời của st0nes, vì (ít nhất là cho postgres) một tuyên bố LOCK TABLE chỉ có thể được phát hành trong một giao dịch. Và mặc dù ở Django thường gần như tất cả mọi thứ xảy ra trong một giao dịch, điều này LockingManager không đảm bảo, rằng bạn thực sự là trong một giao dịch, ít nhất là để hiểu biết của tôi. Ngoài ra tôi không muốn thay đổi hoàn toàn Mô hình Manager chỉ để có thể khóa tại một vị trí và do đó tôi đang tìm kiếm thứ gì đó hoạt động giống như with transaction.atomic():, nhưng cũng khóa Mô hình cụ thể.

Vì vậy, tôi đã đưa ra với điều này:

from django.conf import settings 
from django.db import DEFAULT_DB_ALIAS 
from django.db.transaction import Atomic, get_connection 


class LockedAtomicTransaction(Atomic): 
    """ 
    Does a atomic transaction, but also locks the entire table for any transactions, for the duration of this 
    transaction. Although this is the only way to avoid concurrency issues in certain situations, it should be used with 
    caution, since it has impacts on performance, for obvious reasons... 
    """ 
    def __init__(self, model, using=None, savepoint=None): 
     if using is None: 
      using = DEFAULT_DB_ALIAS 
     super().__init__(using, savepoint) 
     self.model = model 

    def __enter__(self): 
     super(LockedAtomicTransaction, self).__enter__() 

     # Make sure not to lock, when sqlite is used, or you'll run into problems while running tests!!! 
     if settings.DATABASES[self.using]['ENGINE'] != 'django.db.backends.sqlite3': 
      cursor = None 
      try: 
       cursor = get_connection(self.using).cursor() 
       cursor.execute(
        'LOCK TABLE {db_table_name}'.format(db_table_name=self.model._meta.db_table) 
       ) 
      finally: 
       if cursor and not cursor.closed: 
        cursor.close() 

Vì vậy, nếu bây giờ tôi muốn khóa model ModelToLock, điều này có thể được sử dụng như thế này:

with LockedAtomicTransaction(ModelToLock): 
    # do whatever you want to do 
    ModelToLock.objects.create() 

EDIT: Lưu ý rằng tôi chỉ có kiểm tra điều này bằng cách sử dụng postgres. Nhưng với sự hiểu biết của tôi, nó cũng nên làm việc trên mysql chỉ như thế.

+1

Rất tốt, cảm ơn. – Norman8054

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