Complete e-commerce project-(8) Commodity order module (2): mysql optimistic lock and transaction isolation level

What is concurrency?

Recap:

  • The problem solved by the transaction in the previous section is that multiple logical tasks must succeed at the same time or fail at the same time (such as creating an order object and creating an order product object)

Concurrent scene

  • When the inventory is limited, but the colleagues have multiple users to buy, concurrency problems will occur.
  • There is 1 product included in product a, and users a and b purchase at the same time, and when the number of products in the database is read, they are all 1 ! , Both of them succeeded in creating an order, but : obviously there is only one item in the database, which is not enough to sell to two people. This is the concurrency problem to be solved next.

Ways to solve concurrency:

(1) Queuing

  • In essence, the requests are processed one by one, but the user does not need to feel that he is waiting for others to execute it before executing his request . Celery needs to be used . Start celery with only one process, and one order and one order request will be queued in the background.

(2) The scheme of using **'lock'**.

Pessimistic lock

  • Explanation: This lock is real, in which mysql database, add data to the specified lock , learned to know if the operating system, the equivalent of a critical resource. Similar to mutual exclusion locks, but the disadvantage is that it is easy to cause deadlocks if they are not handled well !
    Insert picture description here
select stock from tb_sku where id=1 for update;

SKU.objects.select_for_update().get(id=1)

Optimistic lock

  • Explanation: The optimistic lock is not a real lock, but when updating, it is judged whether the inventory at this time is the inventory previously queried. If it is the same, it means that no one has modified it and the inventory can be updated. Otherwise, it means that someone else has grabbed the resource. Perform inventory update again
update tb_sku set stock=2 where id=1 and stock=7;

SKU.objects.filter(id=1, stock=7).update(stock=2)

The problem of mysql transaction isolation level

  • Problem introduction: The above optimistic lock can already handle our business needs well, but one thing is that it is placed in a transaction . MySQL default transaction isolation level is repeatable read , that is to say, regardless of how other users to buy goods, not affect our transaction data to judge, because the read data transaction is not updated, join others bought the transaction is completed There are still 0 items , and what we read by Optimistic Lock is still the previous 1 , and will not refresh to read new data.
  • Solution: Change the transaction level of the database.
mysql transaction level
Serializable:
  • Serialization , the execution of one transaction
  • The execution efficiency is too low, which means that only one transaction is allowed to execute, and other transactions are allowed to execute after execution.
Repeatable read (default):
  • Repeatable reading , regardless of whether other transactions modify and commit the data, the data value seen in this transaction is always unaffected by other transactions
Read committed:
  • Read has been submitted , after other transactions have submitted the modification to the data, this transaction can read the modified data value
Read uncommitted:
  • Read uncommitted , as long as other transactions modify the data, even if it is not committed, the transaction can see the modified data value

When using optimistic locking, if a transaction modifies the inventory and commits the transaction, other transactions should be able to read the modified data value, so the repeatable read isolation level cannot be used, and it should be modified to read committed ( Read committed)

Modify transaction level mode (ubuntu):

Insert picture description here
Insert picture description here

Now it’s time to upload our processing business code

with transaction.atomic():   # 禁止事务自动提交
            # 开启事务
            sid = transaction.savepoint()
            # 2.创建订单基本对象
            order_id = '%s%09d' % (now.strftime('%Y%m%d%H%M%S'), user.id)
            total_count = 0
            total_amount = 0
            if pay_method == '1':
                # 待发货
                status = 1
            else:
                # 待支付
                status = 2
            order = OrderInfo.objects.create(
                order_id=order_id,
                user_id=user.id,
                address_id=address_id,
                total_count=0,
                total_amount=0,
                freight=10,
                pay_method=pay_method,
                status=status
            )

            # 3.查询商品对象
            skus = SKU.objects.filter(pk__in=cart_selected_int)

            # 4.遍历
            for sku in skus:
                cart_count = cart_dict_int.get(sku.id)
                # 4.1判断库存,不足则提示,如果足够则继续执行
                if sku.stock < cart_count:
                    # 回滚事务
                    transaction.savepoint_rollback(sid)
                    return http.JsonResponse({
    
    'code': RETCODE.PARAMERR, 'errmsg': '商品[%d]库存不足' % sku.id})

                # 4.2修改sku的库存、销量
                # sku.stock -= cart_count
                # sku.sales += cart_count
                # sku.save()

                # 4.2 使用乐观锁
                stock_old = sku.stock
                stock_new = sku.stock - cart_count
                sales_new = sku.sales + cart_count
                result = SKU.objects.filter(pk=sku.id, stock=stock_old).update(stock=stock_new, sales=sales_new)
                # result表示sql语句修改数据的个数
                if result == 0:
                    # 库存发生变化,未成功购买
                    transaction.savepoint_rollback(sid)
                    return http.JsonResponse({
    
    'code': RETCODE.PARAMERR, 'errmsg': '服务器忙,请稍候重试'})

                # 4.3创建订单商品对象
                order_sku = OrderGoods.objects.create(
                    order_id=order_id,
                    sku_id=sku.id,
                    count=cart_count,
                    price=sku.price
                )

                # 4.4 计算总金额、总数量
                total_count += cart_count
                total_amount += sku.price * cart_count

            # 5.修改订单对象的总金额、总数量
            order.total_count = total_count
            order.total_amount = total_amount + 10
            order.save()
            transaction.savepoint_commit(sid)
        # 6.删除购物车中选中的商品
        redis_cli.hdel('cart%d' % request.user.id, *cart_selected_int)
        redis_cli.srem('selected%d' % request.user.id, *cart_selected_int)

Okay, basically the processing is complete!

Guess you like

Origin blog.csdn.net/pythonstrat/article/details/108208527