面试必背概念:
(1) 什么是事务?
超简单地说,事务就是多条SQL语句组合(或者单条SQL)去完成的一个业务场景,高深一点的概念叫做逻辑操作单元,也就是说,这么多条SQL(或者单条SQL)都成功执行,才能一个完整的事务执行。
(2)什么是事务的ACID?
A:atomicity。原子性。那一个SQL操作组合,要是全部执行成功,要么全部不执行,要么全部回滚,不允许一些成功一些不成功。
C:consistency。一致性。这是针对数据来说的,在操作数据的时候必须是从一种状态变换到另外一种状态。
I : isolation。隔离性。事务并发执行的时候,每个事务都有各自的空间,它们相互隔离,不相互干扰。
D:durability。持久性。数据发生改变并且事务提交后,数据的变更就会永久保存到数据库中。
(3)如何开启事务,提交事务,回滚事务?
emmmmmm…还是看菜鸟教程吧👇
如果觉得写得很乱,请看本地小Demo(不可重复读的隔离级别下)。
注意我上面说的写操作是针对Update,如果是新增,则不会造成阻塞,但是会出现幻读。
你以为这就是最灵异事件了吗?还有更可怕的:
还有,请看下面这个案例
如果想要读到其他事务未提交但是已经修改的数据的业务场景,可以用读未提交隔离级别Read -Uncommitted ,如果是只能读到其他事务已提交的数据的业务场景,使用Read-commited 读已提交,如果想事务之间的数据不要产生脏读和防止部分的幻读,那么请使用REPEATABLE-READ可重复读。
(4)什么是事务的隔离级别?分别有哪些隔离级别?
你在自己的Mysql中输入:SHOW VARIABLES LIKE ‘transaction_isolation’; 就可以看到👇
隔离级别:
1:Read Uncommitted 读未提交:顾名思义就是可以读取到其他事务修改了但是未提交的数据,随时可能回滚,这种隔离级别脏读幻读不可重复读都会发生
2:Read Commited 读已提交:就是只能读取到其他事务提交了的变更,可以避免脏读,无法避免幻读和不可重复读
3:Repeatable Read 可重复读:通过版本号控制哪些数据对于当前事务不可见,哪些可见等等,能确保事务多次读取的值都是相同的,在这个事务持续期间,禁止其他事务对该行数据进行更新修改,可以避免脏读和不可重复读,但是幻读可能会存在(MySql默认的)
4:Serilizable 穿行化:一个事务执行期间,禁止其他事务对数据表操作,从而避免了并发问题,可避免以上出现的全部问题,但是效率最低下
(5)什么是脏读?幻读?不可重复读?
这些都是数据库的事务并发操作造成的问题
脏读:A修改了id=2但是未提交,B事务开启但是未提交的状态下读取到了A修改的数据,然后A回滚了,B读取到的数据是无效的脏读
不可重复读:A读到了id=1此时A事务未提交未结束,B事务开启修改id=2未提交结束,A事务再读取的时候数据变了(此时A依旧未提交,也就是在一个事务中读取同一个数据得不到同一个值)
幻读:A读取了一张表,一共三条数据(未提交),B新插入了几行(未提交),A再次读取会发现多了几行(未提交)
下面开始讲羞涩难懂的大道理
MVCC机制(度娘大版概念图镇楼)
是不是很抽象?什么版本?什么多版本?什么控制?什么快照?马什么梅?什么冬梅呀?
解释:它的意思其实是,数据库中的数据,针对每个事务开始时间的不同,运行时间的不同,结束提交的时候不同,在同一个时刻内,看到的相同表里的同一行数据是不相同的。因为------它们身处在不同的版本。(这种恐怖故事在我刚出来实习时候就有幸遇上了)
MVCC是基于轻量级锁的思想概念实现的,依靠三个得力助手:Undo Log 日志 , Read View 视图副本,表引擎为InnoDB的表的三个隐藏字段(DB_ROW_ID,DB_TRX_ID,DB_ROLL_PTR)
【1】Undo Log : 具有表结构的日志文件,记录着回滚指针的ID的回滚段,还有对应行的旧数据,在事务开始操作之前会把数据库中对应的表的行的内容,copy 一份记录到Undo Log的缓冲区中,在发生回滚的时候,根据回滚指针找到对应的回滚段,再把旧的数据回滚刷新到磁盘。其中Undo Log 里面又有:Insert Undo Log 和 Update Undo Log 。
Insert Undo Log :插入回滚日志,在插入类型操作事务commit的时候清理掉。
Update Undo Log :更新回滚日志,更新类型操作事务用来维护读取数据的一致性和事务回滚,在没有事务需要对这部分数据进行读取的一致性时候清理。
【2】Read View :用来保存一大堆当前活跃的事务ID,由于隔离性,事务与事务之间是无法知道对方的事务版本ID的,而事务版本ID,负责控制当前事务哪些数据可见哪些不可见,是数据一致性的重要保障。它底层有四个重要的参数:(trx_ids,low_limit_id,up_limit_id,creator_trx_id),不同的隔离级别,视图副本开启的时机也是不一样的,例如读已提交和读未提交这些,由于存在脏读幻读,一直读取的都是修改的数据而不从视图副本中读取旧值,所以它们的视图副本是默认不开启或者是在commit前开启。而可重复读,则是在事务开始时开启,保证每次都是读取对应版本副本中的数值,以此保证一致性。
trx_ids:当前活跃的所有事务版本ID(一个[XX,XX,XX]集合)
low_limit_id:新的版本事务ID
up_limit_id:最老的版本事务ID
creator_trx_id: 创建当前read view的事务版本号,也可以简单称作当前版本号
(从大佬源码博客那边偷来的一张概念图,好羞耻,偷图贼)
靠这个东西的这么多参数,怎么实现的多版本控制呢?答案是可见性比较算法:
事务查找表的数据记录时候,会根据当前版本号和对应数据表中行的最新版本号比较:
1:如果大于最新版本,则说明该行数据是在当前read view 副本创建之后才产生的,所以对于当前不给予显示。
2:如果小于最小版本,则是当前版本的事务开始前数据就存在了,就可以显示
3:如果大于最小版本,小于最大版本,则是这个数据在当前事务开始时候未提交,这时候再把当前事务版本ID跟trx_ids集合中的活跃事务版本做匹配。
3.1:如果存在,且等于当前事务ID,则说明是自己创建的,当然可见
3.2:如果存在但是不等于当前事务ID,则说明是别的事务创建的数据,不可见
3.3如果不存在trx_ids,则说明不是当前活跃的所有事务版本,说明早就已经commit 了,所以可见。
【3】InnoDB的表的三个隐藏字段(DB_ROW_ID,DB_TRX_ID,DB_ROLL_PTR)
注意:MyIsam表引擎不支持事务,必须是InnoDB,同时InnoDB引擎通过MVCC来实现,就是在每一行的数据中会有三个隐藏的列(其实还会有一个删除标记位)
(1)DB_ROW_ID: 当前行的ID。类似自增主键会进行自增,并且如果该表没有主键和唯一性索引,数据库自动选择这一列作为聚簇索引,有主键或者唯一索引的话就不会生成索引。
(2)DB_TRX_ID:当前行的事务版本号。表示这一行数据最后插入或修改的事务id,此外删除在内部也被当作一次更新,在行的特殊位置添加一个删除标记。读取数据时候就是拿这个跟事务的版本号经过可见性比较算法对比来决定是否展示。(deleted_bit 删除标记位:0可见的有效记录,1已被删除的可见但无效记录)
(3)DB_ROLL_PTR:回滚指针。指向上一个版本数据在undo log 里的位置指针;专门用于事务回滚。配合上undo日志完成回滚。