当前位置: 首页 > 科技观察

阿里面试官:MySQL是如何实现ACID的?

时间:2023-03-19 21:17:11 科技观察

作为大厂后端新生,两本书靠岸,深知一路无人带路的艰辛,也想把自己的心路历程和心得分享给大家。后期大厂访谈系列持续更新中...1上一篇之前,有同学在阿里巴巴二面被问到:MYSQL是如何实现ACID的?其实如果简单介绍一下ACID是什么,大家肯定能回答出来。但是,如果你要回答底层是如何实现ACID特性的,就得考验你的本事了!今天笔者就简单谈谈我对ACID特性实现原理的理解。本文主要讨论MYSQLInnoDB引擎下的ACID实现原理,简单回顾一下什么是事务和隔离级别。2事务和ACID什么是事务?书中给出的概念太多,难以理解。作者对事务的理解:一系列操作,要么全部成功,要么全部失败。它具有ACID的四个特性。并发下,可能会出现脏读、幻读、不可重复读等并发问题,所以引入了四个隔离级别。01事务ACID特性MYSQL作为关系型数据库,最常见的InnoDB引擎如何保证ACID。(Atomicity)原子性:有些列操作要么全部成功,要么全部失败。(Isolation)隔离:一个事务的结果只有在其他事务提交时才可见。(Consistency)一致性:数据库总是从一种一致状态变为另一种一致状态。状态(事务修改前后的数据一般保证传输一致)(持久性)持久性:事务提交后,数据修改是永久性的02原子性。它是什么?如果想了解更多或者内部是如何实现的,可以仔细阅读本书。这里我简单分享一下我的理解。知道这些,面试基本就够了。Undolog,即回滚日志,可以用来实现隔离MVCC,保证原子性。稍后将讨论MVCC。实现原子性的关键是在事务回滚时能够撤销所有成功执行的SQL语句。当事务修改数据库时,InnoDB会生成对应的undolog。撤销日志会保存事务开始前的旧版本数据。当事务发生异常时,会回滚到旧版本。当发生回滚时,InnoDB会根据undolog的内容做相反的逻辑操作。insert语句在回滚时会执行delete;delete语句会执行insert;回滚时的update语句,回滚时会执行相反的update将数据改回来。总之,MYSQL的原子性是由undolog保证的。undolog的作用我做了一个总结:作用:undolog记录事务开始前的旧版本数据,用于实现回滚,保证原子性,实现MVCC。undolog中保存修改前的旧版本数据,然后行记录中有一个隐藏字段回滚指针指向旧版本。03持久化在说持久化之前,我们首先要知道redolog。老规矩,如果你想学习和理解看书,这里只是作者的访谈和答案分享。我们举一个生活小案例来理解:redolog是一种物理日志。它类似于用于卸货的小推车。如果我们把每件物品都卸下来再入库,岂不是浪费时间(效率低下,还要找一个合适的存放位置)。这时候如果有手推车,我们先把货物放在手推车里,等手推车装满了再把货物放在仓库里。岂不是大大提高了效率。MYSQL中也使用了类似的思想。当我们更新数据库时,首先将更新操作记录在redolog中,等到redolog满了或者MYSQL空闲了再刷盘。其实就是MySQL中经常提到的WAL技术。WAL的全称是Write-AheadLogging。它的关键点是先写日志,再写磁盘,也就是先装小车,不忙的时候再装库。总之,MYSQL的持久化是靠重做日志来保证的。我对redolog的作用做了一个总结:redolog物理日志的作用:会记录事务启动后对数据的修改。crash-safe特性:空间是固定的,写入后会循环写入。有两个指针writepos指向当前记录位置,checkpoint指向要擦除的位置。Redolog相当于一辆取货小车。货品多了,一件一件存起来就来不及了。先将货物放入手推车,待货物不多或手推车满载或店铺空闲时,再将货物送入仓库。用于crash-safe,redolog可用于数据库异常掉电时的恢复。下面只是为了理解:写过程:先写redologbuffer,再写到文件系统的pagecache,此时不持久化,然后fsync持久化到磁盘写策略:根据innodb_flush_log_at_trx_commit参数控制(我的记忆:Innodb根据事务的commit方式刷新日志)0-->事务提交时,redologbuffer中只剩下redolog1-->redolog直接持久化到磁盘(所以就有了双“1”的配置,后面会讲到)2——>把redolog写到pagecache04隔离说到隔离,我们都知道MYSQL有四个隔离级别来解决现有的并发问题.脏读、幻读和不可重复读。那么不同隔离级别的隔离是如何实现的呢?具体实现原理是什么?接下来,我们就来说说吧。锁1.表锁locktabletable_nameread/writemyisam执行select自动加读锁,执行update/delete/insert自动加写锁。对表加读锁,不会阻塞其他线程的读操作,会阻塞对表的写操作。写锁,读写操作都阻塞2.行锁的类型Gaplock-间隙锁:锁定区间范围,防止幻读,左开右开,只在可重复读隔离级别下生效——|——防止多个事务向同一个范围内插入记录,而这会导致幻读问题。记录锁-recordLock:锁行记录、索引索引、索引失效、表的key锁lock-next-keyLock:记录锁+间隙锁左开右闭(解决幻读)锁方式select....forupdateholdwritelock,别人不能加读锁,也不能加写锁select....lockinsharemodeholdreadLocks,别人可以加读锁,写锁不能加共享锁-读锁-S锁独占锁-写锁-X锁意向锁:读意向锁+写意向锁是在需要的时候加的,并且不会立即释放,事务提交后才会释放,两阶段锁协议3,全局锁-fulldatabaselogicalbackup4,deadlock两个或多个事务相互占用同一个资源并请求加锁,导致相互等待,无限阻塞innodbrollback最小排他行级锁的事务设置锁等待超时时间乐观锁和悲观锁ks悲观锁使用数据库自??带的锁机制——多写什么是MVCC:多版本并发控制。提取原理总结:使用版本链+ReadView详细解释:版本链:同一行数据可能有多个版本的innodb数据表,每行数据记录都会有几个隐藏字段,row_id,事务ID,回滚指针。1.Innodb采用主键索引(聚集索引),会使用主键来维护索引。如果表没有主键,将使用第一个非空唯一索引。如果没有唯一索引,则隐藏字段row_id作为主键索引。2、一笔交易开始时,会向系统申请一个交易ID,交易ID会严格递增,并将最近一次操作它的交易ID3插入到行记录中。undolog会记录事务前的旧版本数据,然后行记录中的回滚指针会指向旧版本位置,从而形成版本链。所以undolog可以用来实现回滚保证原子性,也可以用来实现MVCC版本链。图3版本链形成一个读取视图。在ReadCommitted隔离级别下,将为每个查询生成一个ReadView。RereadableRead只在事务开始时生成一个ReadView,这个ReadView会被后续的每次查询使用。实现不同的隔离扇区。阅读视图中包含什么?(一致性视图)一个数组+up_limit_id(低水位)+low_limit_id(高水位)(这里的up和low没有错,是这样定义的)1.数组包含当前Active事务ID(未提交事务),低水位为活跃交易的最小ID,高水位为下次分配的交易ID,即当前最大交易ID+1。数据可见性规则是如何实施的?数据版本的可见性规则是根据数据的行trx_id和一致性视图(ReadView)进行比较得到的。view数组把所有的trx_ids分成了几种不同的情况。图4数据版本可见性规则读取原理:某事务T要访问数据A,先获取数据A中的事务id(获取最近对其进行操作的事务的事务ID),对比一开始生成的readview事务T的:1.如果在readview的左边(小于readview),说明这个事务可以访问这个数据(在左边说明事务已经提交)2.如果在leftsideofreadview右边(比readview大),表示这个版本是以后启动的一个事务生成的,肯定是看不见的;3、如果当前事务在未提交事务集合中:如果trx_id这一行在数组中,说明这个版本是由一个还没有提交的事务生成的,不可见;b.如果trx_id这一行不在数组中,说明这个版本是由一个已经提交的事务生成的,并且是可见的。不可访问,获取roll_pointer,通过版本链获取上一个版本。根据数据历史版本的事务ID,再次与视图数组进行比较。本次执行后,虽然期间修改了这行数据,但是无论事务A什么时候查询,看到这行数据的结果都是一致的,所以我们称之为一致读。总之,MYSQL的隔离性是靠MVCC+锁来保证的。我总结了各个隔离级别的实现原理:隔离级别原理及问题解决分析:readuncommitted:原理:直接读数据不能解决任何并发问题readcommitted:读操作不加锁,独占锁为写操作添加,解决脏读。原理:使用MVCC实现,每条语句执行前会生成一个ReadView(一致性视图)Repeatablereading:MVCC实现,事务开始时只会创建ReadView,然后事务中其他查询使用这个阅读视图。解决脏读,不可重复读,快照读(普通查询,读取历史数据)使用MVCC解决幻读,当前读(读取最新提交的数据)通过间隙锁解决幻读(lockinsharemode,forupdate,update,detete,insert),间隙锁只有在可重复读的情况下才生效。(默认隔离级别)Serializable:原理:使用锁,读加共享锁,写加独占锁,串行执行总结:readcommitted和repeatableread的原理是MVCCReadView的生成时机不同。可重复读只是在事务开始时生成一个ReadView,后面会用到;ReadCommitted会在每次执行前生成一个ReadView。05Consistency一致性是交易追求的最终目标。而隔离,其实都是为了保证数据库状态的一致性。当然,以上都是数据库层面的保证,一致性的实现还需要应用层面的保证。也就是说,你的事。例如购买操作只扣用户余额,不减少库存。保证状态的一致性肯定是不可能的。如果你视周围的人为魔鬼,你将生活在地狱;如果您将周围的人视为天使,您将生活在天堂。本文转载自微信公众号“小龙编码”,可通过以下二维码关注。转载本文请联系小龙编码公众号。

猜你喜欢