Fixed #14180 -- Prevented unneeded index creation on MySQL-InnoDB

Thanks zimnyx for the report and Simon Charette, Tim Graham for
the reviews.
This commit is contained in:
Claude Paroz 2014-12-24 15:55:57 +01:00
parent 4718296546
commit 2ceb10f3b0
5 changed files with 42 additions and 9 deletions

View File

@ -142,13 +142,17 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
def get_storage_engine(self, cursor, table_name): 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( cursor.execute(
"SELECT engine " "SELECT engine "
"FROM information_schema.tables " "FROM information_schema.tables "
"WHERE table_name = %s", [table_name]) "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): def get_constraints(self, cursor, table_name):
""" """

View File

@ -51,3 +51,15 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
'table': self.quote_name(model._meta.db_table), 'table': self.quote_name(model._meta.db_table),
'column': self.quote_name(field.column), 'column': self.quote_name(field.column),
}, [effective_default]) }, [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)

View File

@ -249,6 +249,9 @@ Database backends
and up will support microseconds. See the :ref:`MySQL database notes and up will support microseconds. See the :ref:`MySQL database notes
<mysql-fractional-seconds>` for more details. <mysql-fractional-seconds>` 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 Email
^^^^^ ^^^^^

View File

@ -72,14 +72,14 @@ class SQLCommandsTestCase(TestCase):
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.simplefilter("ignore", category=RemovedInDjango20Warning) warnings.simplefilter("ignore", category=RemovedInDjango20Warning)
output = sql_indexes(app_config, no_style(), connections[DEFAULT_DB_ALIAS]) output = sql_indexes(app_config, no_style(), connections[DEFAULT_DB_ALIAS])
# PostgreSQL creates one additional index for CharField # Number of indexes is backend-dependent
self.assertIn(self.count_ddl(output, 'CREATE INDEX'), [3, 4]) self.assertTrue(1 <= self.count_ddl(output, 'CREATE INDEX') <= 4)
def test_sql_destroy_indexes(self): def test_sql_destroy_indexes(self):
app_config = apps.get_app_config('commands_sql') app_config = apps.get_app_config('commands_sql')
output = sql_destroy_indexes(app_config, no_style(), connections[DEFAULT_DB_ALIAS]) output = sql_destroy_indexes(app_config, no_style(), connections[DEFAULT_DB_ALIAS])
# PostgreSQL creates one additional index for CharField # Number of indexes is backend-dependent
self.assertIn(self.count_ddl(output, 'DROP INDEX'), [3, 4]) self.assertTrue(1 <= self.count_ddl(output, 'DROP INDEX') <= 4)
def test_sql_all(self): def test_sql_all(self):
app_config = apps.get_app_config('commands_sql') 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]) output = sql_all(app_config, no_style(), connections[DEFAULT_DB_ALIAS])
self.assertEqual(self.count_ddl(output, 'CREATE TABLE'), 3) self.assertEqual(self.count_ddl(output, 'CREATE TABLE'), 3)
# PostgreSQL creates one additional index for CharField # Number of indexes is backend-dependent
self.assertIn(self.count_ddl(output, 'CREATE INDEX'), [3, 4]) self.assertTrue(1 <= self.count_ddl(output, 'CREATE INDEX') <= 4)
class TestRouter(object): class TestRouter(object):

View File

@ -5,7 +5,7 @@ from django.db import connection
from django.test import TestCase from django.test import TestCase
from django.test.utils import IgnorePendingDeprecationWarningsMixin from django.test.utils import IgnorePendingDeprecationWarningsMixin
from .models import Article, IndexTogetherSingleList from .models import Article, ArticleTranslation, IndexTogetherSingleList
class CreationIndexesTests(IgnorePendingDeprecationWarningsMixin, TestCase): class CreationIndexesTests(IgnorePendingDeprecationWarningsMixin, TestCase):
@ -82,3 +82,17 @@ class SchemaIndexesTests(TestCase):
"""Test indexes are not created for related objects""" """Test indexes are not created for related objects"""
index_sql = connection.schema_editor()._model_indexes_sql(Article) index_sql = connection.schema_editor()._model_indexes_sql(Article)
self.assertEqual(len(index_sql), 1) 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, [])