事务是主要针对于对一系列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`:读取已提交,其他事务提交了对数据的修改后,本事务就能读取到修改后的数据值