文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

面试题:说说看你对数据库事务和 ACID 的理解?并发事务可能会产生哪些问题,该如何解决?什么是快照读和 MVCC,解决了什么问题?

2024-11-28 15:28

关注

面试官:什么是数据库的事务,说说你对事务特性的理解?

数据库事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。这些操作要么全部执行成功,要么全部不执行,是一个不可分割的工作单位。

对于事务的特性,可以从以下几个方面来理解:

一、原子性(Atomicity)

原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部完成,要么全部不完成。在数据库操作中,如果事务中的某个操作失败,则整个事务会回滚到事务开始前的状态。这种特性通过数据库的Undo机制来实现,即在事务执行过程中,如果出现错误或用户执行ROLLBACK语句,系统可以回滚到事务开始前的状态。

二、隔离性(Isolation)

隔离性是指并发执行的事务之间相互隔离,不允许一个事务的执行结果影响其他事务的执行。这种特性避免了多个事务并发执行时可能出现的数据不一致问题。数据库系统通常通过锁和其他并发控制技术(如MVCC)来实现隔离性。表现形式是,当一个事务正在对某个数据进行操作时,其他事务不能对该数据进行并发修改,以防止数据不一致的问题发生。

三、持久性(Durability)

持久性是指一旦事务提交,它对数据库中数据的改变就是永久性的,即使在系统崩溃后,事务的修改结果也不会丢失。这种特性通过数据库的Redo机制来实现。当事务提交后,系统将把事务的所有操作写入到日志文件中,以便在系统恢复后通过Redo日志重新执行这些操作,保证数据的一致性。

四、一致性(Consistency)

一致性是指事务必须将数据库从一种一致状态转换到另一种一致状态。这么说有点抽象,我个人的具体理解是:一致性体现在两点。

同一个表的在本次事务中有联系的多条记录的状态要对的上,比如转账前后两个账户的金额总和应该不变(两条同一张表的update语句)。

不同表在本次事务中有联系的多条记录的状态要对的上,比如消费后增加用户积分并减少用户金额,那么用户的金额减少后,不能因为故障导致用户积分没增加(两条不同表的update语句)。

上述四个特点中,一致性是事务的最终目的。只要其他三个特性都满足了,那么一致性自然而然也就会满足,也就是说原子性,隔离性和持久性是需要作出的努力,一致性是我们想要的结果。

面试官:原子性——说说看Mysql是如何通过undo日志实现原子性?

首先是MySQL如何通过undo日志实现原子性的详细解释:

一、undo日志的作用

undo日志,也被称为回滚日志,是MySQL中用于记录事务在执行过程中对数据的修改前的状态(即旧值)的一种日志。当事务需要回滚时,MySQL可以利用undo日志将数据恢复到事务开始前的状态,从而保证事务的原子性。

undo日志分为3类:

下面是undo日志的具体结构,其他的不用关注,重点关注图中倒数第二格的 ,里面包含增删改操作前的具体字段和值,数据库回滚就是通过这些字段和值来进行的。

二、实现原子性的过程

(1) 事务开始:

(2) 记录undo日志:

(3) 事务提交或回滚:

三、undo日志的具体实现

1.undo日志的存储:

undo日志被存储在InnoDB存储引擎的专用页面中,这些页面被称为undo页面。

undo页分为两种:insert类型的undo日志(里面只放insert类型的undo日志) 和 update类型的undo日志(放update和delete类型的undo日志)。

undo页面以链表的形式组织,每个undo页面都包含了多条undo日志,Innodb会为每一个事务一条或两条undo链表(如果该事务同时包含增删改操作就会生成两条undo链表)。

之所以要将同一个事务产生的undo日志组织在同一个链表而非所有事务的undo日志组织成一个链表也是为了回滚时可以按事务的维度找到只和本事务相关的undo日志进行回滚。

两种不同类型的undo日志页分别用 insert undo 链表 和 update undo 链表管理。

