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): |     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): | ||||||
|         """ |         """ | ||||||
|   | |||||||
| @@ -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) | ||||||
|   | |||||||
| @@ -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 | ||||||
| ^^^^^ | ^^^^^ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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): | ||||||
|   | |||||||
| @@ -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, []) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user