事务的理解

事务是主要针对于对一系列mysql语句执行,要么提交:一起执行,要么回滚:一起不执行

事务主要是对mysql的增 删 改 而查询语句是可以在事务内提交事务前执行的,但是要注意事务的隔离性

事务的隔离性

Repeatable read:可重复读,无论其他事务是否修改并提交了数据,在这个事务中看到的数据值始终不受其他事务影响。
Read committed`:读取已提交,其他事务提交了对数据的修改后,本事务就能读取到修改后的数据值.

问题说明:

当两个顾客在上网抢购同一份商品(两次触发操作在10s内),无论mysql设置成可重复读性或读取已提交性,都会出现一边抢购ok,一边抢购失败,

理论结果:按理说隔离性设置为可重复读,应该同时抢购ok, 而隔离性设置为读取已提交,则为一边抢购ok,一边抢购失败

以下为python代码:
class OrderCommitView (View):
    def post(self, request):
        json_dict = json.loads(request.body.decode())
        address_id = json_dict.get('address_id')
        pay_method = json_dict.get('pay_method')
        if not all([address_id, pay_method]):
            return http.JsonResponse({'code': RETCODE.PARAMERR, 'errmsg': '参数不完整'})
        if int(pay_method) not in [1, 2]:
            return http.JsonResponse({'code': RETCODE.PARAMERR, 'errmsg': '支付方式无效'})
        try:
            addredss = Address.objects.filter(id=address_id, is_deleted=False)
        except Exception as e:
            print(e)
            return http.JsonResponse({'code': RETCODE.PARAMERR, 'errmsg': '地址id错误!'})
  # 开启事物,禁止自动提交
    with transaction.atomic():
        # 开启事务
        sid = transaction.savepoint()

        user = request.user
        now = datetime.now()
        # 1.查询购物车中选中的商品redis中的hash表和集合
        # 2.创建订单基本信息
        # 3.查询商品对象
        # 4.遍历,如果库存不够,则返回提示说商品数量不够。修改sku中的数量。创建order_goods中的订单商品。计算总数量和总价。
        # 5. 删除redis中的数据

        # 1.查询购物车中选中的商品redis中的hash表和集合
        redis_cli = get_redis_connection('carts')
        cart_dict_bytes = redis_cli.hgetall('cart%d' % user.id)
        cart_dict_int = {int(sku_id): int(count) for sku_id, count in cart_dict_bytes.items()}
        cart_set_bytes = redis_cli.smembers('selected%d' % user.id)
        cart_set_int = [int(sku_id) for sku_id in cart_set_bytes]
        # 2.创建订单基本信息 id用时间加sku_id
        # now.strftime:将time结构转换为指定格式的字符串。
        # now.strptime:将指定格式的字符串转为time结构。
        order_id = '%s%09d' % (now.strftime('%Y%m%d%H%M%S'), user.id)
        total_count = 0
        total_amount = 0
        if int(pay_method) == 1:
            status = 1
        elif int(pay_method) == 2:
            status = 2
        # 3.查询商品对象)
        try:
            orderinfo = models.OrderInfo.objects.create(order_id=order_id,
                                                        user_id=user.id,
                                                        address_id=address_id,
                                                        total_count=total_count,
                                                        total_amount=total_amount,
                                                        freight=10,
                                                        pay_method=pay_method,
                                                        status=status, )
        except Exception as e:
            print(e)
            transaction.savepoint_rollback(sid)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '重复提交购物车中的商品1'})
        # 3.查询商品对象
        skus = SKU.objects.filter(id__in=cart_set_int)
        # 4.遍历进行处理
        for sku in skus:
            cart_count = cart_dict_int.get(sku.id)
            if cart_count > sku.stock:
                transaction.savepoint_rollback(sid)
                return http.JsonResponse({'code': RETCODE.PARAMERR, 'errmsg': '%d商品库存不足!!' % sku.name})
            # sku.stock -= cart_count
            # sku.sales += cart_count
            print('------------10之前')
            print(sku.stock)
            print(sku.id)
            time.sleep(10)
            # 4.2使用乐观锁进行修改
            stock_old = sku.stock
            stock_new = sku.stock - cart_count
            sales_new = sku.sales + cart_count
            print('------------10之后')
            print(sku.stock)
            print(sku.id)

            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': '服务器忙,请稍候重试'})

            order_sku = models.OrderGoods.objects.create(order_id=order_id,
                                                          sku_id=sku.id,
                                                          count=cart_count,
                                                          price=sku.price,
                                                          )
            # 计算商品总数量和总价格
            total_count += cart_count
            total_amount += total_count * sku.price
            # 跳出该循环
        # 保存到orderinfo
        orderinfo.total_count = total_count
        orderinfo.total_amount = total_amount + 10
        orderinfo.save()

        # 提交事务
        transaction.savepoint_commit(sid)
        print('---------事务执行完成!')
    # 删除reids中的数据
    try:
        redis_cli.hdel('cart%d' % user.id, *cart_set_int)
        redis_cli.srem('selected%d' % user.id, *cart_set_int)
    except Exception as e:
        print(e)
        return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '重复提交购物车中的商品2'})
    return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'order_id': order_id})
# 以下为mysql打印日志:
-----------------------A连接相关处理----------------------------
2019-06-09T12:22:44.064037Z	  234 Connect	users@localhost on meiduo_mall using TCP/IP
2019-06-09T12:22:44.064341Z	  234 Query	SET AUTOCOMMIT = 0
2019-06-09T12:22:44.064599Z	  234 Query	SET AUTOCOMMIT = 1
2019-06-09T12:22:44.065008Z	  234 Query	SELECT @@SQL_AUTO_IS_NULL
2019-06-09T12:22:44.065503Z	  234 Query	SELECT VERSION()
2019-06-09T12:22:44.065975Z	  234 Quit	

2019-06-09T12:22:44.067078Z	  235 Connect	users@localhost on meiduo_mall using TCP/IP
2019-06-09T12:22:44.067346Z	  235 Query	SET AUTOCOMMIT = 0
2019-06-09T12:22:44.067573Z	  235 Query	SET AUTOCOMMIT = 1
2019-06-09T12:22:44.067883Z	  235 Query	SELECT `users_tb`.`id`, `users_tb`.`password`, `users_tb`.`last_login`, `users_tb`.`is_superuser`, `users_tb`.`username`, `users_tb`.`first_name`, `users_tb`.`last_name`, `users_tb`.`email`, `users_tb`.`is_staff`, `users_tb`.`is_active`, `users_tb`.`date_joined`, `users_tb`.`mobile`, `users_tb`.`email_active`, `users_tb`.`default_address_id` FROM `users_tb` WHERE `users_tb`.`id` = 37



-----------------------A----------------------------37

# A设置禁止自动提交  

2019-06-09T12:22:44.069893Z	  235 Query	SET AUTOCOMMIT = 0

# A保存节点 

2019-06-09T12:22:44.071101Z	  235 Query	SAVEPOINT `s139669907764992_x1`

# A插入orderinfo模型类

2019-06-09T12:22:44.076062Z	  235 Query	INSERT INTO `tb_order_info` (`create_time`, `update_time`, `order_id`, `user_id`, `address_id`, `total_count`, `total_amount`, `freight`, `pay_method`, `status`) VALUES ('2019-06-09 12:22:44.074158', '2019-06-09 12:22:44.074219', '20190609202244000000037', 37, 39, 0, '0.00', '10.00', 2, 2)

# A查询sku

2019-06-09T12:22:44.080854Z	  235 Query	SELECT `tb_sku`.`id`, `tb_sku`.`create_time`, `tb_sku`.`update_time`, `tb_sku`.`name`, `tb_sku`.`caption`, `tb_sku`.`spu_id`, `tb_sku`.`category_id`, `tb_sku`.`price`, `tb_sku`.`cost_price`, `tb_sku`.`market_price`, `tb_sku`.`stock`, `tb_sku`.`sales`, `tb_sku`.`comments`, `tb_sku`.`is_launched`, `tb_sku`.`default_image` FROM `tb_sku` WHERE `tb_sku`.`id` IN (9)

A sleeping-----------------------------------------------

-----------------------B连接相关处理----------------------------
2019-06-09T12:22:47.209590Z	  236 Connect	users@localhost on meiduo_mall using TCP/IP
2019-06-09T12:22:47.210304Z	  236 Query	SET AUTOCOMMIT = 0
2019-06-09T12:22:47.210888Z	  236 Query	SET AUTOCOMMIT = 1
2019-06-09T12:22:47.211445Z	  236 Query	SELECT @@SQL_AUTO_IS_NULL
2019-06-09T12:22:47.212262Z	  236 Query	SELECT VERSION()
2019-06-09T12:22:47.213766Z	  236 Quit	
2019-06-09T12:22:47.215720Z	  237 Connect	users@localhost on meiduo_mall using TCP/IP
2019-06-09T12:22:47.216069Z	  237 Query	SET AUTOCOMMIT = 0
2019-06-09T12:22:47.216423Z	  237 Query	SET AUTOCOMMIT = 1
2019-06-09T12:22:47.216882Z	  237 Query	SELECT `users_tb`.`id`, `users_tb`.`password`, `users_tb`.`last_login`, `users_tb`.`is_superuser`, `users_tb`.`username`, `users_tb`.`first_name`, `users_tb`.`last_name`, `users_tb`.`email`, `users_tb`.`is_staff`, `users_tb`.`is_active`, `users_tb`.`date_joined`, `users_tb`.`mobile`, `users_tb`.`email_active`, `users_tb`.`default_address_id` FROM `users_tb` WHERE `users_tb`.`id` = 36

-----------------------B----------------------------

# B禁止自动提交

2019-06-09T12:22:47.219462Z	  237 Query	SET AUTOCOMMIT = 0

# B保存节点

2019-06-09T12:22:47.219865Z	  237 Query	SAVEPOINT `s139669899110144_x1`

# B查询商品表

2019-06-09T12:22:47.222682Z	  237 Query	INSERT INTO `tb_order_info` (`create_time`, `update_time`, `order_id`, `user_id`, `address_id`, `total_count`, `total_amount`, `freight`, `pay_method`, `status`) VALUES ('2019-06-09 12:22:47.222070', '2019-06-09 12:22:47.222152', '20190609202247000000036', 36, 37, 0, '0.00', '10.00', 2, 2)

# B查询sku

2019-06-09T12:22:47.228467Z	  237 Query	SELECT `tb_sku`.`id`, `tb_sku`.`create_time`, `tb_sku`.`update_time`, `tb_sku`.`name`, `tb_sku`.`caption`, `tb_sku`.`spu_id`, `tb_sku`.`category_id`, `tb_sku`.`price`, `tb_sku`.`cost_price`, `tb_sku`.`market_price`, `tb_sku`.`stock`, `tb_sku`.`sales`, `tb_sku`.`comments`, `tb_sku`.`is_launched`, `tb_sku`.`default_image` FROM `tb_sku` WHERE `tb_sku`.`id` IN (9)

B sleeping--------------------------------------------
A 苏醒-----------------------------------------

-------------------------A成功--------------------
2019-06-09T12:22:54.090965Z	  235 Query	UPDATE `tb_sku` SET `stock` = 0, `sales` = 1 WHERE (`tb_sku`.`id` = 9 AND `tb_sku`.`stock` = 1)
# 
2019-06-09T12:22:54.092821Z	  235 Query	INSERT INTO `tb_order_goods` (`create_time`, `update_time`, `order_id`, `sku_id`, `count`, `price`, `comment`, `score`, `is_anonymous`, `is_commented`) VALUES ('2019-06-09 12:22:54.092150', '2019-06-09 12:22:54.092230', '20190609202244000000037', 9, 1, '3388.00', '', 5, 0, 0)

2019-06-09T12:22:54.097561Z	  235 Query	UPDATE `tb_order_info` SET `create_time` = '2019-06-09 12:22:44.074158', `update_time` = '2019-06-09 12:22:54.095917', `user_id` = 37, `address_id` = 39, `total_count` = 1, `total_amount` = '3398.00', `freight` = '10.00', `pay_method` = 2, `status` = 2 WHERE `tb_order_info`.`order_id` = '20190609202244000000037'

2019-06-09T12:22:54.098471Z	  235 Query	RELEASE SAVEPOINT `s139669907764992_x1`
2019-06-09T12:22:54.098799Z	  235 Query	COMMIT
2019-06-09T12:22:54.100929Z	  235 Query	SET AUTOCOMMIT = 1
2019-06-09T12:22:54.111609Z	  235 Quit	

-------------------------B失败后--------------------
2019-06-09T12:22:57.236235Z	  237 Query	UPDATE `tb_sku` SET `stock` = 0, `sales` = 1 WHERE (`tb_sku`.`id` = 9 AND `tb_sku`.`stock` = 1)
2019-06-09T12:22:57.236676Z	  237 Query	ROLLBACK TO SAVEPOINT `s139669899110144_x1`
2019-06-09T12:22:57.239096Z	  237 Query	COMMIT
2019-06-09T12:22:57.239342Z	  237 Query	SET AUTOCOMMIT = 1
2019-06-09T12:22:57.240342Z	  237 Quit	

分析:

result = SKU.objects.filter(pk=sku.id, stock=stock_old).update(stock=stock_new, sales=sales_new)

对应的mysql执行语句:

2019-06-09T12:22:54.090965Z 235 Query UPDATE tb_sku SET stock = 0, sales = 1 WHERE (tb_sku.id = 9 AND tb_sku.stock = 1)

2019-06-09T12:22:57.236235Z 237 Query UPDATE tb_sku SET stock = 0, sales = 1 WHERE (tb_sku.id = 9 AND tb_sku.stock = 1)

结论:

Repeatable read:可重复读,无论其他事务是否修改并提交了数据,在这个事务中看到的数据值始终不受其他事务影响。

如果对于普通的select查询或者在django中使用: SKU.objects.get/filter(pk=sku.id,stock=stock_old)进行查询数据值是不会受其他事务的影响,
但是在这里它将查询的结果(SKU.objects.filter(pk=sku.id, stock=stock_old))作为了查询条件是不受事务隔离性地约束的,

result = SKU.objects.filter(pk=sku.id,stock=stock_old).update(stock=stock_new, sales=sales_new)

Query	UPDATE `tb_sku` SET `stock` = 0, `sales` = 1 WHERE (`tb_sku`.`id` = 9 AND `tb_sku`.`stock` = 1)

Read committed`:读取已提交,其他事务提交了对数据的修改后,本事务就能读取到修改后的数据值

你可能感兴趣的:(python)