Fixed #27683 -- Made MySQL default to the read committed isolation level.
Thanks Shai Berger for test help and Adam Johnson for review.
This commit is contained in:
parent
c4e18bb1ce
commit
924af638e4
|
@ -217,7 +217,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||
kwargs['client_flag'] = CLIENT.FOUND_ROWS
|
||||
# Validate the transaction isolation level, if specified.
|
||||
options = settings_dict['OPTIONS'].copy()
|
||||
isolation_level = options.pop('isolation_level', None)
|
||||
isolation_level = options.pop('isolation_level', 'read committed')
|
||||
if isolation_level:
|
||||
isolation_level = isolation_level.lower()
|
||||
if isolation_level not in self.isolation_levels:
|
||||
|
|
|
@ -449,8 +449,14 @@ this entry are the four standard isolation levels:
|
|||
* ``'serializable'``
|
||||
|
||||
or ``None`` to use the server's configured isolation level. However, Django
|
||||
works best with read committed rather than MySQL's default, repeatable read.
|
||||
Data loss is possible with repeatable read.
|
||||
works best with and defaults to read committed rather than MySQL's default,
|
||||
repeatable read. Data loss is possible with repeatable read.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
|
||||
In older versions, the MySQL database backend defaults to using the
|
||||
database's isolation level (which defaults to repeatable read) rather
|
||||
than read committed.
|
||||
|
||||
.. _transaction isolation level: https://dev.mysql.com/doc/refman/en/innodb-transaction-isolation-levels.html
|
||||
|
||||
|
|
|
@ -227,6 +227,15 @@ The end of upstream support for Oracle 11.2 is Dec. 2020. Django 1.11 will be
|
|||
supported until April 2020 which almost reaches this date. Django 2.0
|
||||
officially supports Oracle 12.1+.
|
||||
|
||||
Default MySQL isolation level is read committed
|
||||
-----------------------------------------------
|
||||
|
||||
MySQL's default isolation level, repeatable read, may cause data loss in
|
||||
typical Django usage. To prevent that and for consistency with other databases,
|
||||
the default isolation level is now read committed. You can use the
|
||||
:setting:`DATABASES` setting to :ref:`use a different isolation level
|
||||
<mysql-isolation-level>`, if needed.
|
||||
|
||||
:attr:`AbstractUser.last_name <django.contrib.auth.models.User.last_name>` ``max_length`` increased to 150
|
||||
----------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -70,6 +70,15 @@ class MySQLTests(TestCase):
|
|||
self.isolation_values[self.other_isolation_level]
|
||||
)
|
||||
|
||||
def test_default_isolation_level(self):
|
||||
# If not specified in settings, the default is read committed.
|
||||
with get_connection() as new_connection:
|
||||
new_connection.settings_dict['OPTIONS'].pop('isolation_level', None)
|
||||
self.assertEqual(
|
||||
self.get_isolation_level(new_connection),
|
||||
self.isolation_values[self.read_committed]
|
||||
)
|
||||
|
||||
def test_isolation_level_validation(self):
|
||||
new_connection = connection.copy()
|
||||
new_connection.settings_dict['OPTIONS']['isolation_level'] = 'xxx'
|
||||
|
|
|
@ -375,18 +375,17 @@ class AtomicMySQLTests(TransactionTestCase):
|
|||
@skipIf(threading is None, "Test requires threading")
|
||||
def test_implicit_savepoint_rollback(self):
|
||||
"""MySQL implicitly rolls back savepoints when it deadlocks (#22291)."""
|
||||
Reporter.objects.create(id=1)
|
||||
Reporter.objects.create(id=2)
|
||||
|
||||
other_thread_ready = threading.Event()
|
||||
main_thread_ready = threading.Event()
|
||||
|
||||
def other_thread():
|
||||
try:
|
||||
with transaction.atomic():
|
||||
Reporter.objects.create(id=1, first_name="Tintin")
|
||||
other_thread_ready.set()
|
||||
# We cannot synchronize the two threads with an event here
|
||||
# because the main thread locks. Sleep for a little while.
|
||||
time.sleep(1)
|
||||
# 2) ... and this line deadlocks. (see below for 1)
|
||||
Reporter.objects.select_for_update().get(id=1)
|
||||
main_thread_ready.wait()
|
||||
# 1) This line locks... (see below for 2)
|
||||
Reporter.objects.exclude(id=1).update(id=2)
|
||||
finally:
|
||||
# This is the thread-local connection, not the main connection.
|
||||
|
@ -394,14 +393,18 @@ class AtomicMySQLTests(TransactionTestCase):
|
|||
|
||||
other_thread = threading.Thread(target=other_thread)
|
||||
other_thread.start()
|
||||
other_thread_ready.wait()
|
||||
|
||||
with self.assertRaisesMessage(OperationalError, 'Deadlock found'):
|
||||
# Double atomic to enter a transaction and create a savepoint.
|
||||
with transaction.atomic():
|
||||
with transaction.atomic():
|
||||
# 1) This line locks... (see above for 2)
|
||||
Reporter.objects.create(id=1, first_name="Tintin")
|
||||
Reporter.objects.select_for_update().get(id=2)
|
||||
main_thread_ready.set()
|
||||
# The two threads can't be synchronized with an event here
|
||||
# because the other thread locks. Sleep for a little while.
|
||||
time.sleep(1)
|
||||
# 2) ... and this line deadlocks. (see above for 1)
|
||||
Reporter.objects.exclude(id=2).update(id=1)
|
||||
|
||||
other_thread.join()
|
||||
|
||||
|
|
Loading…
Reference in New Issue