把 undo 日志分成 2 个大类是因为insert类型的 undo 日志在事务提交后可以直接删除,而其他类型的 undo 日志还需要为 MVCC(多版本并发控制)服务,不能在事务提交后马上删除。

2.回滚操作:

当事务需要回滚时,MySQL会沿着undo日志链表,按照与事务执行相反的顺序,逐条应用undo日志中的信息,将数据恢复到事务开始前的状态。

面试官:持久性——Mysql的InnoDB是如何实现数据持久化的?

说到数据库持久化就绕不开 WAL 机制 和 redo日志。

一、WAL和redo log的基本概念

WAL是一种数据安全写入机制,其核心思想是在事务进行修改之前,先将修改操作记录到日志中,然后再将修改应用到数据库中。这样做的好处是,即使系统崩溃或断电,也可以通过日志来恢复数据,保证数据的持久性和一致性。

redo日志是InnoDB存储引擎独有的物理日志,记录了数据操作的细节,包括事务开始和结束的标志、修改的数据页和对应的操作等。它主要用于故障恢复,当数据库发生异常关闭或崩溃时,InnoDB可以通过redo日志来恢复数据。

redo日志以固定大小的多个文件(如ib_logfile0、ib_logfile1)形成的文件组的形式存在,是一个可覆盖的循环日志。InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。

write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 标记了日志中已经刷盘成功的数据所对应的redo日志数据。

write pos 和 checkpoint 之间的是redo日志上可被新的事务的增删改操作所覆盖的部分,可以用来记录新的操作。如果 write pos 追上 checkpoint,表示redo日志满了,这时候不能再执行新的更新,得停下来将Buffer Pool中的脏页刷盘,把 checkpoint 推进一下才能继续写redo日志。

有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。

下图是一条简单redo日志的数据结构:

其中 space ID 表示本修改所对应的数据页所在的表空间,page number是所修改的页号,offer是所修改的数据在页中的偏移量。通过这3个信息,就可以在故障恢复时找到要恢复的数据页和数据页中的具体位置。

二、redo log写入的具体过程

(1) 事务开始:

当一个事务开始时,MySQL会为该事务分配一个唯一的事务ID,并将该事务的相关信息存储在内存中的事务控制块(Transaction Control Block, TCB)中。

(2) 修改操作记录到redo log buffer:

在事务执行过程中,所有的修改操作(如插入、更新、删除等)都会被写入redo log缓冲区(redo log buffer)。redo log buffer是一个内存缓冲区,用于暂存待写入redo log的修改操作。

(3) 事务提交和redo log刷盘:

当事务提交时,MySQL会将该事务的所有修改操作按照顺序写入redo log文件中。这个过程称为redo log的刷新(flush)。

需要注意的是,在事务提交之前,MySQL并不会立即将redo log buffer中的修改操作持久化到磁盘上的redo log文件中。而是会等待一个合适的时机来进行持久化操作。基于innodb_flush_log_at_trx_commit配置参数的刷盘策略如下。

innodb_flush_log_at_trx_commit:

三、恢复数据的详细过程

(1) 启动InnoDB:当MySQL服务重启时,InnoDB存储引擎会开始启动。

(2) 定位checkpoint:InnoDB会通过redo日志找到最近一次checkpoint的位置。checkpoint信息保存在日志文件的开始部分,包括checkpoint号、checkpoint lsn(记录了产生该checkpoint时flush的LSN,确保在该LSN前面的数据页都已经落盘,不再需要通过redo log进行恢复)和checkpoint offset(记录了该checkpoint产生时,redo log在ib_logfile中的偏移量)。

(3) 获取并解析redo日志:

从checkpoint相对应的位置开始,InnoDB会获取需要重做的日志。

接着,InnoDB会解析获取的日志,并将其保存到一个哈希表中。哈希表以(space,offset)为键,存储了redo日志的信息。

