MySQL 事务隔离 | 乐观锁
MySQL 事务隔离级别详解
数据库事务具有四个特征,简称为事务的ACID特性
原子性(Atomicity)、
一致性(Consistency)、
隔离性(Isoation)、
持久性(Durability),
什么是事务隔离
事务的隔离性是指在并发环境中,并发的事务是相互隔离的,
可以理解为多个事务同一时间段对数据库的增删改时是要隔离的
隔离种类
SQL标准中定义了四种数据库事务隔离级别,级别从低到高分别为:
读未提交(Read Uncommitted)、-> 脏读
读已提交(Read Committed)、 -> 不可重复读
可重复读(Repeatable Read)、-> 换读
串行化(Serializable)。
在事务的并发操作中会出现脏读、不可重复读、幻读。在事务的并发操作中第二类更新丢失可以通过乐观锁和悲观锁解决。
读未提交(Read Uncommitted)
- 该隔离级别,所有事务都可以看到其他未提交事务的执行结果。通俗地讲就是,在一个事务中可以读取到另一个事务中新增或修改但未提交的数据。
- 该隔离级别可能导致的问题是脏读。因为另一个事务可能回滚,所以在第一个事务中读取到的数据很可能是无效的脏数据,造成脏读现象。
>: set tx_isolation='READ-UNCOMMITTED';
eg:
脏读(读取未提交数据)
A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据。就好像原本的数据比较干净、纯粹,此时由于B事务更改了它,这个数据变得不再纯粹。这个时候A事务立即读取了这个脏数据,但事务B良心发现,又用回滚把数据恢复成原来干净、纯粹的样子,而事务A却什么都不知道,最终结果就是事务A读取了此次的脏数据,称为脏读。
这种情况常发生于转账与取款操作中
读已提交(Read Committed)
- 这是大多数数据库系统的默认隔离级别(但不是mysql默认的)
- 一个事务只能看见已经提交事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。
- 该隔离级别可能导致的问题是不可重复读。因为两次执行同样的查询,可能会得到不一样的结果。
>: set tx_isolation='READ-COMMITTED';
eg:
不可重复读(前后多次读取,数据内容不一致)
事务A在执行读取操作,由整个事务A比较大,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B执行更改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,发现其年龄是30岁,和之前的数据不一样了,也就是数据不重复了,系统不可以读取到重复的数据,成为不可重复读。
可重复读(Repeatable Read)
- 这是MySQL的默认事务隔离级别
- 它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。通俗来讲,可重复读在一个事务里读取数据,怎么读都不会变,除非提交了该事务,再次进行读取。
- 该隔离级别存在的问题是幻读
>: set tx_isolation='REPEATABLE-READ';
eg:
幻读(前后多次读取,数据总量不一致)
事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。
串行化(Serializable)
- 这是最高的隔离级别
- 它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。通俗地讲就是,假如两个事务都操作到同一数据行,那么这个数据行就会被锁定,只允许先读取/操作到数据行的事务优先操作,只有当事务提交了,数据行才会解锁,后一个事务才能成功操作这个数据行,否则只能一直等待
- 该隔离级别可能导致大量的超时现象和锁竞争。
>: set tx_isolation='SERIALIZABLE';
不可重复读和幻读到底有什么区别呢?
(1)不可重复读是读取了其他事务更改的数据,针对insert与update操作
解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。
(2)幻读是读取了其他事务新增的数据,针对insert与delete操作
解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。
乐观锁
在更新语句中增加过滤条件,进行版本的判断,可以是这条记录的某个字段值。然后通过影响行数来判断是否更新成功。影响条数为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
悲观锁
悲观锁分为共享锁和排它锁。
共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,共享锁是用来读取数据的。另外,一个事务获取了同一数据的共享锁,其他事务就不能获取该数据的排它锁。
排它锁又称为写锁,简称X锁,顾名思义,排它锁就是不能与其他所并存,如一个事务获取了一个数据行的排它锁,其他事务就不能再获取该行的其它锁,包括共享锁和排它锁。另外不存什么事务隔离级别,update/insert/delete会自动获取排它锁
共享锁获取方式:select * from account where name='jack' lock in share mode;
排它锁获取方式:select * from account where name='jack' for update;
MySQL分为表级锁和行级锁,共享锁和排它锁是行级锁。表级锁在此不做讨论。