瀏覽代碼

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

Thanks Shai Berger for test help and Adam Johnson for review.
Tim Graham 8 年之前
父節點
當前提交
924af638e4
共有 5 個文件被更改,包括 40 次插入13 次删除
  1. 1 1
      django/db/backends/mysql/base.py
  2. 8 2
      docs/ref/databases.txt
  3. 9 0
      docs/releases/2.0.txt
  4. 9 0
      tests/backends/test_mysql.py
  5. 13 10
      tests/transactions/tests.py

+ 1 - 1
django/db/backends/mysql/base.py

@@ -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:

+ 8 - 2
docs/ref/databases.txt

@@ -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
 

+ 9 - 0
docs/releases/2.0.txt

@@ -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
 ----------------------------------------------------------------------------------------------------------
 

+ 9 - 0
tests/backends/test_mysql.py

@@ -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'

+ 13 - 10
tests/transactions/tests.py

@@ -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()