Przeglądaj źródła

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

Nick Pope 5 lat temu
rodzic
commit
55df1750be

+ 4 - 0
django/db/backends/mysql/features.py

@@ -117,6 +117,10 @@ class DatabaseFeatures(BaseDatabaseFeatures):
         # EXTENDED is deprecated (and not required) in MySQL 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
     def supported_explain_formats(self):
         # Alias MySQL's TRADITIONAL to TEXT for consistency with other

+ 8 - 3
django/db/backends/mysql/operations.py

@@ -299,11 +299,16 @@ class DatabaseOperations(BaseDatabaseOperations):
         elif not format and 'TREE' in self.connection.features.supported_explain_formats:
             # Use TREE by default (if supported) as it's more informative.
             format = 'TREE'
+        analyze = options.pop('analyze', False)
         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
-        if self.connection.features.needs_explain_extended and format is None:
-            # EXTENDED and FORMAT are mutually exclusive options.
+        if self.connection.features.needs_explain_extended and not analyze and format is None:
+            # ANALYZE, EXTENDED, and FORMAT are mutually exclusive options.
             prefix += ' EXTENDED'
         return prefix
 

+ 5 - 4
docs/ref/models/querysets.txt

@@ -2587,13 +2587,14 @@ Pass these flags as keyword arguments. For example, when using PostgreSQL::
     Execution time: 0.058 ms
 
 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
-could result in changes to data if there are triggers or if a function is
-called, even for a ``SELECT`` query.
+adverse effects on your database. For example, the ``ANALYZE`` flag supported
+by MariaDB, MySQL 8.0.18+, and PostgreSQL could result in changes to data if
+there are triggers or if a function is called, even for a ``SELECT`` query.
 
 .. 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:
 

+ 4 - 1
docs/releases/3.1.txt

@@ -169,7 +169,10 @@ Models
   :class:`~django.db.models.DateTimeField`, and the new :lookup:`iso_week_day`
   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
 ~~~~~~~~~~

+ 19 - 0
tests/queries/test_explain.py

@@ -80,6 +80,25 @@ class ExplainTests(TestCase):
         self.assertEqual(len(captured_queries), 1)
         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')
     def test_mysql_extended(self):
         # Inner skip to avoid module level query for MySQL version.