(4) 数据恢复:

InnoDB会遍历哈希表中的redo日志信息。

对于每条redo日志,InnoDB会根据(space ID,page number,offset)读取指定页面,并进行日志覆盖,即根据redo日志中的记录来恢复数据页的内容。

面试官:数据库并发事务可能会出现什么问题,以及该如何解决?

一、数据一致性问题

1.脏读

一个事务读取了另一个事务未提交的数据,这些数据可能会被回滚,从而导致读取到无效数据。

以下是一个具体的脏读示例:

事务A首先执行了一个select操作,从account表中读取了id=1的账户的money值,此时得到的mnotallow=0。

接着,事务A尝试执行一个update操作,将id=1的账户的money值设置为2000。另一个事务(事务B)修改了该账户的money值,并且这个修改还未提交。

如果事务A最终不提交其修改,那么事务B读取到的mnotallow=2000就是一个“脏读”,即读取到了其他事务还未提交的数据。

事务B基于这个“脏读”的数据进行业务处理可能会导致问题,例如修改了其他表里的数据,最终数据不一致。

为了避免脏读,数据库系统通常实施更高的事务隔离级别,如READ COMMITTED或更高,以确保事务只能读取到已提交的数据。

2.不可重复读

一个事务在两次读取同一数据时,因其他事务的提交导致本事务数据发生了变化,从而两次读取无法获得一致的结果。不可重复读会在事务需要基于多次读取结果进行复杂计算时产生影响。

以下是一个不可重复读示例:

为了避免不可重复读,数据库系统需要实施适当的事务隔离级别(如READ COMMITTED或更高)或使用其他并发控制机制来确保事务在读取数据时不会受到其他事务修改数据的影响。

在READ COMMITTED隔离级别下,事务只能读取到其他事务已经提交的修改,从而避免了脏读和不可重复读(但幻读仍然可能发生)。而在更高的隔离级别(如REPEATABLE READ或SERIALIZABLE)下,数据库系统会进一步限制并发操作,以减少或消除幻读现象,但会严重影响并发性能。

3.幻读

一个事务(通过条件)读取多条记录后,因其他事务的插入或删除,导致再次读取时获得的记录集发生变化。幻读问题通常发生在插入或删除操作频繁的场景中。

以下是一个幻读的具体示例:

再举一个关于幻读的例子加强一下大家对幻读的理解:

假设有一个银行系统,它有一个账户表(accounts),用于记录客户的账户余额和其他相关信息。现在,有两个事务T1和T2同时运行,并且它们都对满足某个条件的账户集合进行操作。

(1) 事务T1:

(2) 事务T2:

(3) 事务T1(继续):

(4) 结果:事务T1在处理账户集合时,由于幻读现象,它必须处理一个额外的账户D,这可能导致一些意外的行为或错误。例如,如果事务T1的目标是向所有余额大于500元的老账户发送一条通知,那么由于幻读现象,新账户D也会收到这条通知。

为了避免幻读,数据库系统需要实施更高的事务隔离级别(如SERIALIZABLE)或使用其他并发控制机制(如锁机制或MVCC)来确保事务在读取数据时不会受到其他事务插入或删除数据的影响。

可能得面试官追问:不可重复度和幻读看上去形式上都是两次读取的结果不同,那么不可重复读和幻读的区别是什么?

(1) 发生场景不同:

(2) 关注点不同:

并发事务除了出现数据一致性问题之外,还可能存在其他问题(如死锁和性能下降等)。

二、死锁

死锁是指两个或多个事务在执行过程中,因为相互持有对方所需要的资源而陷入无限等待的状态。死锁会导致系统资源无法有效利用,严重时可能会使系统陷入瘫痪。常见的死锁场景包括两个事务互相等待对方释放锁,以及多个事务循环等待。

三、性能下降

并发事务增多会增加系统的CPU、内存和I/O负载,影响整体性能。具体表现为:

