2013-10-30 10 views
8

Il mio problema è il seguente:Django ORM e bloccaggio tavolo

Ho un rivenditore di auto A, e un tavolo da db denominata sold_cars. Quando viene venduta un'auto, creo la voce in questa tabella.

La tabella ha una colonna intera di nome order_no. Dovrebbe essere unico all'interno delle auto vendute dal rivenditore.

Quindi se il rivenditore A ha venduto le automobili a, b and c, questa colonna deve essere 1, 2, 3. Devo usare questa colonna, e non una chiave primaria perché non voglio avere buchi nella mia numerazione - i dealer A e B (che potrebbero essere aggiunti in seguito) dovrebbero avere numeri di ordine 1, 2, 3 e non A : 1, 3, 5 e B: 2, 4, 6. Quindi ... Seleziono l'ultimo order_no più grande per un determinato rivenditore, incrementarlo di 1 e salvare.

Il problema è che due persone hanno acquistato auto dal concessionario A nello stesso millisecondo ed entrambi gli ordini hanno ottenuto lo stesso ordine_no. Qualche consiglio? Stavo pensando di chiudere questo processo in un blocco di transazioni e bloccare questa tabella fino a quando la transazione non è completa, ma non riesco a trovare alcuna informazione su come farlo.

risposta

6

Penso che questo snippet di codice soddisfi le tue necessità, assumendo che tu stia usando MySQL. In caso contrario, potrebbe essere necessario modificare leggermente la sintassi, ma l'idea dovrebbe comunque funzionare.

Fonte: 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

So che questa domanda è un po 'più vecchio, ma ho appena avuto lo stesso problema e ha voluto condividere i miei insegnamenti.

Non ero abbastanza soddisfatto della risposta di st0nes, poiché (almeno per postgres) una dichiarazione LOCK TABLE può essere emessa solo all'interno di una transazione. E anche se in Django di solito succede quasi tutto all'interno di una transazione, questo LockingManager non si assicura che tu sia effettivamente all'interno di una transazione, almeno per quanto ne so. Inoltre non volevo cambiare completamente i modelli Manager solo per poterlo bloccare in un punto e quindi cercavo qualcosa che funzionasse come lo with transaction.atomic():, ma bloccasse anche un determinato modello.

Così sono arrivato fino a questo:

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() 

Quindi, se ora voglio bloccare il modello di ModelToLock, questo può essere utilizzato in questo modo:

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

EDIT: Si noti che ho solo testato questo utilizzando Postgres. Ma per quanto ne so, dovrebbe funzionare anche su mysql proprio così.

+1

Molto bello, grazie. – Norman8054

Problemi correlati