MySQL 事务隔离级别详解

MySQL 专栏收录该内容
14 篇文章 0 订阅

事务

数据库事务具有四个特征,简称为事务的ACID特性

1.什么是事务
开启一个事务可以包含一堆sql语句,这些sql语句要么都成功,要么一个也别想成功
2.事务作用
保证了对数据操作的数据安全性
3.事务的四大特性

  • 原子性(atomicity): 一个事务是一个不可分割的工作单位,事务中包含的诸多操作要么都成功,要么都不成功
  • 一致性(consistency): 事务必须是使数据从一个一致状态变到另一个一致状态,一致性与原子性是密切相关的
  • 隔离性(isolation): 一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并行的个事务之间不能互相干扰
  • 持久性(durability): 持久性也成永久性(permanence),指一个事务一旦提交,他对数据库中数据的改变就应该是永久性的,接下来的其他操作或故障不应该对其有任何影响 

4.事务使用

修改数据之前先开启事务操作
start transaction;
# 修改操作
update user set balance=900 where name='wsb'; #买支付100元
update user set balance=1010 where name='egon'; #中介拿走10元
update user set balance=1090 where name='ysb'; #卖家拿到90元
# 回滚到上一个状态
rollback;
# 开启事务后,只要没有执行commit操作,数据其实都没有真正刷到硬盘
commit;
'''
开启事务检测操作是否完整,不完整主动回滚到上一个状态,如果完整就应该执行commit操作
'''
# python代码角度,应该实现的伪代码逻辑
try:
    update user set balance=900 where name='wsb'; #买支付100元
    update user set balance=1010 where name='egon'; #中介拿走10元
    update user set balance=1090 where name='ysb'; #卖家拿到90元
except 异常:
    rollback;
else:
    commit;

5.什么是事务隔离

事务的隔离性是指在并发环境中,并发的事务是相互隔离的,
可以理解为多个事务同一时间段对数据库的增删改时是要隔离的

隔离种类

SQL标准中定义了四种数据库事务隔离级别,级别从低到高分别为:

  • 读未提交(Read Uncommitted)、-> 脏读
  • 读已提交(Read Committed)、 -> 不可重复读
  • 可重复读(Repeatable Read)、-> 换读
  • 串行化(Serializable)

在事务的并发操作中会出现脏读、不可重复读、幻读。在事务的并发操作中第二类更新丢失可以通过乐观锁和悲观锁解决。

读未提交(Read Uncommitted)

  • 该隔离级别,所有事务都可以看到其他交事务未提的执行结果。通俗地讲就是,在一个事务中可以读取到另一个事务中新增或修改但未提交的数据。
  • 该隔离级别可能导致的问题是脏读。因为另一个事务可能回滚,所以在第一个事务中读取到的数据很可能是无效的脏数据,造成脏读现象。
set tx_isolation='READ-UNCOMMITTED';

 案例:

脏读(读取未提交数据)

A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据。就好像原本的数据比较干净、纯粹,此时由于B事务更改了它,这个数据变得不再纯粹。这个时候A事务立即读取了这个脏数据,但事务B良心发现,又用回滚把数据恢复成原来干净、纯粹的样子,而事务A却什么都不知道,最终结果就是事务A读取了此次的脏数据,称为脏读。

这种情况常发生于转账与取款操作

读已提交(Read Committed) 

  • 这是大多数数据库系统的默认隔离级别(但不是mysql默认的)
  • 一个事务只能看见已经提交事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。
  • 该隔离级别可能导致的问题是不可重复读。因为两次执行同样的查询,可能会得到不一样的结果。
set tx_isolation='READ-COMMITTED';

 案例:

不可重复读(前后多次读取,数据内容不一致)

事务A在执行读取操作,由于整个事务A比较大,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B执行更改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,发现其年龄是30岁,和之前的数据不一样了,也就是数据和之前的不重复(不一样)了,系统不可以读取到重复的数据,称为不可重复读

可重复读(Repeatable Read)

  • 这是MySQL的默认事务隔离级别
  • 它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。通俗来讲,可重复读在一个事务里读取数据,怎么读都不会变,除非提交了该事务,再次进行读取。
  • 该隔离级别存在的问题是幻读
set tx_isolation='REPEATABLE-READ';

案例:

幻读(前后多次读取,数据总量不一致)

事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。

