Refs #30897 -- Added support for ANALYZE option to Queryset.explain() on MariaDB and MySQL 8.0.18+.

This commit is contained in:
Nick Pope 2019-10-21 17:34:19 +01:00 committed by Mariusz Felisiak
parent 742961332e
commit 55df1750be
5 changed files with 40 additions and 8 deletions

View File

@ -117,6 +117,10 @@ class DatabaseFeatures(BaseDatabaseFeatures):
# EXTENDED is deprecated (and not required) in MySQL 5.7. # EXTENDED is deprecated (and not required) in MySQL 5.7.
return not self.connection.mysql_is_mariadb and self.connection.mysql_version < (5, 7) return not self.connection.mysql_is_mariadb and self.connection.mysql_version < (5, 7)
@cached_property
def supports_explain_analyze(self):
return self.connection.mysql_is_mariadb or self.connection.mysql_version >= (8, 0, 18)
@cached_property @cached_property
def supported_explain_formats(self): def supported_explain_formats(self):
# Alias MySQL's TRADITIONAL to TEXT for consistency with other # Alias MySQL's TRADITIONAL to TEXT for consistency with other

View File

@ -299,11 +299,16 @@ class DatabaseOperations(BaseDatabaseOperations):
elif not format and 'TREE' in self.connection.features.supported_explain_formats: elif not format and 'TREE' in self.connection.features.supported_explain_formats:
# Use TREE by default (if supported) as it's more informative. # Use TREE by default (if supported) as it's more informative.
format = 'TREE' format = 'TREE'
analyze = options.pop('analyze', False)
prefix = super().explain_query_prefix(format, **options) prefix = super().explain_query_prefix(format, **options)
if format: if analyze and self.connection.features.supports_explain_analyze:
# MariaDB uses ANALYZE instead of EXPLAIN ANALYZE.
prefix = 'ANALYZE' if self.connection.mysql_is_mariadb else prefix + ' ANALYZE'
if format and not (analyze and not self.connection.mysql_is_mariadb):
# Only MariaDB supports the analyze option with formats.
prefix += ' FORMAT=%s' % format prefix += ' FORMAT=%s' % format
if self.connection.features.needs_explain_extended and format is None: if self.connection.features.needs_explain_extended and not analyze and format is None:
# EXTENDED and FORMAT are mutually exclusive options. # ANALYZE, EXTENDED, and FORMAT are mutually exclusive options.
prefix += ' EXTENDED' prefix += ' EXTENDED'
return prefix return prefix

View File

@ -2587,13 +2587,14 @@ Pass these flags as keyword arguments. For example, when using PostgreSQL::
Execution time: 0.058 ms Execution time: 0.058 ms
On some databases, flags may cause the query to be executed which could have On some databases, flags may cause the query to be executed which could have
adverse effects on your database. For example, PostgreSQL's ``ANALYZE`` flag adverse effects on your database. For example, the ``ANALYZE`` flag supported
could result in changes to data if there are triggers or if a function is by MariaDB, MySQL 8.0.18+, and PostgreSQL could result in changes to data if
called, even for a ``SELECT`` query. there are triggers or if a function is called, even for a ``SELECT`` query.
.. versionchanged:: 3.1 .. versionchanged:: 3.1
Support for the ``'TREE'`` format on MySQL 8.0.16+ was added. Support for the ``'TREE'`` format on MySQL 8.0.16+ and ``analyze`` option
on MariaDB and MySQL 8.0.18+ were added.
.. _field-lookups: .. _field-lookups:

View File

@ -169,7 +169,10 @@ Models
:class:`~django.db.models.DateTimeField`, and the new :lookup:`iso_week_day` :class:`~django.db.models.DateTimeField`, and the new :lookup:`iso_week_day`
lookup allows querying by an ISO-8601 day of week. lookup allows querying by an ISO-8601 day of week.
* :meth:`.QuerySet.explain` now supports ``TREE`` format on MySQL 8.0.16+. * :meth:`.QuerySet.explain` now supports:
* ``TREE`` format on MySQL 8.0.16+,
* ``analyze`` option on MySQL 8.0.18+ and MariaDB.
Pagination Pagination
~~~~~~~~~~ ~~~~~~~~~~

View File

@ -80,6 +80,25 @@ class ExplainTests(TestCase):
self.assertEqual(len(captured_queries), 1) self.assertEqual(len(captured_queries), 1)
self.assertIn('FORMAT=TRADITIONAL', captured_queries[0]['sql']) self.assertIn('FORMAT=TRADITIONAL', captured_queries[0]['sql'])
@unittest.skipUnless(connection.vendor == 'mysql', 'MariaDB and MySQL >= 8.0.18 specific.')
def test_mysql_analyze(self):
# Inner skip to avoid module level query for MySQL version.
if not connection.features.supports_explain_analyze:
raise unittest.SkipTest('MariaDB and MySQL >= 8.0.18 specific.')
qs = Tag.objects.filter(name='test')
with CaptureQueriesContext(connection) as captured_queries:
qs.explain(analyze=True)
self.assertEqual(len(captured_queries), 1)
prefix = 'ANALYZE ' if connection.mysql_is_mariadb else 'EXPLAIN ANALYZE '
self.assertTrue(captured_queries[0]['sql'].startswith(prefix))
with CaptureQueriesContext(connection) as captured_queries:
qs.explain(analyze=True, format='JSON')
self.assertEqual(len(captured_queries), 1)
if connection.mysql_is_mariadb:
self.assertIn('FORMAT=JSON', captured_queries[0]['sql'])
else:
self.assertNotIn('FORMAT=JSON', captured_queries[0]['sql'])
@unittest.skipUnless(connection.vendor == 'mysql', 'MySQL < 5.7 specific') @unittest.skipUnless(connection.vendor == 'mysql', 'MySQL < 5.7 specific')
def test_mysql_extended(self): def test_mysql_extended(self):
# Inner skip to avoid module level query for MySQL version. # Inner skip to avoid module level query for MySQL version.