summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Lissner <mlissner@michaeljaylissner.com>2021-05-25 09:31:26 -0700
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-05-26 11:21:11 +0200
commit5a8e8f80bb82a867eab7e4d9d099f21d0a976d22 (patch)
treed59a000cab2fda9925951d2c5ed3b942a94a3bd4
parent12b19a1d76e1a6f80923c8358290d605dacd65d4 (diff)
downloaddjango-5a8e8f80bb82a867eab7e4d9d099f21d0a976d22.tar.gz
Fixed #32772 -- Made database cache count size once per set.
-rw-r--r--AUTHORS1
-rw-r--r--django/core/cache/backends/db.py12
-rw-r--r--tests/cache/tests.py13
3 files changed, 20 insertions, 6 deletions
diff --git a/AUTHORS b/AUTHORS
index d9318c618f..cdb43476cb 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -641,6 +641,7 @@ answer newbie questions, and generally made Django that much better:
Michael S. Brown <michael@msbrown.net>
Michael Hall <mhall1@ualberta.ca>
Michael Josephson <http://www.sdjournal.com/>
+ Michael Lissner <mike@free.law>
Michael Manfre <mmanfre@gmail.com>
michael.mcewan@gmail.com
Michael Placentra II <someone@michaelplacentra2.net>
diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py
index d62083a0f6..b84885f1ef 100644
--- a/django/core/cache/backends/db.py
+++ b/django/core/cache/backends/db.py
@@ -128,7 +128,7 @@ class DatabaseCache(BaseDatabaseCache):
exp = datetime.fromtimestamp(timeout, tz=tz)
exp = exp.replace(microsecond=0)
if num > self._max_entries:
- self._cull(db, cursor, now)
+ self._cull(db, cursor, now, num)
pickled = pickle.dumps(value, self.pickle_protocol)
# The DB column is expecting a string, so make sure the value is a
# string, not bytes. Refs #19274.
@@ -247,7 +247,7 @@ class DatabaseCache(BaseDatabaseCache):
)
return cursor.fetchone() is not None
- def _cull(self, db, cursor, now):
+ def _cull(self, db, cursor, now, num):
if self._cull_frequency == 0:
self.clear()
else:
@@ -255,10 +255,10 @@ class DatabaseCache(BaseDatabaseCache):
table = connection.ops.quote_name(self._table)
cursor.execute("DELETE FROM %s WHERE expires < %%s" % table,
[connection.ops.adapt_datetimefield_value(now)])
- cursor.execute("SELECT COUNT(*) FROM %s" % table)
- num = cursor.fetchone()[0]
- if num > self._max_entries:
- cull_num = num // self._cull_frequency
+ deleted_count = cursor.rowcount
+ remaining_num = num - deleted_count
+ if remaining_num > self._max_entries:
+ cull_num = remaining_num // self._cull_frequency
cursor.execute(
connection.ops.cache_key_culling_sql() % table,
[cull_num])
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index 65a8f12fc7..783055182d 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -40,6 +40,7 @@ from django.test import (
ignore_warnings, override_settings,
)
from django.test.signals import setting_changed
+from django.test.utils import CaptureQueriesContext
from django.utils import timezone, translation
from django.utils.cache import (
get_cache_key, learn_cache_key, patch_cache_control, patch_vary_headers,
@@ -1117,6 +1118,18 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase):
with self.assertNumQueries(1):
cache.delete_many(['a', 'b', 'c'])
+ def test_cull_count_queries(self):
+ old_max_entries = cache._max_entries
+ # Force _cull to delete on first cached record.
+ cache._max_entries = -1
+ with CaptureQueriesContext(connection) as captured_queries:
+ try:
+ cache.set('force_cull', 'value', 1000)
+ finally:
+ cache._max_entries = old_max_entries
+ num_count_queries = sum('COUNT' in query['sql'] for query in captured_queries)
+ self.assertEqual(num_count_queries, 1)
+
def test_delete_cursor_rowcount(self):
"""
The rowcount attribute should not be checked on a closed cursor.