串行化(Serializable)

  • 这是最高的隔离级别
  • 它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。通俗地讲就是,假如两个事务都操作到同一数据行,那么这个数据行就会被锁定,只允许先读取/操作到数据行的事务优先操作,只有当事务提交了,数据行才会解锁,后一个事务才能成功操作这个数据行,否则只能一直等待
  • 该隔离级别可能导致大量的超时现象和锁竞争
set tx_isolation='SERIALIZABLE';

不可重复读和幻读到底有什么区别呢?

(1)不可重复读是读取了其他事务更改的数据,针对insert与update操作

解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。

(2)幻读是读取了其他事务新增的数据,针对insert与delete操作

解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。
 

乐观锁

乐观锁本身不是锁.而是一种判断机制

在更新语句中增加过滤条件,进行版本的判断,可以是这条记录的某个字段值。然后通过updata()所影响行数来判断是否更新成功。影响条数为0则更新失败,代表这条记录被其他事务修改了.

注意:

Django2.0以下版本使用乐观锁时需要把隔离机制设置成不可重复读

eg: 事务+乐观锁的使用

# django中使用 事务 + 乐观锁
# 这是一个商城订单提交要修改数据库案例

from rest_framework.views import APIView
from rest_framework.response import  Response
from django.db import transaction  # 导入事务
class Creat(APIView):
    @transaction.atomic  # 装饰事务
    def post(self,request):
        ......
        sid=transaction.savepoint()  # 对下面代码创建事务
        for product in all_product:
           product.product_id=str(product.product_id)
           order_data['order_total']+=product.price*buy_list[product.product_id]
           order_data['quantity']+=buy_list[product.product_id]
           #创建子订单
            for i in range(3):
               stock=product.stock.quantity  # 商品在库中的数量
               new_stock=stock-buy_list[product.product_id]  # 减掉用户购买数量
               if new_stock<0:  # 库存数小于用户购买数量
                  transaction.savepoint_rollback(sid)  # 回滚
                  return Response({"code":203,"msg":f"{product.name}库存不足"})
               # 使用乐观锁: quantity=字段, 在执行update前,锁定该字段的是否没有被改动,若改动了,则update影响调试为0
               res=models.Stock.objects.filter(quantity=stock,stock_id=product.stock.stock_id).update(quantity=new_stock)
               if not  res:
                  if i==2:
                    transaction.savepoint_rollback(sid)  # 回滚
                    return Response({"code": 203, "msg": f"创建订单失败"})
                  else:
                      continue
               else:
                  break

悲观锁

可以理解为就是一把互斥索,

悲观锁认为:事务A在操作数据时,这条数据同时也在被别的事务操作,所以事务在操作数据时加悲观锁(互斥锁),让这条数据不能被其他事务操作

select_for_update()

在查询数据时使用select_for_update()查询,就表示为该数据进行加锁,只有commint()完成后才会释放锁

案例:

class Creat(APIView):
    @transaction.atomic  # 装饰事务
    def post(self,request):
        # 创建保存点
        sid = transaction.savepoint()
        ......
        try:
            # goods=Goods.objects..get(id=goods_id)  # 不加锁
            goods=Goods.objects.select_for_update().get(id=goods_id)  # 加互斥锁
        execpt Goodsgoods.DoesNOTexist:
            # 回滚
            transaction.rollback(sid)
            return JsonResponse({'res':2, 'err':'商品信息有误'})
        ......
        transaction.commint()  # 提交事务
        return JsonResponse({'res':0, 'err':'订单创建成功'})

    悲观锁分为共享锁排它锁

    共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,共享锁是用来读取数据的。另外,一个事务获取了同一数据的共享锁,其他事务就不能获取该数据的排它锁。

    排它锁又称为写锁,简称X锁,顾名思义,排它锁就是不能与其他锁并存,如一个事务获取了一个数据行的排它锁,其他事务就不能再获取该行的其它锁,包括共享锁和排它锁。另外不存什么事务隔离级别,update/insert/delete会自动获取排它锁

    共享锁获取方式:select * from account where name='jack' lock in share mode;

    排它锁获取方式:select * from account where name='jack' for update;

MySQL分为表级锁和行级锁,共享锁和排它锁是行级锁。表级锁在此不做讨论。

 

  • 1
    点赞
  • 0
    评论
  • 1
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

i0208

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值