面试官问谈谈对mysql的事务隔离级别和锁机制的理解?

背景

当我们多个java客户端并非操作mysql中某一批数据CURD时,在java客户端层面不加锁的情况下,mysql可能会出现脏写,脏读,不可重复读,幻读的问题,为此mysql设计了事务隔离机制、锁机制、MCVCC多版本控制并发隔离机制一整套机制来解决多事务并发的问题。

事务及其ACID属性

  • 事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。
  • 原子性(Atomicity) :事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。例如:一个方法开启事务 下单 减库存 增加积分 在操作层面不可分开 ,也就是同时成功,或者者同时失败;
  • 一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性。例如: 一个方法开启事务 下单 减库存 增加积分 , 数据在一致的状态
  • 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。例如:在 RR(可重复读)级别 下(user表中有id、name字段),A session开启事务去查询user表 ,B session开启事务去更新user表中id为1的name,然后再在A session中再次查询user表,发现id为1的name依然跟第一次查询相同(在下面隔离级别有例子)
  • 持久性(Durable) :事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。例如:数据已持久化硬盘

并发事务带来哪些问题

  • 更新丢失(Lost Update)或脏写(Dirty Writes)
      当两个及以上的事务选择同一行,然后基于最初选定的(原)值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题– 最后的更新覆盖了由其他事务所做的更新 。
  • 脏读(Dirty Reads)
      一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致的状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此作进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象的叫做“脏读”。
      总结:事务A读到事务B已修改未提交的数据(事务A读取到了事务B已经修改但尚未提交的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。)
  • 不可重读(Non-Repeatable Reads)
      一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。
      总结:事务A读到事务B已提交的数据,导致事务A内部的相同查询语句在不同时刻读出的结果不一致,不符合隔离性
  • 幻读(Phantom Reads)
      一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。
      总结:事务A读取到了事务B提交的新增数据,不符合隔离性

如何解决脏读、不可重复读、幻读的问题

Ethan
脏读、不可重复读、幻读,其实都是数据库读一致性问题,为此数据库提供一定的事务隔离机制来解决(可参考上图)。
下面分析一下各个隔离级别下可能产生的问题

读未提交

客户端A 和 客户端B都设置隔离级别为读未提交
set tx_isolation='read-uncommitted';
客户端A:
  • 第一把读(事务不提交)

Ethan

  • 第二把读(读到客户端B事务未提交插入的数据)

在这里插入图片描述
第二把查询到的客户端B新增的数据后,这时客户端B回滚了,那此时客户端A查询到的数据就是脏数据,这时要想解决这个问题就要采用读已提交的隔离级别

客户端B
  • 第一把写(事务不提交)

Ethan

  • 第二把回滚事务
    在这里插入图片描述

读已提交

客户端A和客户端B都设置隔离级别读已提交
set tx_isolation='read-committed';
  • 客户端A:
    第一把查
    在这里插入图片描述
    第二把查(等客户端B写入后),发现与第一把查的一样,这时解决了脏读的问题
    在这里插入图片描述
    第三把查(客户端Bcommit后查),这时发现与之前查的新增一条数据,即产生了不可重复读的问题
    在这里插入图片描述

  • 客户端B:
    第一把写
    在这里插入图片描述

第二把执行commit操作
在这里插入图片描述

可重复读

客户端A和客户端B都设置隔离级别为可重复读
set tx_isolation='repeatable-read';
  • 客户端A
    第一把查
    在这里插入图片描述

    第二把查(在客户端B 事务commit之后),可以发现与第一次读的一样,因此解决了不可重复读的问题

    在这里插入图片描述
    第三把更新 客户端B新增的id为12的数据(更新前后对比一下),通过对比发现,更新id为12的数据后,再次查询多了一条id为12的数据,这时产生了幻读的问题
    在这里插入图片描述
    在这里插入图片描述

![在这里插入图片描述](https://img-blog.csdnimg.cn/18d97e743af04628ba682543665edc90.png

  • 客户端B
    第一把写和commit(待客户端A开启事务查完)
    在这里插入图片描述

可串行化

客户端A和客户端B都设置隔离级别为串行化
set tx_isolation='serializable';
  • 客户端A
    第一把开启事务查

在这里插入图片描述

  • 客户端B
    第一把更新id为1的发现被阻塞了(在客户端A开启事务之后),这种虽然解决了幻读问题,但是并发度性极低

在这里插入图片描述

Ethan

表锁:

好处: 每次操作锁住整张表。开销小,加锁快;不会出现死锁。
不足: 锁定粒度大,发生锁冲突的概率最高,并发度最低;一般用在整表数据迁移的场景。
  • 手动加表锁
lock table tab_name01 [read | write],tab_name02 [read | write];
  • 查看表上加过的锁
show open tables WHERE In_use > 0;
  • 删除表锁
unlock tables;
  • 案例(加读锁)

Ethan
从动图上可以看出,对user表手动加读锁之后,其他进程是能直接查询user表的。
Ethan
从动图上可以看出,对user表手动加读锁之后,其他进程不能直接对表进去写请求。

一句话:手动给表加读锁不阻塞其他进程对user表的读请求,但会阻塞写请求

  • 案例(加写锁)

Ethan
从动图中可以看出,给user表手动加写锁,其他进程的读请求会被阻塞。
Ethan
从动图中可以看出,给user表手动加写锁,其他进程的写请求也会被阻塞。
总结:不管是对MyISAM表还是InnoDB表加读锁时,不会阻塞其他进程读请求,但是会阻塞写请求;
加写锁时会阻塞其他进程读写请求。(读不阻塞写,写阻塞读写)

行锁:

好处:每次操作锁住一行数据。开销大,加锁慢;会出现死锁。
不足: 锁定粒度最小,发生锁冲突的概率最低,并发度最高。
  • 案例
    Ethan

一个session开启事务更新不提交,另一个session更新同一条记录会阻塞,更新不同记录不会阻塞

间隙锁(Gap Lock)

间隙锁顾名思义就是锁两个索引记录之间的空隙。
案例:
Ethan
按照表中的数据,间隙就有id为(3,10),(12,正无穷)

当执行SQL:
UPDATE account 
		SET money = money + 88 
		WHERE
			id > 3 
		AND id < 8;
那么此时就有间隙锁(310),还有行锁 锁住 id为10的这行记录,因此在(310]之间的数据不能insertupdate,而(320]就是临键锁		

临键锁(Next-key Locks)

临键锁是行锁和间隙锁的组合。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值