Re: [Django] #34523: Model.objects.update_or_create method sometimes raises TransactionManagementError (was: update_or_create not work in parallel insertion)

1 view
Skip to first unread message

Django

unread,
Apr 28, 2023, 9:10:59 AM4/28/23
to django-...@googlegroups.com
#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.

Reply all
Reply to author
Forward
0 new messages