数据库面试题——ACID靠什么来保证

数据库事务的四大特性:原子性、一致性、隔离性、持久性。

要回答标题的问题得先了解清楚MySQL的日志体系

MySQL日志体系

MySQL在InnoDB存储引擎级别有两种日志:undo log日志和redo log日志,那在MySQL Server级别又有一个binlog日志

undo Log

Undo日志顾名思义是用来做回滚的,其实Undo的作用不止于此,MVCC这种重要的概念也是基于Undo实现的。

为了阐述MVCC,这里需要引入一个比较重要的概念——read view。我们知道,一个事务读取到的数据实际上是一个快照,这是MVCC的基本功能,只有这样,才能保证并发能力,即一个事务拿到记录的X锁之后,并不会阻塞其他事务读取数据,即便X锁和其他的锁是互斥的。

每一次的数据更新,都会生成一个read view,比如下面的图:
在这里插入图片描述

undo里面有按照顺序排列的read view,一个事务开始的时候,系统就会分给它一个read view,这样也就实现了MVCC。

那么谁去读当前值呢?看起来似乎每个读请求都是平行宇宙一样互不干扰,这就又引出了两个新的名词:

  • 当前读
  • 快照读

快照读读取到的就是read view,一般都会写的select一定是快照读了,但是如果你加上了for update,那么就一定是当前读了。

根据undo的生成机制,每次修改数据都会生成一个read view,那么在一个很多写操作的系统上,或者进行过大量批量修改数据的系统上,undo表空间就会变得非常大。在MySQL5.5版本以前,undo是放在共享表空间里的,而且这个共享表空间文件很有意思,一旦变大了就再也不会变小,虽然undo里的日志会被系统自动择机删除,但是其申请的空间就再也不会回缩了。

这是一个不好的设计,因此在后来的版本中,undo已经可以独立表空间文件了,而且这个文件也是可以回收空间的。

redo Log

熟悉MySQL InnoDB引擎的人都知道,InnoDB有一个最重要的概念就是缓冲池,这是在内存中分配的一个区域,InnoDB会将数据首先缓存在此,请求首先去命中缓冲池,无法命中缓冲池的才会在磁盘上进行检索,被检索到的数据还是会缓存在缓冲池中。

但是缓冲池依赖的内存是一种易失性的存储介质,掉电以后所有的数据都会被抹掉,为了数据的持久性,任何在缓冲池中做出的变更操作,都要持久化到磁盘上,只有这样,数据库才能实现持久性,用户也才能放心的将数据放在数据库上。

现在来看看这样一条SQL语句是如何执行的:

update table set col1 = 1 where col2 = 2;

用一个比较直观的流程图表示其过程如下:
在这里插入图片描述
我这里选择性的忽略了一步,就是有一个线程,采用异步的方式,慢慢的将脏数据页从内存中刷入磁盘的数据文件中。这种commit之后首先写redo,然后异步写数据文件的方式,叫做WAL,即预写日志方式。

有了WAL方式,我们的数据只要commit成功就绝对不会丢失了。InnoDB的REDO LOG有两个可以控制的参数,规定了日志文件最大的大小和一共有多少日志文件。

但是不管有多少个文件,都可以看做是一个文件,redo文件是循环利用的,即文件写满了,就会回收空间。在很多资料上,Redo Log文件都会被画成一个环,实际上也确实如此。

我们知道,Redo的存在保证了持久性,所谓持久性,一般都可以理解为只要提交的事务就一定会持久化。那么Redo是如何保证持久化的呢?设想这样一种情况,在某一时刻,数据库崩溃了,mysqld进程异常退出,此时DBA将数据库重启,会发生什么事情?

这里就要引入一个叫做LSN的概念,即Log Sequence Number,可以理解唯一个序列号或者坐标。标记了日志或者数据文件中最新的位置。一般来说,磁盘文件中最新的LSN都会小于Redo中最新的LSN,那么这两个LSN之间的数据块,就是没有刷入磁盘数据文件的数据块了。

