mirror of https://github.com/django/django.git
Fixed #18130 -- Made the isolation level configurable on PostgreSQL.
Thanks limscoder for the report and niwi for the draft patch.
This commit is contained in:
parent
d63e55039d
commit
e0449316eb
|
@ -83,7 +83,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
if autocommit:
|
if autocommit:
|
||||||
level = psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT
|
level = psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT
|
||||||
else:
|
else:
|
||||||
level = psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED
|
level = self.settings_dict["OPTIONS"].get('isolation_level',
|
||||||
|
psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
|
||||||
self._set_isolation_level(level)
|
self._set_isolation_level(level)
|
||||||
self.ops = DatabaseOperations(self)
|
self.ops = DatabaseOperations(self)
|
||||||
self.client = DatabaseClient(self)
|
self.client = DatabaseClient(self)
|
||||||
|
@ -104,6 +105,8 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
conn_params.update(settings_dict['OPTIONS'])
|
conn_params.update(settings_dict['OPTIONS'])
|
||||||
if 'autocommit' in conn_params:
|
if 'autocommit' in conn_params:
|
||||||
del conn_params['autocommit']
|
del conn_params['autocommit']
|
||||||
|
if 'isolation_level' in conn_params:
|
||||||
|
del conn_params['isolation_level']
|
||||||
if settings_dict['USER']:
|
if settings_dict['USER']:
|
||||||
conn_params['user'] = settings_dict['USER']
|
conn_params['user'] = settings_dict['USER']
|
||||||
if settings_dict['PASSWORD']:
|
if settings_dict['PASSWORD']:
|
||||||
|
@ -170,7 +173,9 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
the same transaction is visible across all the queries.
|
the same transaction is visible across all the queries.
|
||||||
"""
|
"""
|
||||||
if self.features.uses_autocommit and managed and not self.isolation_level:
|
if self.features.uses_autocommit and managed and not self.isolation_level:
|
||||||
self._set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
|
level = self.settings_dict["OPTIONS"].get('isolation_level',
|
||||||
|
psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
|
||||||
|
self._set_isolation_level(level)
|
||||||
|
|
||||||
def _leave_transaction_management(self, managed):
|
def _leave_transaction_management(self, managed):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -143,8 +143,11 @@ autocommit behavior is enabled by setting the ``autocommit`` key in
|
||||||
the :setting:`OPTIONS` part of your database configuration in
|
the :setting:`OPTIONS` part of your database configuration in
|
||||||
:setting:`DATABASES`::
|
:setting:`DATABASES`::
|
||||||
|
|
||||||
'OPTIONS': {
|
DATABASES = {
|
||||||
'autocommit': True,
|
# ...
|
||||||
|
'OPTIONS': {
|
||||||
|
'autocommit': True,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
In this configuration, Django still ensures that :ref:`delete()
|
In this configuration, Django still ensures that :ref:`delete()
|
||||||
|
@ -168,6 +171,34 @@ You should also audit your existing code for any instances of this behavior
|
||||||
before enabling this feature. It's faster, but it provides less automatic
|
before enabling this feature. It's faster, but it provides less automatic
|
||||||
protection for multi-call operations.
|
protection for multi-call operations.
|
||||||
|
|
||||||
|
Isolation level
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Like PostgreSQL itself, Django defaults to the ``READ COMMITTED`` `isolation
|
||||||
|
level <postgresql-isolation-levels>`_. If you need a higher isolation level
|
||||||
|
such as ``REPEATABLE READ`` or ``SERIALIZABLE``, set it in the
|
||||||
|
:setting:`OPTIONS` part of your database configuration in
|
||||||
|
:setting:`DATABASES`::
|
||||||
|
|
||||||
|
import psycopg2.extensions
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
# ...
|
||||||
|
'OPTIONS': {
|
||||||
|
'isolation_level': psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Under higher isolation levels, your application should be prepared to
|
||||||
|
handle exceptions raised on serialization failures. This option is
|
||||||
|
designed for advanced uses.
|
||||||
|
|
||||||
|
.. _postgresql-isolation-levels: http://www.postgresql.org/docs/devel/static/transaction-iso.html
|
||||||
|
|
||||||
Indexes for ``varchar`` and ``text`` columns
|
Indexes for ``varchar`` and ``text`` columns
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -125,6 +125,8 @@ Minor features
|
||||||
* The admin list columns have a ``column-<field_name>`` class in the HTML
|
* The admin list columns have a ``column-<field_name>`` class in the HTML
|
||||||
so the columns header can be styled with CSS, e.g. to set a column width.
|
so the columns header can be styled with CSS, e.g. to set a column width.
|
||||||
|
|
||||||
|
* The isolation level can be customized under PostgreSQL.
|
||||||
|
|
||||||
Backwards incompatible changes in 1.6
|
Backwards incompatible changes in 1.6
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
|
@ -242,17 +242,18 @@ class TestNewConnection(TransactionTestCase):
|
||||||
|
|
||||||
@skipUnless(connection.vendor == 'postgresql',
|
@skipUnless(connection.vendor == 'postgresql',
|
||||||
"This test only valid for PostgreSQL")
|
"This test only valid for PostgreSQL")
|
||||||
class TestPostgresAutocommit(TransactionTestCase):
|
class TestPostgresAutocommitAndIsolation(TransactionTestCase):
|
||||||
"""
|
"""
|
||||||
Tests to make sure psycopg2's autocommit mode is restored after entering
|
Tests to make sure psycopg2's autocommit mode and isolation level
|
||||||
and leaving transaction management. Refs #16047.
|
is restored after entering and leaving transaction management.
|
||||||
|
Refs #16047, #18130.
|
||||||
"""
|
"""
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
from psycopg2.extensions import (ISOLATION_LEVEL_AUTOCOMMIT,
|
from psycopg2.extensions import (ISOLATION_LEVEL_AUTOCOMMIT,
|
||||||
ISOLATION_LEVEL_READ_COMMITTED,
|
ISOLATION_LEVEL_SERIALIZABLE,
|
||||||
TRANSACTION_STATUS_IDLE)
|
TRANSACTION_STATUS_IDLE)
|
||||||
self._autocommit = ISOLATION_LEVEL_AUTOCOMMIT
|
self._autocommit = ISOLATION_LEVEL_AUTOCOMMIT
|
||||||
self._read_committed = ISOLATION_LEVEL_READ_COMMITTED
|
self._serializable = ISOLATION_LEVEL_SERIALIZABLE
|
||||||
self._idle = TRANSACTION_STATUS_IDLE
|
self._idle = TRANSACTION_STATUS_IDLE
|
||||||
|
|
||||||
# We want a clean backend with autocommit = True, so
|
# We want a clean backend with autocommit = True, so
|
||||||
|
@ -261,6 +262,7 @@ class TestPostgresAutocommit(TransactionTestCase):
|
||||||
settings = self._old_backend.settings_dict.copy()
|
settings = self._old_backend.settings_dict.copy()
|
||||||
opts = settings['OPTIONS'].copy()
|
opts = settings['OPTIONS'].copy()
|
||||||
opts['autocommit'] = True
|
opts['autocommit'] = True
|
||||||
|
opts['isolation_level'] = ISOLATION_LEVEL_SERIALIZABLE
|
||||||
settings['OPTIONS'] = opts
|
settings['OPTIONS'] = opts
|
||||||
new_backend = self._old_backend.__class__(settings, DEFAULT_DB_ALIAS)
|
new_backend = self._old_backend.__class__(settings, DEFAULT_DB_ALIAS)
|
||||||
connections[DEFAULT_DB_ALIAS] = new_backend
|
connections[DEFAULT_DB_ALIAS] = new_backend
|
||||||
|
@ -279,7 +281,7 @@ class TestPostgresAutocommit(TransactionTestCase):
|
||||||
def test_transaction_management(self):
|
def test_transaction_management(self):
|
||||||
transaction.enter_transaction_management()
|
transaction.enter_transaction_management()
|
||||||
transaction.managed(True)
|
transaction.managed(True)
|
||||||
self.assertEqual(connection.isolation_level, self._read_committed)
|
self.assertEqual(connection.isolation_level, self._serializable)
|
||||||
|
|
||||||
transaction.leave_transaction_management()
|
transaction.leave_transaction_management()
|
||||||
self.assertEqual(connection.isolation_level, self._autocommit)
|
self.assertEqual(connection.isolation_level, self._autocommit)
|
||||||
|
@ -287,13 +289,13 @@ class TestPostgresAutocommit(TransactionTestCase):
|
||||||
def test_transaction_stacking(self):
|
def test_transaction_stacking(self):
|
||||||
transaction.enter_transaction_management()
|
transaction.enter_transaction_management()
|
||||||
transaction.managed(True)
|
transaction.managed(True)
|
||||||
self.assertEqual(connection.isolation_level, self._read_committed)
|
self.assertEqual(connection.isolation_level, self._serializable)
|
||||||
|
|
||||||
transaction.enter_transaction_management()
|
transaction.enter_transaction_management()
|
||||||
self.assertEqual(connection.isolation_level, self._read_committed)
|
self.assertEqual(connection.isolation_level, self._serializable)
|
||||||
|
|
||||||
transaction.leave_transaction_management()
|
transaction.leave_transaction_management()
|
||||||
self.assertEqual(connection.isolation_level, self._read_committed)
|
self.assertEqual(connection.isolation_level, self._serializable)
|
||||||
|
|
||||||
transaction.leave_transaction_management()
|
transaction.leave_transaction_management()
|
||||||
self.assertEqual(connection.isolation_level, self._autocommit)
|
self.assertEqual(connection.isolation_level, self._autocommit)
|
||||||
|
@ -301,7 +303,7 @@ class TestPostgresAutocommit(TransactionTestCase):
|
||||||
def test_enter_autocommit(self):
|
def test_enter_autocommit(self):
|
||||||
transaction.enter_transaction_management()
|
transaction.enter_transaction_management()
|
||||||
transaction.managed(True)
|
transaction.managed(True)
|
||||||
self.assertEqual(connection.isolation_level, self._read_committed)
|
self.assertEqual(connection.isolation_level, self._serializable)
|
||||||
list(Mod.objects.all())
|
list(Mod.objects.all())
|
||||||
self.assertTrue(transaction.is_dirty())
|
self.assertTrue(transaction.is_dirty())
|
||||||
# Enter autocommit mode again.
|
# Enter autocommit mode again.
|
||||||
|
@ -314,7 +316,7 @@ class TestPostgresAutocommit(TransactionTestCase):
|
||||||
list(Mod.objects.all())
|
list(Mod.objects.all())
|
||||||
self.assertFalse(transaction.is_dirty())
|
self.assertFalse(transaction.is_dirty())
|
||||||
transaction.leave_transaction_management()
|
transaction.leave_transaction_management()
|
||||||
self.assertEqual(connection.isolation_level, self._read_committed)
|
self.assertEqual(connection.isolation_level, self._serializable)
|
||||||
transaction.leave_transaction_management()
|
transaction.leave_transaction_management()
|
||||||
self.assertEqual(connection.isolation_level, self._autocommit)
|
self.assertEqual(connection.isolation_level, self._autocommit)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue