Видит ли select_for_update строки, добавленные другой транзакцией select_for_update после разблокировки? - PullRequest
0 голосов
/ 07 марта 2019

Я хочу создать модель с идентификатором, равным текущему наибольшему идентификатору для этой модели плюс один (например, автоинкремент). Я планирую сделать это с select_for_update, чтобы убедиться, что для текущего наибольшего идентификатора нет условия гонки, например:

with transaction.atomic():
    greatest_id = MyModel.objects.select_for_update().order_by('id').last().id
    MyModel.objects.create(id=greatest_id + 1)

Но мне интересно, если два процесса попытаются запустить это одновременно, как только второй разблокирует, будет ли он видеть новый наибольший идентификатор, вставленный первым процессом, или он все еще будет видеть старый наибольший идентификатор?

Например, скажем, текущий максимальный идентификатор равен 10. Два процесса идут для создания новой модели. Первый блокирует ID 10. Затем второй блокирует, потому что 10 заблокирован. Первый вставляет 11 и разблокирует 10. Затем второй разблокирует, и теперь он будет видеть 11, вставленный первым как самый большой, или он все еще будет видеть 10, потому что это строка, на которой он заблокирован?

В select_for_update docs написано:

Обычно, если другая транзакция уже получила блокировку для одной из выбранных строк, запрос будет блокироваться до тех пор, пока блокировка не будет снята.

Так что для моего примера, я думаю, это означает, что второй процесс перезапустит запрос для наибольшего идентификатора, как только он разблокируется и получит 11. Но я не уверен, что интерпретирую это правильно.

Примечание: я использую MySQL для БД.

Ответы [ 2 ]

1 голос
/ 07 марта 2019

Нет, я не думаю, что это сработает.

Во-первых, позвольте мне заметить, что вы должны обязательно проверить документацию для базы данных, которую вы используете, поскольку между базами данных есть много тонких различий, которыене фиксируются в документации Django.

При использовании документации PostgreSQL в качестве руководства проблема заключается в том, что при уровне изоляции READ COMMITTED по умолчанию заблокированный запрос не будет перезапущен.Когда первая транзакция фиксируется, заблокированная транзакция сможет увидеть изменения в этой строке, но не сможет увидеть, что были добавлены новые строки.

Возможно для команды обновлениячтобы увидеть противоречивый снимок: он может видеть влияние одновременных команд обновления на те же строки, которые пытается обновить, но не видит влияния этих команд на другие строки в базе данных.

Так что 10 - это то, что будет возвращено.

0 голосов
/ 07 марта 2019

Редактировать: Мое понимание в этом ответе неверно, я просто оставляю его для документации на случай, если я когда-нибудь захочу вернуться к нему.

После некоторого расследования я считаю, что это будет работать как задумано.

Причина в том, что для этого вызова:

MyModel.objects.select_for_update().order_by('id').last().id

SQL Django генерирует и работает с БД на самом деле:

SELECT ... FROM MyModel ORDER BY id ASC FOR UPDATE;

(вызов только last()происходит после того, как набор запросов уже оценен.)

Это означает, что запрос просматривает все строки при каждом запуске.Это означает, что во второй раз он подберет новую строку и вернет ее соответственно.

Я узнал, что это явление называется «фантомным чтением» и возможно, потому что уровень изоляции моего БД равен REPEATABLE-READ.


@ KevinChristopherHenry "Проблема в том, чтозапрос не повторяется после снятия блокировки, строки уже выбраны "Вы уверены, что так оно и есть?Почему READ COMMITTED подразумевает, что выбор не запускается после снятия блокировки?Я думал, что уровень изоляции определяет, какой снимок данных видит запрос, когда он выполняется, а не ~ когда запрос выполняется.Мне кажется, что выбор происходит до или после снятия блокировки, ортогонально уровню изоляции.И по определению, заблокированный запрос не выбирает строки до тех пор, пока он не будет разблокирован?

Для чего я пытался проверить это, открыв два отдельных соединения с моим БД в оболочке и выполнив некоторыезапросы.Во-первых, я начал транзакцию и получил блокировку «select * from MyModel order by id для обновления».Затем, во втором, я сделал то же самое, заставив select заблокироватьЗатем вернувшись в первую очередь, я вставил новую строку и подтвердил транзакцию.Затем во втором запрос разблокируется, и возвращается новая строка.Это заставляет меня думать, что моя гипотеза верна.

PS Наконец-то я прочитал документацию «нежелательных результатов», которую вы прочитали, и я понял вашу точку зрения - в этом примере похоже, что он игнорирует строки, которые не были предварительно выбраны, так что это может указывать на вывод, что мой второй запрос не подхватит новую строку.Но я проверил в оболочке, и это сделал.Теперь я не знаю, что с этим делать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...