(1)数据库事务
==关系数据库中的一系列操作的集合,该集合中的操作要么全部执行,要么全部不执行。
== 它具有ACID四大特性:
==原子性==(Atomicity);指事务是一个不可分割的单元,事务中的操作要么全部执行,要么全部不执行。
==一致性==(Consistency);事务执行前后数据的完整性必须保持一致。这里的完整性包括实体完整性、参照完整性、用户定义的完整性。
==隔离性==(Isolation);多个用户并发访问数据库时,数据库为每一个用户开启一个事务,多个事务并发执行时,相互之间是隔离开的,互不影响。
==持久性==(Durability);事务一旦被提交,对数据库中数据的改变是永久性性的。
(2)事务的隔离级别
==读未提交==;
事务A更新了数据,但是还没提交;事务B就读取到A更新的数据,事务A回滚后,B就看不到更新的数据。这种现象就是读未提交,即==脏读==
==读已提交==;
事务A更新数据并完成提交;事务B内部就可以读取到A更新的数据。
如:开始时,事务A、B读取的库存量都是100; 事务A更新库存为80,并进行了提交。由于事务B处理比较耗时,还没执行结束,B再次读取库存时,已经变成了80;最后B在更新库存时就要在80的基础上进行更新。
==重复读==;
事务A更新数据并完成提交;在事务B提交之前,其内部无法读取到A更新的数据,只能读取到之前的旧数据。(普通查询,即
快照读
) 容易出现==幻读==: 对于innodb存储引擎,可重复读的隔离级别,使用
当前读(加共享锁)
时容易出现幻读,即一个事务进行两次相同条件的查询时,后一次查询看到了上一次没有看到的行。
# 普通查询(快照读),可以重复读
mysql> select * from stu;
# 当前读(加共享锁),容易出现幻读,针对读取到的新行
mysql> select * from stu lock in share mode;
# 加入共享锁后,其他的事务无法进行写入操作,可以读
# 提交/回滚事务,才释放锁
mysql> commit/rollback
==串行化==;
所有事务串行执行,一个执行完成,再执行另一个。
(3)事务的shell测试
#查询当前会话(终端)的事务隔离级别(Mysql 8.0 +)
select @@transaction_isolation; # 查看当前事务的隔离级别
select @@global.transaction_isolation; # 查看当前系统的事务隔离级别
#设置当前会话的事务隔离级别
set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable
(4)锁概念
==悲观锁==:在查询语句上加锁,保证多事务==写入时==串行执行。 例如,在多个用户同时进行订单提交时,哪个事务先执行到该语句则获取锁,等事务结束后才会释放,其他事务阻塞等待获取锁,保证了同一时刻只有一个事务在写入,虽然保证了数据的一致性,但是牺牲了系统的处理并发的能力;
==乐观锁==:查询时不加锁,在变更时对比原数据与当前重新查询的数据是否一致,若不一致则本次变更失败(即乐观的认为当前没有其他事务在同时进行此过程),一般采用3次循环重复此过程,由于mysql数据库事务的隔离性默认为重复读,在事务结束之前,查询到的原数据不会更新,因此需要修改数据库事务隔离的级别为read committed(在Django2.0中已经自动将所有数据库的事务隔离级别修改为read committed),此时乐观锁可执行。
在冲突较少时,如订单并发量较少,使用乐观锁(省去加锁、释放锁的开销,提高性能),在冲突较多时,如订单并发量大,使用悲观锁。一般将整个事务的过程都放置于try中。
乐观锁和悲观锁:
#查询语句不加锁,即乐观锁
good = Goods.objects.get(id=good_id)
#查询语句加锁,即悲观锁,冲突较多时使用,且只能在事务内部使用
from django.db import transaction
with transaction.atomic():
point = transaction.savepoint()
try:
good = Goods.objects.select_for_update().get(id=good_id) #查询语句加锁,其他事务无法写入
except:
transaction.savepoint_rollback(point)
transaction.savepoint_commit(point)
悲观锁和乐观锁
-
判断 商品库存和要购买的数量
-
悲观锁:考虑 在修改库存之前,一定有人会打扰,因此使用 锁保护起来,同一时刻,只允许一个写入操作执行
-
乐观锁:考虑 在修改数据之前,没有人打扰,因此只是,在修改时,多了一步判断,判断原始数据是否被修改
流程:
with transaction.atomic(): # 生成新的订单 (开启事务)
point = transaction.savepoint() # 生成回滚节点
# # 创建订单
transaction.savepoint_rollback(point) # 回滚
# # 返回响应 订单生成失败
# 生成订单,修改总金额和总数量
transaction.savepoint_commit(point) # 提交事务
# # 删除后端redis数据库中:购物车数据,选中的商品
# # 生成支付地址
应用(1):
class OrderAPIView(APIView):
@check_login
def post(self, request):
# 1. 获取参数: 收货地址id、支付方式
products_list = request.data.get("productsArr")
addr_id = request.data.get('addrID')
pay_method = request.data.get('payMethod')
user = request.user
# 2. 组织数据
try:
addr = Address.objects.get(id=addr_id)
except Exception as e:
return Response({"code":204, 'msg': '地址不存在'})
if pay_method not in ['0', '1']:
return Response({'msg': '支付方式不合法'}, status=400)
# 订单编号
order_id = datetime.now().strftime('%Y%m%d%H%M%S') + ('%09d' % user.id) # 根据时间和用户id, 生成唯一的订单id
total_amount = 0
total_count = 0
freight = 10
# 3. 开启事务
with transaction.atomic(): # 开启事务
# 创建事务回滚点
point = transaction.savepoint()
# 4. 创建订单
try:
order = Order.objects.create(id=order_id, user=user, addr=addr, total_amount=total_amount, total_count=total_count, freight=10, pay_method=pay_method,pay_status="0") # 保存订单,生成订单对象
# 5. 循环遍历所有加购的商品,将具体的商品信息存入 订单商品表
for product in products_list:
try:
good = Good.objects.get(id=product.get("productID")) # 通过商品id,去mysql中查询 对应的商品信息
origin_stock = good.stock
origin_count = good.count
except:
return Response({"code":204, "msg":"商品已下架"})
if product.get("num") > good.stock:
# 回滚事务
transaction.savepoint_rollback(point) # 订单不足,下单失败,需要先回滚,后返回信息
return Response({"code":204, 'msg': '库存不足'})
# 6. 判断是否有其他人修改商品库存
n = Good.objects.filter(id=product.get("productID"), stock=origin_stock, count=origin_count)
# 修改库存之前,先判断库存是否为原始库存, 假设没有任何一条记录被修改,n就是0, n代表多少条数据被修改
if not n:
raise ValueError("前后数据不一致,创建订单失败")
# 将订单中的每个商品信息存入 订单商品表
OrderGood.objects.create(order=order, good=good, count=product.get("num"), price=good.selling_price)
good.stock = origin_stock - product.get("num")
good.count = origin_count + product.get("num")
good.save()
total_count += product.get("num") # 对每个商品的数量累加求和
total_amount += good.selling_price * product.get("num") # 对每个商品的小计累加求和
# 最后价格累加邮费
total_amount += freight
order.total_amount = total_amount
order.total_count = total_count
order.save() # 等到 循环累加完之后,订单保存一次即可
except Exception as e:
transaction.savepoint_rollback(point) # 下单失败,事务进行回滚,保证原始数据不变
return Response({'msg': "订单提交失败"}, status=500)
# 提交事务
transaction.savepoint_commit(point) # 所有操作成功,提交事务
# 移除redis中所有已经下过订单的商品信息
redis_cli = redis.Redis(host="localhost", port=6379, db=0, password='laufing')
for i in products_list:
redis_cli.hdel('cart:%s' % user.id, i.get("productID")) # 购物车中选中的商品id以及对应的数量
redis_cli.srem('cart_selected:%s' % user.id, i.get("productID")) # 移除 集合中所有选中商品的id
redis_cli.close()
# 6. 返回订单信息
return Response({"code":201, "msg":"创建订单成功"})
应用(2):
class OrderInfo(APIView):
def post(self, request):
# 验证用户是否登录
try:
user_info = request.user_info
user = User.objects.get(id=user_info.get("id"))
except Exception as e:
print(e)
return Response({
"code": 204,
"msg": "用户未登录"
})
# 提取前端提交的是数据
products_list = request.data.get("productsArr")
addr_id = request.data.get("addrID")
pay_method = request.data.get("payMethod")
# 更新商品信息库存减少销量增加
total_amount = 0 # 总价
total_count = 0 # 总数量
# 3.重组订单的数据,生成新的订单 (开启事务)
with transaction.atomic():
point = transaction.savepoint() # 生成回滚节点
order_id = datetime.datetime.now().strftime("%Y%m%d%H%M%S") \
+ "_%s" % user.id # 生成订单号
try:
order_data = Order.objects.create(
order_id=order_id,
user=user,
address_id=addr_id,
total_amount=total_amount,
freight=10,
total_count=total_count,
pay_method=pay_method,
pay_status=0
)
except Exception as e:
print(e)
transaction.savepoint_rollback(point) # 回滚
return Response({
"code": 204,
"msg": "生成订单失败!"
})
for i in products_list:
# 4.获取勾选商品信息,判断库存是否充足
good_data = Goods.objects.get(id=i.get("productID"))
# 乐观锁:获取初始的商品库存和数量
origin_stock = good_data.stock
origin_count = good_data.count
if good_data.stock < i.get("num"):
transaction.savepoint_rollback(point) # 回滚
return Response({
"code": 204,
"msg": "%s库存不足!" % good_data.sku_name
})
# 5.更新商品信息:库存减少,销量增加
total_amount += i.get("price") * i.get("num")
total_count += i.get("num")
# 乐观锁:操作数据时,看一下数据是否与原始数据一致
try:
good_data = Goods.objects.get(id=i.get("productID"),
stock=origin_stock,
count=origin_count)
except Exception as e:
print(e)
transaction.savepoint_rollback(point) # 回滚
return Response({
"code": 204,
"msg": "修改商品信息失败!"
})
good_data.stock -= i.get("num")
good_data.count += i.get("num")
good_data.save()
# 6.生成订单商品
try:
OrderGoods.objects.create(
good_id=int(i.get("productID")),
count=i.get("num"),
price=i.get("price"),
score=5,
order=order_data
)
except Exception as e:
print(e)
transaction.savepoint_rollback(point) # 回滚
return Response({
"code": 204,
"msg": "生成订单中商品详情失败!"
})
# 修改总金额和总数量
order_data.total_amount = total_amount
order_data.total_count = total_count
order_data.save()
transaction.savepoint_commit(point) # 提交事务
# 7. 删除后端redis数据库中:购物车数据,选中的商品
cart_key = "cart_%s" % user.username
cart_selected_key = "cart_selected_%s" % user.username
r = redis.Redis(host="127.0.0.1", port=6379, db=0)
for i in products_list:
r.hdel(cart_key, i.get("productID")) # 删除购物车中的数据
r.srem(cart_selected_key, i.get("productID")) # 删除选中的商品
# r.close()
# 生成支付地址
url = get_alipay_url(order_id, total_amount)
return Response({
"code": 200,
"msg": "生成订单成功!",
"url": url
})