Kaydet (Commit) 924af638 authored tarafından Tim Graham's avatar Tim Graham Kaydeden (comit) GitHub

Fixed #27683 -- Made MySQL default to the read committed isolation level.

Thanks Shai Berger for test help and Adam Johnson for review.
üst c4e18bb1
......@@ -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()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment