From 89032876f427a77ab4de26493190280377567d1c Mon Sep 17 00:00:00 2001 From: c-bata Date: Fri, 27 Mar 2020 01:51:30 +0900 Subject: [PATCH] Fixed #31275 -- Optimized sql_flush() without resetting sequences on MySQL. Co-Authored-By: Simon Charette --- AUTHORS | 1 + django/db/backends/mysql/operations.py | 47 ++++++++++++++++++-------- docs/releases/3.1.txt | 10 ++++++ 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/AUTHORS b/AUTHORS index 371bfe070cc..d0b01a5eb2e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -585,6 +585,7 @@ answer newbie questions, and generally made Django that much better: Martin von Gagern Mart Sõmermaa Marty Alchin + Masashi Shibata masonsimon+django@gmail.com Massimiliano Ravelli Massimo Scamarcia diff --git a/django/db/backends/mysql/operations.py b/django/db/backends/mysql/operations.py index a8e9af2263e..9d69ba11523 100644 --- a/django/db/backends/mysql/operations.py +++ b/django/db/backends/mysql/operations.py @@ -194,21 +194,40 @@ class DatabaseOperations(BaseDatabaseOperations): return 'RETURNING %s' % ', '.join(columns), () def sql_flush(self, style, tables, sequences, allow_cascade=False): - # NB: The generated SQL below is specific to MySQL - # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements - # to clear all tables of all data - if tables: - sql = ['SET FOREIGN_KEY_CHECKS = 0;'] - for table in tables: - sql.append('%s %s;' % ( - style.SQL_KEYWORD('TRUNCATE'), - style.SQL_FIELD(self.quote_name(table)), - )) - sql.append('SET FOREIGN_KEY_CHECKS = 1;') - sql.extend(self.sequence_reset_by_name_sql(style, sequences)) - return sql - else: + if not tables: return [] + sql = ['SET FOREIGN_KEY_CHECKS = 0;'] + tables = set(tables) + with_sequences = set(s['table'] for s in sequences) + # It's faster to TRUNCATE tables that require a sequence reset since + # ALTER TABLE AUTO_INCREMENT is slower than TRUNCATE. + sql.extend( + '%s %s;' % ( + style.SQL_KEYWORD('TRUNCATE'), + style.SQL_FIELD(self.quote_name(table_name)), + ) for table_name in tables.intersection(with_sequences) + ) + # Otherwise issue a simple DELETE since it's faster than TRUNCATE + # and preserves sequences. + sql.extend( + '%s %s %s;' % ( + style.SQL_KEYWORD('DELETE'), + style.SQL_KEYWORD('FROM'), + style.SQL_FIELD(self.quote_name(table_name)), + ) for table_name in tables.difference(with_sequences) + ) + sql.append('SET FOREIGN_KEY_CHECKS = 1;') + return sql + + def sequence_reset_by_name_sql(self, style, sequences): + return [ + '%s %s %s %s = 1;' % ( + style.SQL_KEYWORD('ALTER'), + style.SQL_KEYWORD('TABLE'), + style.SQL_FIELD(self.quote_name(sequence_info['table'])), + style.SQL_FIELD('AUTO_INCREMENT'), + ) for sequence_info in sequences + ] def validate_autopk_value(self, value): # MySQLism: zero in AUTO_INCREMENT field does not work. Refs #17653. diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index 492622d3bd8..70241b1809a 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -347,6 +347,10 @@ Models * :meth:`.QuerySet.bulk_create` now sets the primary key on objects when using MariaDB 10.5+. +* The ``DatabaseOperations.sql_flush()`` method now generates more efficient + SQL on MySQL by using ``DELETE`` instead of ``TRUNCATE`` statements for + tables which don't require resetting sequences. + Pagination ~~~~~~~~~~ @@ -415,6 +419,12 @@ Tests * :class:`~django.test.runner.DiscoverRunner` now skips running the system checks on databases not :ref:`referenced by tests`. +* :class:`~django.test.TransactionTestCase` teardown is now faster on MySQL + due to :djadmin:`flush` command improvements. As a side effect the latter + doesn't automatically reset sequences on teardown anymore. Enable + :attr:`.TransactionTestCase.reset_sequences` if your tests require this + feature. + URLs ~~~~