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:
Aymeric Augustin 2013-03-02 15:00:28 +01:00
parent d63e55039d
commit e0449316eb
4 changed files with 55 additions and 15 deletions

View File

@ -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):
""" """

View File

@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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
===================================== =====================================

View File

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