python - 我应该使用模型的 ID 处理订单号吗?
问题描述
我有一个个人电子商务网站。
我使用模型的 ID 作为订单号。只是因为它看起来很合乎逻辑,而且我期望 ID 每次都会增加 1。
但是,我注意到我的订单(我的订单模型)的 ID 已经跳跃了两次:
a) 从 54 到 86(相差 32)。
b) 从 99 到 132(相差 33)。
不知道为什么,不知道我是否应该使用自定义字段而不是模型 ID。
我正在使用 Django 3.0 并在 Heroku 上托管我的项目。
模型.py:
class Order(models.Model):
ORDER_STATUS = (
('recibido_pagado', 'Recibido y pagado'),
('recibido_no_pagado', 'Recibido pero no pagado'),
('en_proceso', 'En proceso'),
('en_camino', 'En camino'),
('entregado', 'Entregado'),
('cancelado', 'Cancelado por no pagar' )
)
token = models.CharField(max_length=100, blank=True, null=True)
first_name = models.CharField(max_length=50, blank=True, null=True)
last_name = models.CharField(max_length=50, blank=True, null=True)
phone_number = models.CharField(max_length=30, blank=True)
total = models.DecimalField(max_digits=10, decimal_places=2)
stickers_price = models.DecimalField(max_digits=10, decimal_places=2)
discount = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0.00'))
shipping_cost = models.DecimalField(max_digits=10, decimal_places=2)
email = models.EmailField(max_length=250, blank = True, verbose_name= 'Correo electrónico')
last_four = models.CharField(max_length=100, blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
shipping_address = models.CharField(max_length=100, blank=True, null=True)
shipping_address1 = models.CharField(max_length=100, blank=True, null=True)
reference = models.CharField(max_length=100, blank=True, null=True)
shipping_department = models.CharField(max_length=100, blank=True, null=True)
shipping_province = models.CharField(max_length=100, blank=True, null=True)
shipping_district = models.CharField(max_length=100, blank=True, null=True)
reason = models.CharField(max_length=400, blank=True, null=True, default='')
status = models.CharField(max_length=20, choices=ORDER_STATUS, default='recibido_pagado')
comments = models.CharField(max_length=400, blank=True, null=True, default='')
cupon = models.ForeignKey('marketing.Cupons', blank=True, null=True, default=None, on_delete=models.SET_NULL)
class Meta:
db_table = 'Order'
ordering = ['-created']
def __str__(self):
return str(self.id)
def igv(self):
igv = int(self.total) * 18/100
return igv
def shipping_date(self):
shipping_date = self.created + datetime.timedelta(days=10)
return shipping_date
def deposit_payment_date(self):
deposit_payment_date = self.created + datetime.timedelta(days=2)
return
创建订单的视图:
@csrf_exempt
def cart_charge_deposit_payment(request):
amount = request.POST.get('amount')
email = request.user.email
shipping_address = request.POST.get('shipping_address')
shipping_cost = request.POST.get('shipping_cost')
discount = request.POST.get('discount')
stickers_price = request.POST.get('stickers_price')
comments = request.POST.get('comments')
last_four = 1111
transaction_amount = amount
first_name = request.user.first_name
last_name = request.user.last_name
phone_number = request.user.profile.phone_number
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
shipping_address1 = request.user.profile.shipping_address1
reference = request.user.profile.reference
shipping_department = request.user.profile.shipping_department
shipping_province = request.user.profile.shipping_province
shipping_district = request.user.profile.shipping_district
order_details = Order.objects.create(
token='Random',
first_name=first_name,
last_name=last_name,
phone_number=phone_number,
email=email, # Using email entered in Culqi module, NOT user.email. Could be diff.
total=transaction_amount,
stickers_price = stickers_price,
discount = discount,
shipping_cost=shipping_cost,
last_four=last_four,
created=current_time,
shipping_address=shipping_address,
shipping_address1=shipping_address1,
reference=reference,
shipping_department=shipping_department,
shipping_province=shipping_province,
shipping_district=shipping_district,
status='recibido_no_pagado',
cupon=cupon,
comments=comments
)
...
解决方案
如果您需要连续编号而没有孔,则不应使用 Django 的自动生成id
字段作为您的订单号。
为了即使在并发下也能保证唯一性,Django 创建了一个数据库序列,它是数据库中的一个对象,每次查询时都会产生一个新值。请注意,即使未将其保存到任何地方的数据库中,该序列也会使用生成的值。
然后发生的情况是,每当您尝试创建实例并且此操作在数据库级别失败时,无论如何都会消耗序列中的一个数字。因此,假设您成功创建了第一个订单,它的 ID 号为 1。然后假设您尝试创建第二个订单,但INSERT
数据库中的订单失败(例如某些完整性检查或其他)。之后您成功创建了第三个订单,您会期望该订单的 ID 号为 2,但实际上它的 ID 号为 3,因为即使没有保存,也已从序列中消费了 2 号。
id
所以不,如果您需要确保订单号中没有漏洞,则不能使用。
现在为了有连续的编号,您可以简单地添加一列
order_number = models.PositiveIntegerField(unique=True, null=True)
问题是如何正确设置其值。因此,在没有并发的理想世界中(两个进程对同一个数据库运行查询),您可以简单地获得迄今为止的最大订单号,加 1 然后将此值保存到order_number
. 问题是如果你天真地这样做,你最终会出现重复(实际上是完整性错误,因为unique=True
会防止重复)。
解决此问题的一种方法是在计算和更新订单号时锁定您的表(请参阅此 SO 问题) 。
正如我假设您并不关心订单号是否忠实地反映了创建订单的顺序,而只是它是顺序的并且没有漏洞,您可以做的是在事务中运行如下查询(假设您的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
.
假设您能够解决隔离级别问题,那么关于如何运行此查询的问题
from django.db import connections, transaction
with transaction.atomic(using='your_isolated_db_alias'):
with connections['your_isolated_db_alias'].cursor() as cursor:
cursor.execute('UPDATE orders_order SET order_number = (SELECT COALESCE(MAX(order_number), 0) FROM orders_order) + 1 WHERE id = %s AND order_number IS NULL', order.id)
上面的代码片段假设您有想要在名为 的变量中设置订单号的订单order
。如果您的隔离是正确的,那么您应该是安全的。
现在有第三种选择,它用作select_for_update()
表锁定机制(尽管它不是为此而设计的,而是用于行级锁定)。所以这个想法很简单,就像在您首先创建订单然后更新它以设置订单号之前一样。因此,为了保证您不会得到重复的(又名 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()
先完成全部订单,那么您也应该是安全的。
这些是我能想到的如何解决连续编号的方法。我很想看到一个开箱即用的解决方案,但不幸的是我不知道。
推荐阅读
- c# - 如何用C#连接mysql8.0
- kubernetes - 使用 wilcard 和 Cloudflare DNS 进行证书管理,停留在“OrderCreated”
- flutter - 错误:函数声称它不返回任何东西
- python - Django:使用 ExpressionWrapper 的查询集
- ios - 当应用程序处于运行/后台状态时,为什么动态链接在 ios 设备上不起作用?
- javascript - 使用 JavaScript 为所有未点击的圆圈更改 SVG 圆圈的 CSS 属性
- javascript - 如何将html +刀片添加到google maps api的javascript变量中
- python - mp4 元数据未找到但存在
- android - 由于不推荐使用 ViewModelProviders.of(),我应该如何创建 ViewModel 的对象?
- reactjs - 如何使用 React Hooks 重写它?