在崩溃重启后,数据库只需要将redo中这部分没有刷入磁盘的数据块刷到数据文件中就可以了,这样崩溃恢复的速度就会大大提高。
在这里插入图片描述
上图是redo文件的逻辑示意图,这里又引入了一个概念叫checkpoint,实际上是一个动作,即每次读取最老的脏页,确保这个脏页对应的LSN之前的LSN都已经写入了数据文件,这个脏页的LSN作为checkpoint点记录到日志文件中。write pos指写入的位置,也是一个LSN值,这两个LSN值之间的部分是可写部分,如果一旦write pos要赶上checkpoint了,就再做一次checkpoint。

至此我们已经讨论了redo log的一般套路,但是实际使用MySQL过程中还会遇到两个参数:

  • innodb_flush_method
  • innodb_flush_log_at_trx_commit

这两个参数是控制事务提交时的刷磁盘策略的,通常第一个参数在Linux系统上我们都会设置成O_DIRECT,至于这个O_DIRECT的实现方式,可以参考任何一本Linux内核编程的书,这里只需要知道这个O_DIRECT代表了不走OS cache,直接将缓存中的数据块刷到磁盘中。

下图说明了实际情况:
在这里插入图片描述
而第二个参数,则是控制了commit时redo刷盘的时机:

  • 1:每次commit,都会将redo buffer中的数据块写入redo并立刻刷磁盘;
  • 2:每隔一秒,将redo buffer中的数据块写入redo并刷盘;
  • 3:每次commit都会将redo buffer中的数据块写入redo log,但是每隔1s才会刷盘

binlog

Redo其实是InnoDB特有的一种日志,这也是和Oracle学来的技术,因此学完了InnoDB以后再去学Oracle会感觉很轻松,因为基本原理都是一样的。

Binlog又是一种重要的日志,只不过这种日志是MySQL提供的,什么引擎都可以用。Binlog记录了数据的实际变更(当然如果binlog格式是mixed或者statement,那么大部分情况下记录的是SQL语句),当然有了这种日志,就能够实现复制功能了。而事实上大部分人用binlog都会去做复制,以及备份,当然也有很多人基于binlog开发了类似Oracle的闪回工具。

既然有了binlog,那么什么时候写binlog又是一个值得探究的事情,这就可以引出一个参数,sync_binlog,这个参数控制了binlog和事务提交的关系,取值范围是0-N:

  • 0:不去强制要求,由系统自行判断何时写入;
  • 1:每次commit的时候都要写binlog;
  • N:每N个事务,才会去写binlog

如此看来,最安全的选择还是1,加上之前的innodb_flush_log_at_trx_commit参数,如果选择最安全的方式也是1,在很多材料中都会写MySQL配置为双1,就是指的这两个参数的配置。

既然有两种日志,就会有一个写入的策略问题,这个问题也就引出了另一个概念——两阶段提交。所谓两阶段提交,其实就是将redo的提交拆分成了prepare和commit两个阶段,注意这里的commit不是commit语句,是一种状态。

当事务发起commit的时候,根据上面的描述,首先会将脏数据块写入redo log,但是此时还没有写binlog,因此阶段处于prepare阶段,只有当binlog完成了写操作之后,才会将redo log标记为commit,这个事务才算是真的完结了。

这时,我们来思考一个问题,如果写binlog的时候crash了,怎么办?因为redo log还是处于prepare状态,实际上事务没有真的commit,因此是会回滚的。

Binlog有一个比较好玩的参数:binlog_format,这个参数有三个选项,分别是ROW,STATEMENT和MIXED。

最开始学习MySQL的时候看到这个MIXED便被他的名字迷惑了,这个参数一定是智能的,一定是最好的。但是事实证明我还是想错了,这个参数其实是为了兼容老旧的STATEMENT参数设计的,大部分情况下,binlog里记录都是STATEMENT格式。这里就要说一下STATEMENT格式记录了什么了。

