#34523: Model.objects.update_or_create method sometimes raises
TransactionManagementError
-------------------------------------+-------------------------------------
     Reporter:  gatello-s            |                    Owner:  nobody
         Type:  Bug                  |                   Status:  new
    Component:  Database layer       |                  Version:  4.2
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:
     Keywords:  update_or_create     |             Triage Stage:
  TransactionManagementError         |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Changes (by Anton Plotkin):

 * cc: Anton Plotkin (added)
 * status:  closed => new
 * resolution:  needsinfo =>


Old description:

> I am tests on mariadb 10.5.19 (myisam).
> This test work fine on django-3.2.16
>
> {{{
> class TransactionManagementErrorTest(TestCase):
>
>     class TestModel(models.Model):
>         field = models.IntegerField(null=True)
>
>         class Meta(object):
>             db_table = 'test_model_update_or_create'
>
>         class QuerySet(models.QuerySet):
>             def create(self, **kwargs):
>                 super().create(**kwargs)  # simulate parallel insertion
>                 return super().create(**kwargs)
>
>         class TestModelManager(models.Manager.from_queryset(QuerySet)):
>             pass
>
>         objects = TestModelManager()
>
>     def exec_sql(self, sql):
>         from django.db import connections, router
>         db = router.db_for_write(self.TestModel)
>         connection = connections[db]
>         connection.cursor().execute(sql)
>
>     def setUp(self):
>         super().setUp()
>         self.exec_sql(
>             'CREATE TABLE IF NOT EXISTS `test_model_update_or_create`
> (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `field` integer
> NULL);'
>         )
>
>     def tearDown(self):
>         self.exec_sql(
>             'DROP TABLE IF EXISTS `test_model_update_or_create`;'
>         )
>         super().tearDown()
>
>     def test_update_or_create(self):
>         self.TestModel.objects.update_or_create(id=1, defaults={'field':
> 2})
> }}}

New description:

 When using with **myisam**-only database the method **update_or_create**
 can occur a TransactionManagementError exception if creating a record was
 unsuccessful (because of IntegrityError, for example the record with a
 same primary key was already created by another process).

 The problem has started after the upgrading from Django 3.2.16 to 4.1.7

 The test below just simulates a parallel insertion of the record with the
 same PK^

 Database backend: mariadb server (10.5.19) (default-storage-engine is
 **myisam**).

 This test works fine with Django 3.2.16 and fails on Django 4.1.7+:
 {{{
 class TransactionManagementErrorTest(TestCase):

     class TestModel(models.Model):
         managed = False
         field = models.IntegerField(null=True)

         class Meta(object):
             db_table = 'test_model_update_or_create'

         class QuerySet(models.QuerySet):
             def create(self, **kwargs):
                 super().create(**kwargs)  # simulate parallel insertion
                 return super().create(**kwargs)

         class TestModelManager(models.Manager.from_queryset(QuerySet)):
             pass

         objects = TestModelManager()

     def exec_sql(self, sql):
         from django.db import connections, router
         db = router.db_for_write(self.TestModel)
         connection = connections[db]
         connection.cursor().execute(sql)

     def setUp(self):
         super().setUp()
         self.exec_sql(
             'CREATE TABLE IF NOT EXISTS `test_model_update_or_create`
 (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `field` integer NULL);'
         )

     def tearDown(self):
         self.exec_sql(
             'DROP TABLE IF EXISTS `test_model_update_or_create`;'
         )
         super().tearDown()

     def test_update_or_create(self):
         self.TestModel.objects.update_or_create(id=1, defaults={'field':
 2})
 }}}

 **Exception**: django.db.transaction.TransactionManagementError: An error
 occurred in the current transaction. You can't execute queries until the
 end of the 'atomic' block.

 **Failure stack:**
 {{{
 Traceback (most recent call last):
   File
 "/home/alex/workspace/test_TransactionManagementError/mysite/polls/tests.py",
 line 47, in test_update_or_create
     self.TestModel.objects.update_or_create(id=1, defaults={'field': 2})
   File
 "/home/alex/workspace/test_TransactionManagementError/.venv/lib/python3.9
 /site-packages/django/db/models/manager.py", line 87, in manager_method
     return getattr(self.get_queryset(), name)(*args, **kwargs)
   File
 "/home/alex/workspace/test_TransactionManagementError/.venv/lib/python3.9
 /site-packages/django/db/models/query.py", line 949, in update_or_create
     obj, created = self.select_for_update().get_or_create(defaults,
 **kwargs)
   File
 "/home/alex/workspace/test_TransactionManagementError/.venv/lib/python3.9
 /site-packages/django/db/models/query.py", line 926, in get_or_create
     return self.get(**kwargs), False
   File
 "/home/alex/workspace/test_TransactionManagementError/.venv/lib/python3.9
 /site-packages/django/db/models/query.py", line 633, in get
     num = len(clone)
   File
 "/home/alex/workspace/test_TransactionManagementError/.venv/lib/python3.9
 /site-packages/django/db/models/query.py", line 380, in __len__
     self._fetch_all()
   File
 "/home/alex/workspace/test_TransactionManagementError/.venv/lib/python3.9
 /site-packages/django/db/models/query.py", line 1881, in _fetch_all
     self._result_cache = list(self._iterable_class(self))
   File
 "/home/alex/workspace/test_TransactionManagementError/.venv/lib/python3.9
 /site-packages/django/db/models/query.py", line 91, in __iter__
     results = compiler.execute_sql(
   File
 "/home/alex/workspace/test_TransactionManagementError/.venv/lib/python3.9
 /site-packages/django/db/models/sql/compiler.py", line 1560, in
 execute_sql
     cursor.execute(sql, params)
   File
 "/home/alex/workspace/test_TransactionManagementError/.venv/lib/python3.9
 /site-packages/django/db/backends/utils.py", line 67, in execute
     return self._execute_with_wrappers(
   File
 "/home/alex/workspace/test_TransactionManagementError/.venv/lib/python3.9
 /site-packages/django/db/backends/utils.py", line 80, in
 _execute_with_wrappers
     return executor(sql, params, many, context)
   File
 "/home/alex/workspace/test_TransactionManagementError/.venv/lib/python3.9
 /site-packages/django/db/backends/utils.py", line 83, in _execute
     self.db.validate_no_broken_transaction()
   File
 "/home/alex/workspace/test_TransactionManagementError/.venv/lib/python3.9
 /site-packages/django/db/backends/base/base.py", line 531, in
 validate_no_broken_transaction
     raise TransactionManagementError(
 django.db.transaction.TransactionManagementError: An error occurred in the
 current transaction. You can't execute queries until the end of the
 'atomic' block.
 }}}

--

-- 
Ticket URL: <https://code.djangoproject.com/ticket/34523#comment:11>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/01070187c7fcf4ae-b8b0f8ae-ef69-4290-b729-45f248a2cfe5-000000%40eu-central-1.amazonses.com.

Reply via email to