面试官 : 你是怎么了解 InnoDB 引擎中的事务的?
候选者:在我的了解下,事务能够使「一组操作」要么全副胜利,要么全副失败
候选者:事务其目标是为了「保证数据最终的一致性」。
候选者:举个例子,我给你发支付宝转了 888 块红包。那天然我的支付宝余额会扣减 888 块,你的支付宝余额会减少 888 块。
候选者:而事务就是保障我的余额扣减跟你的余额削减是同时胜利或者同时失败的,这样这次转账就失常了
面试官 : 嗯,那你理解事务的几大个性吗?
候选者:嗯,就是 ACID 嘛,别离是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
候选者:原子性指的是:以后事务的操作要么同时胜利,要么同时失败。原子性由 undo log 日志来保障,因为 undo log 记录着数据批改前的信息。
候选者:比方咱们要 insert 一条数据了,那 undo log 会记录的一条对应的 delete 日志。咱们要 update 一条记录时,那 undo log 会记录之前的「旧值」的 update 记录。
候选者:如果执行事务过程中出现异常的状况,那执行「回滚」。InnoDB 引擎就是利用 undo log 记录下的数据,来将数据「复原」到事务开始之前
候选者:一致性我稍稍往后讲,我先来说下隔离性
面试官:嗯…
候选者:隔离性指的是:在事务「并发」执行时,他们外部的操作不能相互烦扰。如果多个事务能够同时操作一个数据,那么就会产生脏读、反复读、幻读的问题。
候选者:于是,事务与事务之间须要存在「肯定」的隔离。在 InnoDB 引擎中,定义了四种隔离级别供咱们应用:
候选者:别离是:read uncommit(读未提交)、read commit (读已提交)、repeatable read (可反复复读)、serializable (串行)
候选者:不同的隔离级别对事务之间的隔离性是不一样的(级别越高事务隔离性越好,但性能就越低),而隔离性是由 MySQL 的各种锁来实现的,只是它屏蔽了加锁的细节。
候选者:持久性指的就是:一旦提交了事务,它对数据库的扭转就应该是永久性的。说白了就是,会将数据长久化在硬盘上。
候选者:而持久性由 redo log 日志来保障,当咱们要批改数据时,MySQL 是先把这条记录所在的「页」找到,而后把该页加载到内存中,将对应记录进行批改。
候选者:为了避免内存批改完了,MySQL 就挂掉了(如果内存改完,间接挂掉,那这次的批改相当于就失落了)。
候选者:MySQL 引入了 redo log,内存写完了,而后会写一份 redo log,这份 redo log 记录着这次在某个页上做了什么批改。
候选者:即使 MySQL 在中途挂了,咱们还能够依据 redo log 来对数据进行复原。
候选者:redo log 是程序写的,写入速度很快。并且它记录的是物理批改(xxxx 页做了 xxx 批改),文件的体积很小,复原速度也很快。
候选者:回头再来讲一致性,「一致性」能够了解为咱们应用事务的「目标」,而「隔离性」「原子性」「持久性」均是为了保障「一致性」的伎俩,保障一致性须要由利用程序代码来保障
候选者:比方,如果事务在产生的过程中,呈现了异常情况,此时你就得回滚事务,而不是强行提交事务来导致数据不统一。
面试官:嗯,挺好的,讲了蛮多的
面试官 :方才你也提到了隔离性嘛, 而后你说在 MySQL 中有四种隔离级别,能别离来介绍下吗?
候选者:嗯,为了讲清楚隔离级别,我顺带来说下 MySQL 锁相干的常识吧。
候选者:在 InnoDB 引擎下,按锁的粒度分类,能够简略分为行锁和表锁。
候选者:行锁实际上是作用在索引之上的(索引上次曾经说过了,这里就不赘述了)。当咱们的 SQL 命中了索引,那锁住的就是命中条件内的索引节点(这种就是行锁),如果没有命中索引,那咱们锁的就是整个索引树(表锁)。
候选者:简略来说就是:锁住的是整棵树还是某几个节点,齐全取决于 SQL 条件是否有命中到对应的索引节点。
候选者:而行锁又能够简略分为读锁(共享锁、S 锁)和写锁(排它锁、X 锁)。
候选者:读锁是共享的,多个事务能够同时读取同一个资源,但不容许其余事务批改。写锁是排他的,写锁会阻塞其余的写锁和读锁。
候选者:我当初就再回到隔离级别上吧,就间接以例子来阐明啦。
面试官:嗯…
候选者:首先来说下 read uncommit(读未提交)。比如说:A 向 B 转账,A 执行了转账语句,但 A 还没有提交事务,B 读取数据,发现自己账户钱变多了!B 跟 A 说,我曾经收到钱了。A 回滚事务【rollback】,等 B 再查看账户的钱时,发现钱并没有多。
候选者:简略的定义就是:事务 B 读取到了事务 A 还没提交的数据,这种用专业术语来说叫做「脏读」。
候选者:对于锁的维度而言,其实就是在 read uncommit 隔离级别下,读不会加任何锁,而写会加排他锁。读什么锁都不加,这就让排他锁无奈排它了。
候选者:而咱们又晓得,对于更新操作而言,InnoDB 是必定会加写锁的(数据库是不可能容许在同一时间,更新同一条记录的)。而读操作,如果不加任何锁,那就会造成下面的脏读。
候选者:脏读在生产环境下必定是无奈承受的,那如果读加锁的话,那意味着:当更新数据的时,就没方法读取了,这会极大地升高数据库性能。
候选者 :在 MySQL InnoDB 引擎层面,又有新的解决方案(解决加锁后读写性能问题),叫做 MVCC(Multi-Version Concurrency Control) 多版本并发管制
候选者:在 MVCC 下,就能够做到读写不阻塞,且防止了相似脏读这样的问题。那 MVCC 是怎么做的呢?
候选者:MVCC 通过生成数据快照(Snapshot),并用这个快照来提供肯定级别(语句级或事务级)的一致性读取
候选者:回到事务隔离级别下,针对于 read commit (读已提交) 隔离级别,它生成的就是语句级快照,而针对于 repeatable read (可反复读),它生成的就是事务级的快照。
候选者:后面提到过 read uncommit 隔离级别下会产生脏读,而 read commit (读已提交) 隔离级别解决了脏读。思维其实很简略:在读取的时候生成一个”版本号”,等到其余事务 commit 了之后,才会读取最新已 commit 的”版本号”数据。
候选者:比如说:事务 A 读取了记录(生成版本号),事务 B 批改了记录(此时加了写锁),事务 A 再读取的时候,是根据最新的版本号来读取的(当事务 B 执行 commit 了之后,会生成一个新的版本号),如果事务 B 还没有 commit,那事务 A 读取的还是之前版本号的数据。
候选者:通过「版本」的概念,这样就解决了脏读的问题,而「版本」其实就是对应快照的数据。
候选者:read commit (读已提交) 解决了脏读,但也会有其余并发的问题。「不可反复读」:一个事务读取到另外一个事务曾经提交的数据,也就是说一个事务能够看到其余事务所做的批改。
候选者:不可反复读的例子:A 查询数据库失去数据,B 去批改数据库的数据,导致 A 屡次查询数据库的后果都不一样【危害:A 每次查问的后果都是受 B 的影响的】
候选者 :理解 MVCC 根底之后,就很容易想到 repeatable read (可反复复读) 隔离级别是怎么防止不可反复读的问题了(后面也提到了)。
候选者 :repeatable read (可反复复读) 隔离级别是「事务级别」的快照!每次读取的都是「以后事务的版本」,即便以后数据被其余事务批改了(commit),也只会读取以后事务版本的数据。
候选者 :而 repeatable read (可反复复读) 隔离级别会存在幻读的问题,「幻读」指的是指在一个事务内读取到了别的事务插入的数据,导致前后读取不统一。
候选者 :在 InnoDB 引擎下的的 repeatable read (可反复复读) 隔离级别下,快照读 MVCC 影响下,曾经解决了幻读的问题(因为它是读历史版本的数据)
候选者:而如果是以后读(指的是 select * from table for update),则须要配合间隙锁来解决幻读的问题。
候选者 :剩下的就是 serializable (串行) 隔离级别了,它的最高的隔离级别,相当于不容许事务的并发,事务与事务之间执行是串行的,它的效率最低,但同时也是最平安的。
面试官 :嗯,能够的。 我看你提到了 MVCC 了,无妨来说下他的原理?
候选者:MVCC 的次要是通过 read view 和 undo log 来实现的
候选者:undo log 后面也提到了,它会记录批改数据之前的信息,事务中的原子性就是通过 undo log 来实现的。所以,有 undo log 能够帮咱们找到「版本」的数据
候选者:而 read view 实际上就是在查问时,InnoDB 会生成一个 read view,read view 有几个重要的字段,别离是:trx_ids(尚未提交 commit 的事务版本号汇合),low_limit_id(下一次要生成的事务 ID 值),low_limit_id(尚未提交版本号的事务 ID 最小值)以及 creator_trx_id(以后的事务版本号)
候选者:在每行数据有两列暗藏的字段,别离是 DB_TRX_ID(记录着以后 ID)以及 DB_ROLL_PTR(指向上一个版本数据在 undo log 里的地位指针)
候选者:铺垫到这了,很容易就发现,MVCC 其实就是靠「比对版本」来实现读写不阻塞,而版本的数据存在于 undo log 中。
候选者:而针对于不同的隔离级别(read commit 和 repeatable read),无非就是 read commit 隔离级别下,每次都获取一个新的 read view,repeatable read 隔离级别则每次事务只获取一个 read view
面试官:嗯,OK 的。细节就不讲究了,明天就到这里吧。
本文总结:
- 事务为了保证数据的最终一致性
-
事务有四大个性,别离是原子性、一致性、隔离性、持久性
- 原子性由 undo log 保障
- 持久性由 redo log 保障
- 隔离性由数据库隔离级别供咱们抉择,别离有 read uncommit,read commit,repeatable read,serializable
- 一致性是事务的目标,一致性由应用程序来保障
- 事务并发会存在各种问题,别离有脏读、反复读、幻读问题。下面的不同隔离级别能够解决掉因为并发事务所造成的问题,而隔离级别实际上就是由 MySQL 锁来实现的
- 频繁加锁会导致数据库性能低下,引入了 MVCC 多版本控制来实现读写不阻塞,进步数据库性能
- MVCC 原理即通过 read view 以及 undo log 来实现
欢送关注我的微信公众号【Java3y】来聊聊 Java 面试
【对线面试官 - 挪动端】系列 一周两篇继续更新中!
【对线面试官 - 电脑端】系列 一周两篇继续更新中!
原创不易!!求三连!!