Skip to content

Commit

Permalink
Fixed #18130 -- Made the isolation level configurable on PostgreSQL.
Browse files Browse the repository at this point in the history
Thanks limscoder for the report and niwi for the draft patch.
  • Loading branch information
aaugustin committed Mar 2, 2013
1 parent d63e550 commit e044931
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 15 deletions.
9 changes: 7 additions & 2 deletions django/db/backends/postgresql_psycopg2/base.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ def __init__(self, *args, **kwargs):
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)
Expand All @@ -104,6 +105,8 @@ def get_connection_params(self):
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']:
Expand Down Expand Up @@ -170,7 +173,9 @@ def _enter_transaction_management(self, managed):
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):
""" """
Expand Down
35 changes: 33 additions & 2 deletions docs/ref/databases.txt
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Expand Down
2 changes: 2 additions & 0 deletions docs/releases/1.6.txt
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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
===================================== =====================================


Expand Down
24 changes: 13 additions & 11 deletions tests/transactions_regress/tests.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -242,17 +242,18 @@ def test_commit_unless_managed_in_managed(self):


@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
Expand All @@ -261,6 +262,7 @@ def setUp(self):
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
Expand All @@ -279,29 +281,29 @@ def test_initial_autocommit_state(self):
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)


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)


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.
Expand All @@ -314,7 +316,7 @@ def test_enter_autocommit(self):
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)


Expand Down

0 comments on commit e044931

Please sign in to comment.