如果是我来设计怎么将主库的事件发送到从库,那么我在设计时一定会首先想到将主库上执行过的所有的SQL都发给从库,这样就可以了。

这样的确可以,而且还很简单,但是确实有隐患。举一个简单的例子来说,如果主库上的SQL语句里有讲sysdate()插入表的语句,那么在从库上执行的时候,这个sysdate()获取到的实际上是从库的时间,这就存在主库和从库不一致的情况了,谁也不能保证语句立刻就能发送到从库并立刻执行,这一切操作保证在1s之内是很难的,存在网络问题,存在单线程复制回放问题的限制。

因此这种方式后来的MySQL开发者也觉得不好,所以设计了一种新的binlog格式,即ROW格式。这种格式记录了实际的数据变更,这样就解决了上面说的问题。不过,为了兼容旧版,MySQL的设计者设计了一个颇具迷惑性的MIXED选项,这个选项大部分情况下,都会把SQL语句记录在binlog里,而且这个选项也没有办法支持最新的GTID特性。

综上所述,一个系统要做复制,一定要使用ROW格式。

不过STATEMENT格式也不是没有其好处,至少binlog很好读,里面都是明文记录的SQL语句,要追查什么很方便,而ROW都是二进制加密的,可读性非常差,这里提供一个一般性的语句:

mysqlbinlog --base64-output=decode-rows -vv binlogXXXX

binlog是实现复制的重要日志,Master负责将事件记录在binlog中,并且通知slave将日志取走,Slave的IO线程将数据取走之后,将binlog中的事件保存在本地的中继日志中,由一个叫做sql线程的线程开始依次将中继日志中的事件回放在本地。

这个过程就是复制的基本原理,虽然现在有异步复制,半同步复制,增强型半同步复制,但是复制的基本流程和原理却始终没有变。

在此需要引入一个重要的概念——GTID,即全局事务ID。当然本文并不是论述GTID及其运维的,因此只是简单提要。

GTID的出现大大简化了基于binlog的复制配置和运维难度,配置复制的时候不再需要直接指定pos等信息,而是可以自动化的进行。

每一个事务之前都会有一个set GTID的语句,因此从库在回放的时候,首先会执行该语句,那么这个GTID就会被从库维护起来,表示这个GTID已经执行过了。

假如我需要将slave节点挂载在主备上,那么基于GTID的复制会简化我的操作,因为slave上记录了所有已经执行过的GTID,此时是不需要手动干预去指定pos之类的值的,slave自己就可以判断要从哪里开始继续复制。

其它日志

其他日志包括文本格式的error log,慢查日志和general log,还有中继日志。

其中中继日志顾名思义,就是将主库发送来的binlog先保存在本地,然后按循序进行回放。

文本格式的日志里,error log记录了MySQL运行过程中打印出来的日志,包括warning,error或者info,这些都是排查问题时的参考。

慢查日志记录了符合条件(慢查时间阈值,是否使用索引)的SQL,这是很重要的性能优化和排查依据。

general log一般用来调试的时候使用,记录了所有的数据库操作明细,开启以后会大量降低数据库系统性能,不建议在生产环境上开启。

ACID靠什么来保证

原子性

原子性是由undo log日志保证的,它记录了需要回滚的日志信息,也就是说我们的事务还没提交需要回滚,那么事务回滚就是根据undo log日志来撤销已经执行成功的SQL。

说白了,undo log其实就是SQL的反向执行,它记录了反向执行的SQL语句,把正向语句回滚回去。

一致性

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态;如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。

一致性是ACID的目的,也就是说,只需要保证原子性、隔离性、持久性,自然也就保证了数据的一致性。比如说,我们的ID在数据库中是唯一的,此时插入了一个唯一ID,数据库会给我们做一个检查,告诉咱们是否发生了主键冲突,如果主键冲突数据就无法插入。

另一部分是业务数据的一致性,这需要程序代码来保证。比如说转账这个场景,假设我要转账100元出去,实际上数据库中只有90元,那这时候就不应该转账成功,这种情况通过数据库是无法保证的,只能由程序来保证。

