mirror of
https://github.com/pretix/pretix.git
synced 2026-05-08 15:44:02 +00:00
Run exporters in repeatable read by default (Z#23173095) (#5500)
* Run exporters in repeatable read by default (Z#23173095) * Update src/pretix/helpers/database.py Co-authored-by: Richard Schreiber <schreiber@rami.io> * Rename parameter, add test * Do not run during tests --------- Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
@@ -21,7 +21,8 @@
|
||||
#
|
||||
import contextlib
|
||||
|
||||
from django.core.exceptions import FieldDoesNotExist
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
|
||||
from django.db import connection, transaction
|
||||
from django.db.models import (
|
||||
Aggregate, Expression, F, Field, Lookup, OrderBy, Value,
|
||||
@@ -62,6 +63,43 @@ def casual_reads():
|
||||
yield
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def repeatable_reads_transaction():
|
||||
"""
|
||||
pretix, and Django, operate in the transaction isolation level READ COMMITTED by default. This is not a strong level
|
||||
of isolation, but we NEED to use it: Otherwise e.g. our quota logic breaks, because we need to be able to get the
|
||||
*current* number of tickets sold at any time in a transaction, not the number of tickets sold *before* our transaction
|
||||
started.
|
||||
|
||||
However, this isolation mode has drawbacks, for example during reporting. When a user retrieves a report from the
|
||||
system, it should return numbers that are consistent with each other. However, if the report makes multiple SQL
|
||||
queries in READ COMMITTED mode, the results might be different for each query, causing numbers to be inconsistent
|
||||
with each other.
|
||||
|
||||
This context manager creates a transaction that is running in REPEATABLE READ mode to avoid this problem.
|
||||
|
||||
**You should only make read-only queries during this transaction and not rely on quota calculations.**
|
||||
"""
|
||||
is_under_test = 'tests.testdummy' in settings.INSTALLED_APPS
|
||||
try:
|
||||
with transaction.atomic(durable=not is_under_test):
|
||||
if not is_under_test:
|
||||
# We're not running this in tests, where we can basically not use this since the test runner does its
|
||||
# own transaction logic for efficiency
|
||||
with connection.cursor() as cursor:
|
||||
if 'postgresql' in settings.DATABASES['default']['ENGINE']:
|
||||
cursor.execute('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;')
|
||||
elif 'sqlite' in settings.DATABASES['default']['ENGINE']:
|
||||
pass # noop
|
||||
else:
|
||||
raise ImproperlyConfigured("Cannot set transaction isolation mode on this database backend")
|
||||
|
||||
connection.tx_in_repeatable_read = True
|
||||
yield
|
||||
finally:
|
||||
connection.tx_in_repeatable_read = False
|
||||
|
||||
|
||||
class GroupConcat(Aggregate):
|
||||
function = 'group_concat'
|
||||
template = '%(function)s(%(distinct)s%(field)s, "%(separator)s")'
|
||||
|
||||
Reference in New Issue
Block a user