Fixed #28062 -- Added a setting to disable server-side cursors on PostgreSQL.
When a connection pooler is set up in transaction pooling mode, queries relying on server-side cursors fail. The DISABLE_SERVER_SIDE_CURSORS setting in DATABASES disables server-side cursors for this use case.
This commit is contained in:
parent
504e7782fe
commit
88336fdbb5
|
@ -306,7 +306,8 @@ class QuerySet:
|
||||||
An iterator over the results from applying this QuerySet to the
|
An iterator over the results from applying this QuerySet to the
|
||||||
database.
|
database.
|
||||||
"""
|
"""
|
||||||
return iter(self._iterable_class(self, chunked_fetch=True))
|
use_chunked_fetch = not connections[self.db].settings_dict.get('DISABLE_SERVER_SIDE_CURSORS')
|
||||||
|
return iter(self._iterable_class(self, chunked_fetch=use_chunked_fetch))
|
||||||
|
|
||||||
def aggregate(self, *args, **kwargs):
|
def aggregate(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -189,6 +189,41 @@ cursor query is controlled with the `cursor_tuple_fraction`_ option.
|
||||||
|
|
||||||
.. _cursor_tuple_fraction: https://www.postgresql.org/docs/current/static/runtime-config-query.html#GUC-CURSOR-TUPLE-FRACTION
|
.. _cursor_tuple_fraction: https://www.postgresql.org/docs/current/static/runtime-config-query.html#GUC-CURSOR-TUPLE-FRACTION
|
||||||
|
|
||||||
|
.. _transaction-pooling-server-side-cursors:
|
||||||
|
|
||||||
|
Transaction pooling and server-side cursors
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. versionadded:: 1.11.1
|
||||||
|
|
||||||
|
Using a connection pooler in transaction pooling mode (e.g. `pgBouncer`_)
|
||||||
|
requires disabling server-side cursors for that connection.
|
||||||
|
|
||||||
|
Server-side cursors are local to a connection and remain open at the end of a
|
||||||
|
transaction when :setting:`AUTOCOMMIT <DATABASE-AUTOCOMMIT>` is ``True``. A
|
||||||
|
subsequent transaction may attempt to fetch more results from a server-side
|
||||||
|
cursor. In transaction pooling mode, there's no guarantee that subsequent
|
||||||
|
transactions will use the same connection. If a different connection is used,
|
||||||
|
an error is raised when the transaction references the server-side cursor,
|
||||||
|
because server-side cursors are only accessible in the connection in which they
|
||||||
|
were created.
|
||||||
|
|
||||||
|
One solution is to disable server-side cursors for a connection in
|
||||||
|
:setting:`DATABASES` by setting :setting:`DISABLE_SERVER_SIDE_CURSORS
|
||||||
|
<DATABASE-DISABLE_SERVER_SIDE_CURSORS>` to ``True``.
|
||||||
|
|
||||||
|
To benefit from server-side cursors in transaction pooling mode, you could set
|
||||||
|
up :doc:`another connection to the database </topics/db/multi-db>` in order to
|
||||||
|
perform queries that use server-side cursors. This connection needs to either
|
||||||
|
be directly to the database or to a connection pooler in session pooling mode.
|
||||||
|
|
||||||
|
Another option is to wrap each ``QuerySet`` using server-side cursors in an
|
||||||
|
:func:`~django.db.transaction.atomic` block, because it disables ``autocommit``
|
||||||
|
for the duration of the transaction. This way, the server-side cursor will only
|
||||||
|
live for the duration of the transaction.
|
||||||
|
|
||||||
|
.. _pgBouncer: https://pgbouncer.github.io/
|
||||||
|
|
||||||
Test database templates
|
Test database templates
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
|
|
@ -2026,6 +2026,11 @@ won't cache results after iterating over them. Oracle and :ref:`PostgreSQL
|
||||||
<postgresql-server-side-cursors>` use server-side cursors to stream results
|
<postgresql-server-side-cursors>` use server-side cursors to stream results
|
||||||
from the database without loading the entire result set into memory.
|
from the database without loading the entire result set into memory.
|
||||||
|
|
||||||
|
On PostgreSQL, server-side cursors will only be used when the
|
||||||
|
:setting:`DISABLE_SERVER_SIDE_CURSORS <DATABASE-DISABLE_SERVER_SIDE_CURSORS>`
|
||||||
|
setting is ``False``. Read :ref:`transaction-pooling-server-side-cursors` if
|
||||||
|
you're using a connection pooler configured in transaction pooling mode.
|
||||||
|
|
||||||
.. versionchanged:: 1.11
|
.. versionchanged:: 1.11
|
||||||
|
|
||||||
PostgreSQL support for server-side cursors was added.
|
PostgreSQL support for server-side cursors was added.
|
||||||
|
|
|
@ -641,6 +641,21 @@ PostgreSQL), it is an error to set this option.
|
||||||
|
|
||||||
When :setting:`USE_TZ` is ``False``, it is an error to set this option.
|
When :setting:`USE_TZ` is ``False``, it is an error to set this option.
|
||||||
|
|
||||||
|
.. setting:: DATABASE-DISABLE_SERVER_SIDE_CURSORS
|
||||||
|
|
||||||
|
``DISABLE_SERVER_SIDE_CURSORS``
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. versionadded:: 1.11.1
|
||||||
|
|
||||||
|
Default: ``False``
|
||||||
|
|
||||||
|
Set this to ``True`` if you want to disable the use of server-side cursors with
|
||||||
|
:meth:`.QuerySet.iterator`. :ref:`transaction-pooling-server-side-cursors`
|
||||||
|
describes the use case.
|
||||||
|
|
||||||
|
This is a PostgreSQL-specific setting.
|
||||||
|
|
||||||
.. setting:: USER
|
.. setting:: USER
|
||||||
|
|
||||||
``USER``
|
``USER``
|
||||||
|
|
|
@ -4,7 +4,17 @@ Django 1.11.1 release notes
|
||||||
|
|
||||||
*Under development*
|
*Under development*
|
||||||
|
|
||||||
Django 1.11.1 fixes several bugs in 1.11.
|
Django 1.11.1 adds a minor feature and fixes several bugs in 1.11.
|
||||||
|
|
||||||
|
Allowed disabling server-side cursors on PostgreSQL
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
The change in Django 1.11 to make :meth:`.QuerySet.iterator()` use server-side
|
||||||
|
cursors on PostgreSQL prevents running Django with `pgBouncer` in transaction
|
||||||
|
pooling mode. To reallow that, use the :setting:`DISABLE_SERVER_SIDE_CURSORS
|
||||||
|
<DATABASE-DISABLE_SERVER_SIDE_CURSORS>` setting in :setting:`DATABASES`.
|
||||||
|
|
||||||
|
See :ref:`transaction-pooling-server-side-cursors` for more discussion.
|
||||||
|
|
||||||
Bugfixes
|
Bugfixes
|
||||||
========
|
========
|
||||||
|
|
|
@ -496,6 +496,8 @@ plugins
|
||||||
pluralizations
|
pluralizations
|
||||||
po
|
po
|
||||||
podcast
|
podcast
|
||||||
|
pooler
|
||||||
|
pooling
|
||||||
popup
|
popup
|
||||||
postfix
|
postfix
|
||||||
postgis
|
postgis
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
import operator
|
||||||
import unittest
|
import unittest
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
@ -23,6 +25,18 @@ class ServerSideCursorsPostgres(TestCase):
|
||||||
cursors = cursor.fetchall()
|
cursors = cursor.fetchall()
|
||||||
return [self.PostgresCursor._make(cursor) for cursor in cursors]
|
return [self.PostgresCursor._make(cursor) for cursor in cursors]
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def override_db_setting(self, **kwargs):
|
||||||
|
for setting, value in kwargs.items():
|
||||||
|
original_value = connection.settings_dict.get(setting)
|
||||||
|
if setting in connection.settings_dict:
|
||||||
|
self.addCleanup(operator.setitem, connection.settings_dict, setting, original_value)
|
||||||
|
else:
|
||||||
|
self.addCleanup(operator.delitem, connection.settings_dict, setting)
|
||||||
|
|
||||||
|
connection.settings_dict[setting] = kwargs[setting]
|
||||||
|
yield
|
||||||
|
|
||||||
def test_server_side_cursor(self):
|
def test_server_side_cursor(self):
|
||||||
persons = Person.objects.iterator()
|
persons = Person.objects.iterator()
|
||||||
next(persons) # Open a server-side cursor
|
next(persons) # Open a server-side cursor
|
||||||
|
@ -52,3 +66,17 @@ class ServerSideCursorsPostgres(TestCase):
|
||||||
del persons
|
del persons
|
||||||
cursors = self.inspect_cursors()
|
cursors = self.inspect_cursors()
|
||||||
self.assertEqual(len(cursors), 0)
|
self.assertEqual(len(cursors), 0)
|
||||||
|
|
||||||
|
def test_server_side_cursors_setting(self):
|
||||||
|
with self.override_db_setting(DISABLE_SERVER_SIDE_CURSORS=False):
|
||||||
|
persons = Person.objects.iterator()
|
||||||
|
next(persons) # Open a server-side cursor
|
||||||
|
cursors = self.inspect_cursors()
|
||||||
|
self.assertEqual(len(cursors), 1)
|
||||||
|
del persons # Close server-side cursor
|
||||||
|
|
||||||
|
with self.override_db_setting(DISABLE_SERVER_SIDE_CURSORS=True):
|
||||||
|
persons = Person.objects.iterator()
|
||||||
|
next(persons) # Should not open a server-side cursor
|
||||||
|
cursors = self.inspect_cursors()
|
||||||
|
self.assertEqual(len(cursors), 0)
|
||||||
|
|
Loading…
Reference in New Issue