因此一致性是由其它三大特性来保证,而业务一致性由程序代码保证

隔离性

在MySQL中隔离性是通过MVCC多版本并发控制机制来保证的,它是在事务隔离级别中最最重要的一个概念,那它是怎么实现的呢?

多版本并发控制:读取数据时通过一种类似快照的方式将数据保存下来,这样读锁和写锁就不冲突了,不同事务的session会看到自己特定版本的数据,也就是版本链,通过版本链的概念来达到读和写能够并发进行。

MVCC只在READ COMMITTED(已提交读)和REPEATABLE READ(可重复读)两个隔离级别下工作,其他两个隔离级别和MVCC不兼容,这是因为READ UNCOMMITTED(读未提交)总是读取最新的数据行,而不是符合当前事务版本的数据行,而ZERIALIZABLE(串行化)则会对所有读取的行都加锁,MVCC就没有意义了。

在MySQL的InnoDB下,聚簇索引记录中有两个必要的隐藏列:

  • trx_id:它用来存储每次对某条聚簇索引记录进行修改时的事务ID,这个事务ID由MySQL分配。
  • roll_pointer:每次对哪条聚簇索引记录有修改的时候,都会把老版本写入undo日志中,这个roll_pointer就是存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个版本的记录信息(注意插入操作的undo日志没有这个属性,因为它没有老版本)。

理解了这些概念,咱们再来看看MVCC。已提交读和可重复读的区别就在于它们生成ReadView的策略不同。MVCC就是版本链+ReadView所组成的这么一种概念,当我们掌握了版本链和ReadView这两个概念,也就明白了MVCC,我们接着来看看这个ReadView。
在这里插入图片描述
我们在开启事务时创建ReadView,ReadView维护了当前活动的事务ID,即未提交的正在进行中的事务ID,排序生成一个数组访问数据,获取需要修改的记录中的事务ID(获取的是事务ID最大的记录),然后去对比ReadView:

  • 如果获取的事务ID在ReadView的左边(比ReadView都小),表示可以访问(在左边意味着该事务已经提交)。
  • 如果获取的事务ID在ReadView的右边(比ReadView都大),或者就在ReadView中,表示不可以访问,获取roll_pointer,取上一版本重新对比(在右边意味着,该事务在ReadView生成之后出现,在ReadView中意味着该事务还未提交)。

在这里插入图片描述
已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,也就是说我每次select查出来的ReadView都会重新生成,所以ReadView可能会不一样,就是说读到的数据就会不一样;

而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView,每次select查询都是一样的。

在这里咱们发现了这两者的性能是有差别的,MySQL为了提高查询性能,默认使用了可重复读这种隔离级别(原因之一)。

这就是MySQL的MVCC,通过版本链,实现多版本可并发读-写、写-读,通过ReadView生成策略的不同实现不同的隔离级别。

持久性

持久性意味着事务操作最终要持久化到数据库中,持久性是由 内存+redo log来保证的,InnoDB在修改数据的时候,同时在内存和redo log记录这次操作,宕机的时候可以从redo log中恢复数据。

同时,我们都知道MySQL Server的主从同步就是通过binlog来实现的,从服务器通过binlog文件的SQL拿过去执行一遍,保证跟主服务器的数据一致,而binlog和redo log都存储了表中的数据,都可以用来做数据恢复的,那怎么保证binlog和redo log的数据一致呢?

下面是InnoDB下redo log的过程:

  • 对redo log进行写盘,写完后事务进入prepare状态。
  • 如果前面prepare成功,马上就会进行binlog写盘,再继续将事务日志持久化到binlog。
  • 如果binlog持久化成功,那么事务则进入commit状态(在redo log里面写一条commit记录)。

