#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 django-updates+unsubscr...@googlegroups.com.
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