mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	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:
		| @@ -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): | ||||
|         """ | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -249,6 +249,9 @@ Database backends | ||||
|   and up will support microseconds. See the :ref:`MySQL database notes | ||||
|   <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 | ||||
| ^^^^^ | ||||
|  | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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, []) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user