这意味着一个事务到底有没有成功,由两方面来保证:第一是redo log里面有没有commit记录,如果有commit记录,那么binlog一定是持久化成功了,也就是说事务成功了。再者就是redo log最终还会进行刷盘,它的刷盘会在系统空闲时进行,并不是写到redo log时马上进行刷盘。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,下面是对大数据面试题——spark面试题(一)的回答: 1. 什么是Spark?它与Hadoop有什么区别? Spark是一个快速、通用、可扩展的大数据处理引擎,它可以在内存中进行数据处理,因此比Hadoop更快。与Hadoop相比,Spark的优点在于它可以在内存中进行数据处理,因此速度更快,而且它支持更多的数据处理方式,例如流处理、图形处理等。 2. Spark的核心组件有哪些? Spark的核心组件包括Spark Core、Spark SQL、Spark Streaming、MLlib和GraphX。 3. 什么是RDD?它有哪些特点? RDD是Spark中的一个基本概念,它代表一个不可变的分布式数据集合。RDD具有以下特点: - 可以在内存中进行计算,因此速度快; - 支持多种操作,例如map、reduce、filter等; - 可以进行持久化,以便在后续计算中重复使用。 4. Spark中的map和flatMap有什么区别? map和flatMap都是RDD中的转换操作,它们的区别在于: - map操作对每个元素进行转换,返回一个新的元素; - flatMap操作对每个元素进行转换,返回一个包含多个元素的序列。 5. 什么是Spark的shuffle操作? Spark的shuffle操作是指将数据重新分区的操作,它通常发生在reduce操作之前。Shuffle操作会将数据从多个节点上收集到一个节点上,然后重新分区,以便进行后续的计算。 6. Spark中的cache和persist有什么区别? cache和persist都是将RDD持久化到内存中,以便在后续计算中重复使用。它们的区别在于: - cache操作默认将数据持久化到内存中,而persist操作可以指定将数据持久化到内存、磁盘或者其他存储介质中; - cache操作是persist操作的一种简化形式,它默认将数据持久化到内存中,并且只能持久化到内存中。 7. Spark中的reduceByKey和groupByKey有什么区别? reduceByKey和groupByKey都是对键值对RDD进行操作的函数,它们的区别在于: - reduceByKey操作会在每个分区内先进行本地聚合,然后再进行全局聚合,因此效率更高; - groupByKey操作会将所有的键值对都进行网络传输,然后在一个节点上进行聚合,因此效率较低。 8. Spark中的broadcast变量有什么作用? broadcast变量是一种只读的变量,它可以在所有节点上共享,以便在计算过程中使用。使用broadcast变量可以避免在网络上传输大量的数据,从而提高计算效率。 9. 什么是Spark的checkpoint操作? Spark的checkpoint操作是将RDD持久化到磁盘上,以便在后续计算中重复使用。与cache和persist不同的是,checkpoint操作会将数据持久化到磁盘上,以便在内存不足时可以从磁盘上恢复数据。 10. Spark中的Task是什么? Task是Spark中的一个基本概念,它代表一个可以在一个节点上执行的计算任务。Spark将一个RDD分成多个分区,每个分区对应一个Task,这些Task可以并行执行,以提高计算效率。 ### 回答2: 今天我们来讨论一下关于Spark大数据面试的一些常见问题。Spark是一种基于Hadoop的开源计算系统,它能够快速处理大规模数据,并且支持多种编程语言,包括Java、Scala和Python等。以下是一些Spark面试题及其答案: 1. Spark有哪几种部署模式? Spark有三种部署模式,分别是本地模式、集群模式和分布式模式。本地模式指的是在本地运行Spark应用程序,不需要连接到外部计算机。集群模式指的是单个Spark集群环境,它由一组Spark节点组成,可以在数据中心或云中运行。分布式模式指的是使用多个Spark集群并行处理大规模数据。 2. Spark和Hadoop的区别是什么? Spark和Hadoop都是处理大规模数据的工具,但它们有一些区别。首先,Spark处理数据速度快,因为它将数据存储在内存中,而Hadoop则将数据存储在磁盘中。其次,Spark支持更多的编程语言,包括Java、Scala和Python等,而Hadoop只支持Java。此外,Spark具有更好的机器学习和图形处理功能,可以更好地支持大规模数据分析。 3. Spark的RDD是什么? RDD是Spark中重要的概念,全称为Resilient Distributed Dataset。它是一个不可变的分布式数据集合,可以分区存储在不同节点上,并且每个分区都可以在并行处理中进行处理。RDD支持两种操作,即转化操作和行动操作。转化操作将一个RDD转换为另一个RDD,而行动操作返回一个结果或将结果输出至外部系统。 4. Spark的优化技术有哪些? Spark优化技术包括数据本地化、共享变量、宽依赖和窄依赖、缓存和持久化,以及数据分区等技术。数据本地化将数据存储在尽可能接近计算节点的位置,以减少网络传输的开销。共享变量将常用的变量通过广播或累加器的方式在节点中共享,从而减少网络传输量。宽依赖和窄依赖指的是在转化操作中RDD之间的依赖关系,窄依赖表示每个父分区最多与一个子分区有关联,而宽依赖则表示多个子分区可能与多个父分区关联。缓存和持久化技术可将RDD保存在内存中,从而加速访问速度。数据分区可以将数据划分为较小的块进行并行处理。 5. Spark Streaming是什么? Spark Streaming是Spark的一个扩展模块,它支持实时数据流处理。Spark Streaming可以将实时数据流以微批次方式处理,每个批次的数据处理平均耗时只有几秒钟。Spark Streaming可以将数据存储在内存或磁盘中,同时支持多种数据源和数据输出方式。 以上是关于Spark大数据面试题的一些回答,希望能够对大家有所帮助。如果你想深入学习Spark和大数据处理技术,可以考虑参加相关的培训课程或在线课程。 ### 回答3: Spark是一个分布式计算框架,它可以使大规模数据处理更加高效和便捷。因此,在企业招聘大数据领域的人才时,对Spark的技术能力要求越来越高。以下是Spark面试题的回答: 1. Spark有哪些组件? Spark框架由三个核心组件组成:Spark Core、Spark SQL和Spark Streaming。此外,还有Spark MLlib、Spark GraphX、Spark R等个别不同的子组件。 2. 什么是RDD?与Dataframe有什么区别? RDD(弹性分布式数据集)是Spark的核心数据抽象,是不可变的分布式对象集合。RDD可以从文件中读取数据、从内存中读取数据、并行修改数据等。而Dataframe和RDD类似,但是Dataframe更加强大,因为它是带有结构化的RDD。Dataframe在处理大规模结构化数据时非常有效和便捷。 3. Spark如何处理缺失数据? Spark提供了两种处理缺失数据的方法:第一种是使用DataFrame API中的na函数,可以删除或替换缺失值;第二种是使用MLlib中的Imputer类,可以将缺失值替换为均值或中位数。 4. 什么是Spark的任务(task)? 一个任务是Spark作业中的最小执行单位。Spark集群上的作业被划分为多个任务,这些任务可以并行执行。 5. Spark的shuffle操作是什么?它为什么是昂贵的? Spark的shuffle操作是将一组数据重新分配到不同计算节点上的操作。Shuffle操作可能会导致大量数据的磁盘写入、网络传输和数据重组,这些都是非常昂贵的操作。因此,它在Spark集群中是一个相当昂贵的操作。 6. Spark中的Partition有什么作用? Partition是Spark中的数据划分单位。它可以将数据分成多个块并对每个块进行处理。Partition 可以提高 Spark 的并行度和运行效率,因为它可以将大规模数据分成多个小块,并在集群的多个计算节点上并行处理数据。 总而言之,Spark是大数据领域中使用最广泛的计算引擎之一,其技术理念和应用场景非常广泛。对于求职者而言,掌握 Spark 的基本概念和技术特点,提高对 Spark 的理解和应用能力,将有助于更好地处理和分析大规模数据集。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值