From 2ceb10f3b02cbebad6ed908880f49a7c3e901d12 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 24 Dec 2014 15:55:57 +0100 Subject: [PATCH] Fixed #14180 -- Prevented unneeded index creation on MySQL-InnoDB Thanks zimnyx for the report and Simon Charette, Tim Graham for the reviews. --- django/db/backends/mysql/introspection.py | 8 ++++++-- django/db/backends/mysql/schema.py | 12 ++++++++++++ docs/releases/1.8.txt | 3 +++ tests/commands_sql/tests.py | 12 ++++++------ tests/indexes/tests.py | 16 +++++++++++++++- 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py index d11b0bee47..30ac6d4cab 100644 --- a/django/db/backends/mysql/introspection.py +++ b/django/db/backends/mysql/introspection.py @@ -142,13 +142,17 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): def get_storage_engine(self, cursor, table_name): """ - Retrieves the storage engine for a given table. + Retrieves the storage engine for a given table. Returns the default + storage engine if the table doesn't exist. """ cursor.execute( "SELECT engine " "FROM information_schema.tables " "WHERE table_name = %s", [table_name]) - return cursor.fetchone()[0] + result = cursor.fetchone() + if not result: + return self.connection.features._mysql_storage_engine + return result[0] def get_constraints(self, cursor, table_name): """ diff --git a/django/db/backends/mysql/schema.py b/django/db/backends/mysql/schema.py index 573779a49b..697490e888 100644 --- a/django/db/backends/mysql/schema.py +++ b/django/db/backends/mysql/schema.py @@ -51,3 +51,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): 'table': self.quote_name(model._meta.db_table), 'column': self.quote_name(field.column), }, [effective_default]) + + def _model_indexes_sql(self, model): + storage = self.connection.introspection.get_storage_engine( + self.connection.cursor(), model._meta.db_table + ) + if storage == "InnoDB": + for field in model._meta.local_fields: + if field.db_index and not field.unique and field.get_internal_type() == "ForeignKey": + # Temporary setting db_index to False (in memory) to disable + # index creation for FKs (index automatically created by MySQL) + field.db_index = False + return super(DatabaseSchemaEditor, self)._model_indexes_sql(model) diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 3d38ec2443..6a164821b6 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -249,6 +249,9 @@ Database backends and up will support microseconds. See the :ref:`MySQL database notes ` for more details. +* The MySQL backend no longer creates explicit indexes for foreign keys when + using the InnoDB storage engine, as MySQL already creates them automatically. + Email ^^^^^ diff --git a/tests/commands_sql/tests.py b/tests/commands_sql/tests.py index e1d4272616..10f0404776 100644 --- a/tests/commands_sql/tests.py +++ b/tests/commands_sql/tests.py @@ -72,14 +72,14 @@ class SQLCommandsTestCase(TestCase): with warnings.catch_warnings(): warnings.simplefilter("ignore", category=RemovedInDjango20Warning) output = sql_indexes(app_config, no_style(), connections[DEFAULT_DB_ALIAS]) - # PostgreSQL creates one additional index for CharField - self.assertIn(self.count_ddl(output, 'CREATE INDEX'), [3, 4]) + # Number of indexes is backend-dependent + self.assertTrue(1 <= self.count_ddl(output, 'CREATE INDEX') <= 4) def test_sql_destroy_indexes(self): app_config = apps.get_app_config('commands_sql') output = sql_destroy_indexes(app_config, no_style(), connections[DEFAULT_DB_ALIAS]) - # PostgreSQL creates one additional index for CharField - self.assertIn(self.count_ddl(output, 'DROP INDEX'), [3, 4]) + # Number of indexes is backend-dependent + self.assertTrue(1 <= self.count_ddl(output, 'DROP INDEX') <= 4) def test_sql_all(self): app_config = apps.get_app_config('commands_sql') @@ -88,8 +88,8 @@ class SQLCommandsTestCase(TestCase): output = sql_all(app_config, no_style(), connections[DEFAULT_DB_ALIAS]) self.assertEqual(self.count_ddl(output, 'CREATE TABLE'), 3) - # PostgreSQL creates one additional index for CharField - self.assertIn(self.count_ddl(output, 'CREATE INDEX'), [3, 4]) + # Number of indexes is backend-dependent + self.assertTrue(1 <= self.count_ddl(output, 'CREATE INDEX') <= 4) class TestRouter(object): diff --git a/tests/indexes/tests.py b/tests/indexes/tests.py index 86000bb94c..0710446245 100644 --- a/tests/indexes/tests.py +++ b/tests/indexes/tests.py @@ -5,7 +5,7 @@ from django.db import connection from django.test import TestCase from django.test.utils import IgnorePendingDeprecationWarningsMixin -from .models import Article, IndexTogetherSingleList +from .models import Article, ArticleTranslation, IndexTogetherSingleList class CreationIndexesTests(IgnorePendingDeprecationWarningsMixin, TestCase): @@ -82,3 +82,17 @@ class SchemaIndexesTests(TestCase): """Test indexes are not created for related objects""" index_sql = connection.schema_editor()._model_indexes_sql(Article) self.assertEqual(len(index_sql), 1) + + @skipUnless(connection.vendor == 'mysql', "This is a mysql-specific issue") + def test_no_index_for_foreignkey(self): + """ + MySQL on InnoDB already creates indexes automatically for foreign keys. + (#14180). + """ + storage = connection.introspection.get_storage_engine( + connection.cursor(), ArticleTranslation._meta.db_table + ) + if storage != "InnoDB": + self.skip("This test only applies to the InnoDB storage engine") + index_sql = connection.schema_editor()._model_indexes_sql(ArticleTranslation) + self.assertEqual(index_sql, [])