Если вам нужна последовательная нумерация без дырок, вы не должны использовать автоматически генерируемое поле id
Django в качестве номера заказа.
Чтобы гарантировать уникальность даже при параллельности Django создает последовательность базы данных который является объектом в базе данных, который производит новое значение каждый раз, когда к нему обращаются. Обратите внимание, что последовательность использует полученное значение, даже если оно нигде не сохранено в базе данных.
В таком случае происходит следующее: всякий раз, когда вы пытаетесь создать экземпляр, и эта операция завершается неудачно на уровне базы данных, число из последовательность потребляется в любом случае. Допустим, вы успешно создали свой первый ордер, он будет иметь идентификационный номер 1. Затем, допустим, вы пытаетесь создать второй ордер, но INSERT
в базе данных не удается (например, для проверки целостности или чего-то еще) , После того, как вы успешно создадите третий ордер, вы ожидаете, что этот ордер имеет идентификационный номер 2, но на самом деле он будет иметь идентификационный номер 3, поскольку номер 2 был использован из последовательности, даже если он не был сохранен.
Так что нет, вы не можете использовать id
, если вам нужно убедиться, что в ваших номерах заказов нет дыр.
Теперь, чтобы иметь последовательную нумерацию, вы можете просто добавить столбец
order_number = models.PositiveIntegerField(unique=True, null=True)
Вопрос в том, как правильно установить его значение. Поэтому в идеальном мире, где нет параллелизма (два процесса выполняют запросы к одной и той же базе данных), вы можете просто получить максимальный номер заказа, добавить 1, а затем сохранить это значение в order_number
. Дело в том, что если вы сделаете это наивно, у вас останутся дубликаты (на самом деле ошибки целостности, потому что unique=True
предотвратит дубликаты).
Одним из способов решения этой проблемы будет , чтобы заблокировать вашу таблицу (см. Это ТАК вопрос) пока вы вычисляете и обновляете свой номер заказа.
Как я полагаю, вам все равно, что номер заказа точно отражает порядок, в котором были созданы заказы, а только то, что он является последовательным и без дыры, что вы можете сделать, это выполнить запрос, подобный следующему, внутри транзакции (при условии, что ваша Order
модель находится в приложении orders
django):
UPDATE orders_order SET order_number = (SELECT COALESCE(MAX(order_number), 0) FROM orders_order) + 1 WHERE id = [yourid] AND order_number IS NULL
Теперь даже с этим запросом вы могут возникнуть проблемы с параллелизмом, , поскольку Django использует postgres уровень изоляции по умолчанию по умолчанию . Поэтому, чтобы сделать этот запрос безопасным, вам нужно изменить уровень изоляции. Обратитесь к этому вопросу SO , чтобы узнать, как получить два отдельных соединения с двумя разными уровнями изоляции. Чтобы сделать этот запрос безопасным, нужно установить для уровня изоляции значение SERIALIZABLE
.
. Предполагая, что вы смогли решить проблему с уровнем изоляции, необходимо выполнить этот запрос
* 1036. *
В приведенном выше фрагменте предполагается, что у вас есть заказ, для которого вы хотите установить номер заказа в переменной с именем order
. Если ваша изоляция правильная, тогда вы должны быть в безопасности.
Теперь есть третий вариант - - использовать select_for_update()
в качестве механизма блокировки таблицы (хотя он не предназначен для этого, но для блокировки на уровне строк). Таким образом, идея проста, так же, как прежде, чем вы сначала создадите свой заказ, а затем обновите его, чтобы установить номер заказа. Таким образом, чтобы гарантировать, что у вас не будет дубликатов (aka IntegrityError) номеров заказов, вы должны выполнить запрос, который выберет все Заказы в вашей БД, а затем использовать select_for_update()
следующим образом:
from django.db import transaction
with transaction.atomic():
# This locks every row in orders_order until the end of the transaction
Order.objects.all().select_for_update() # pointless query just to lock the table
max_on = Order.objects.aggregate(max_on=Max('order_number'))['max_on']
Order.objects.filter(id=order.id).update(order_number=max_on + 1)
До тех пор, пока вы уверены, что у вас есть хотя бы 1 заказ, прежде чем вводить кодовый блок выше, И что вы всегда сначала делаете полный select_for_update()
, тогда вы также должны быть в безопасности.
И вот способы, которыми я могу придумать, как решить последовательную нумерацию. Я бы хотел увидеть готовое решение для этого, но, к сожалению, я не знаю ни одного.