locker.py 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. from django.db import connection
  2. from django.conf import settings
  3. #
  4. # NOTE ABOUT LOCKING
  5. #
  6. # This decorator is needed to prevent concurrent access to
  7. # data in code sections that have to be serialized. One option
  8. # is to set default isolation level of your database management
  9. # system to SERIALIZABLE which has a performance drawback. There
  10. # is another option, namely to set transaction isolation level
  11. # at beginning of transaction via SET TRANSACTION ISOLATION
  12. # LEVEL SERIALIZABLE or similar statement, but this is not
  13. # possible in Django with TransactionMiddleware where transaction
  14. # is opened at the start of a HTTP request.
  15. #
  16. # Therefore this method implements EXCLUSIVE table locks to
  17. # acquire exclusive access for code sections where needed.
  18. #
  19. # Check for database drivers, this is not done in the decorator
  20. # so this check is executed only once upon module load
  21. if settings.DATABASES['default']['ENGINE'].find('postgresql') != -1:
  22. # READ COMMITTED is the default isolation level in PostgreSQL so
  23. # we add explicit locking to support default isolation level.
  24. LOCK_TYPE = "postgresql"
  25. elif settings.DATABASES['default']['ENGINE'].find('mysql') != -1:
  26. # MySQL InnoDB default isolation level is REPEATABLE READ so we
  27. # add explicit locking to support default isolation level.
  28. LOCK_TYPE = "mysql"
  29. elif settings.DATABASES['default']['ENGINE'].find('sqlite') != -1:
  30. # Locking is not necessary for SQLite as default isolation
  31. # level for SQLite is SERIALIZABLE. (This means concurrent
  32. # transactions can fail and users have to retry their requests.)
  33. # You should probably add locking if you change isolation level.
  34. LOCK_TYPE = None
  35. else:
  36. LOCK_TYPE = None
  37. def require_lock(*models):
  38. tables = [model._meta.db_table for model in models]
  39. def _lock(func):
  40. def _do_lock(*args,**kws):
  41. cursor = connection.cursor()
  42. if LOCK_TYPE == "postgresql":
  43. cursor.execute("LOCK TABLE %s IN ROW EXCLUSIVE MODE" % ', '.join(tables))
  44. elif LOCK_TYPE == "mysql":
  45. cursor.execute("LOCK TABLES %s" % ', '.join(["%s WRITE" % x for x in tables]))
  46. try:
  47. return func(*args,**kws)
  48. finally:
  49. if LOCK_TYPE == "mysql":
  50. cursor.execute("UNLOCK TABLES")
  51. if cursor:
  52. cursor.close()
  53. return _do_lock
  54. return _lock
  55. def model_lock(model):
  56. cursor = connection.cursor()
  57. if LOCK_TYPE == "postgresql":
  58. cursor.execute("SELECT 1 FROM %s WHERE %s = '%s' FOR UPDATE" % (model._meta.db_table, model._meta.pk.name, model.pk))