说到事务并发就不得不先说数据库的隔离级别。事务的隔离级别是数据库中用于控制并发事务间相互影响的机制。

以下是关于事务隔离级别的详细解释以及选择建议:

事务隔离级别的类型

(1) 读未提交(READ UNCOMMITTED):

(2) 读提交(READ COMMITTED):

(3) 可重复读(REPEATABLE READ):

(4) 序列化(SERIALIZABLE):

面试官:知道什么是快照读吗,它是用来解决什么问题的?

(下面的内容可能有点长,希望大家能耐心看完,毕竟面试加分的本质就是答出面试官所问的这个问题相关的但没有问出来的点)。

快照读(Snapshot Read)是数据库事务处理中的一种读取数据的方式,它确保事务在读取数据时看到的是数据在某个时间点(即事务开始时)的状态,就像拍摄了一张数据在那个时间点的“快照”一样。这种读取方式不会受到其他并发事务的影响,即使其他事务在读取过程中对数据进行了修改,快照读仍然能够读取到事务开始时的数据版本。

快照读的主要特点是:

在MySQL等数据库系统中,快照读通常是通过多版本并发控制(MVCC,Multi-Version Concurrency Control)来实现的。

MVCC是数据库管理系统中用于实现并发控制的一种方法。通过维护数据的多个版本来实现并发控制。

在MVCC中,每个数据项都有多个版本,每个版本都记录了一个时间点或事务ID,表示该版本被创建或修改的时间。当一个事务读取数据时,它会读取一个特定时间点的快照,这个快照包含了在该时间点之前提交的所有事务的结果。通过这种方式,事务可以看到一个一致的视图,而不受其他事务的影响。

具体来说,MVCC的实现通常依赖于以下几个关键组件:

一、Undo Log

定义:Undo Log是数据库中用于记录数据修改历史的日志。当事务进行更新或删除操作时,数据库会生成相应的Undo Log,以便在需要时能够回滚到之前的版本。

在MVCC中,Undo Log不仅用于事务回滚,还用于维护版本链,一条undo日志会作为版本链中的一个节点,节点之间通过undo日志的回滚指针连接。

当事务进行更新或删除操作时,数据库会生成一条undo日志作为新的数据版本,并通过undo日志的回滚指针将新旧版本(旧的undo日志)连接起来,形成版本链。

二、版本链

定义:版本链是指每个数据项都维护一个记录其修改历史的链表。链表中的每个节点代表数据项的一个版本,这里的“一个版本”就是一条undo日志,包括历史记录的数据内容、修改时间戳或事务ID等信息。

实现:在数据库中,每个数据行都会有一个隐藏的回滚指针(roll_pointer)字段,该字段指向该行的上一个版本(如果存在的话)。这样,通过回滚指针,可以将各个版本连接起来,形成一个版本链。

下图呈现了两个事务对数据表修改过程中生成版本链的过程。

假设一开始hero.name = "刘备"。那么版本链如下:

三、读视图(Read View)

定义:读视图是数据库在特定时刻为某个事务创建的一个快照,该快照包含了在该时刻所有未提交事务的信息。

实现:读视图通常包含以下关键信息:

作用:读视图用于判断哪些数据版本对当前事务是可见的。具体来说,当一个事务读取数据时,它会根据读视图中的信息,在版本链中查找满足条件的版本。

四、MVCC的读操作实现

当事务进行读操作时,数据库会根据读视图和版本链来判断应该读取哪个版本的数据。具体来说,数据库会按照以下步骤进行读操作:

五、MVCC解决的问题

MVCC主要解决了数据库在高并发环境下的读写冲突问题,以及数据一致性问题。具体来说,它解决了以下几个方面的问题:

(面试官可能得追问:MVCC的缺点)

维护MVCC并不是没有成本的,下面是MVCC所带来的问题:

来源:程序员阿沛内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