diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index 772792d39d9..b5cca52e23e 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -132,6 +132,41 @@ Write your own :doc:`custom SQL to retrieve data or populate models `. Use ``django.db.connection.queries`` to find out what Django is writing for you and start from there. +Retrieve individual objects using a unique, indexed column +========================================================== + +There are two reasons to use a column with +:attr:`~django.db.models.Field.unique` or +:attr:`~django.db.models.Field.db_index` when using +:meth:`~django.db.models.query.QuerySet.get` to retrieve individual objects. +First, the query will be quicker because of the underlying database index. +Also, the query could run much slower if multiple objects match the lookup; +having a unique constraint on the column guarantees this will never happen. + +So using the :ref:`example Weblog models `:: + + >>> entry = Entry.objects.get(id=10) + +will be quicker than: + + >>> entry = Entry.object.get(headline="News Item Title") + +because ``id`` is indexed by the database and is guaranteed to be unique. + +Doing the following is potentially quite slow: + + >>> entry = Entry.objects.get(headline__startswith="News") + +First of all, `headline` is not indexed, which will make the underlying +database fetch slower. + +Second, the lookup doesn't guarantee that only one object will be returned. +If the query matches more than one object, it will retrieve and transfer all of +them from the database. This penalty could be substantial if hundreds or +thousands of records are returned. The penalty will be compounded if the +database lives on a separate server, where network overhead and latency also +play a factor. + Retrieve everything at once if you know you will need it ======================================================== diff --git a/tests/modeltests/basic/tests.py b/tests/modeltests/basic/tests.py index ebd70d14d9a..1c83b980a73 100644 --- a/tests/modeltests/basic/tests.py +++ b/tests/modeltests/basic/tests.py @@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals from datetime import datetime -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from django.db.models.fields import Field, FieldDoesNotExist from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from django.utils import six @@ -128,6 +128,40 @@ class ModelTest(TestCase): b = Article.objects.get(pk=a.id) self.assertEqual(a, b) + # Create a very similar object + a = Article( + id=None, + headline='Area man programs in Python', + pub_date=datetime(2005, 7, 28), + ) + a.save() + + self.assertEqual(Article.objects.count(), 2) + + # Django raises an Article.MultipleObjectsReturned exception if the + # lookup matches more than one object + self.assertRaisesRegexp( + MultipleObjectsReturned, + "get\(\) returned more than one Article -- it returned 2!", + Article.objects.get, + headline__startswith='Area', + ) + + self.assertRaisesRegexp( + MultipleObjectsReturned, + "get\(\) returned more than one Article -- it returned 2!", + Article.objects.get, + pub_date__year=2005, + ) + + self.assertRaisesRegexp( + MultipleObjectsReturned, + "get\(\) returned more than one Article -- it returned 2!", + Article.objects.get, + pub_date__year=2005, + pub_date__month=7, + ) + def test_object_creation(self): # Create an Article. a = Article(