ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

彻底搞懂数据库事务:ACID、隔离级别与分布式事务

彻底搞懂数据库事务:ACID、隔离级别与分布式事务 刚整理完一批软考模拟题发现关于事务的题目错误率特别高。很多考生能把“ACID”四个字母背得滚瓜烂熟但题目稍微变一变比如问“隔离级别怎么影响并发”“binlog和redo log有什么区别”就卡住了。说实话这不能全怪考生。市面上的软考教材讲事务基本就是“事务是逻辑操作单元ACID是四个特性”然后直接上定义看完合上书还是不会做题。但软考中级软件设计师、系统架构师考试以及面试中经常问的分布式事务、事务注解、MySQL事务题目考的就是你能不能把这个概念落到工程实践里。这篇就用实际项目中的场景把事务和ACID掰开揉碎讲清楚。不仅讲概念是什么还讲数据库底层是怎么实现这些特性的以及软考里围绕这些知识点的高频考法。1. 先从转账这个经典场景说起1.1 没有事务的世界有多混乱假设你在写一个银行转账功能从A账户扣1000元给B账户加1000元。最简单粗暴的写法是两条update语句UPDATE account SET balance balance - 1000 WHERE id A; UPDATE account SET balance balance 1000 WHERE id B;这两条语句正常情况下没问题。但假如第一条执行成功后数据库突然断电、服务进程崩溃、或者网络超时第二条没执行。结果就是A的钱扣了B的钱没到账。这个错误在金融系统里是不可接受的——钱的总额凭空少了1000块。这不是一个虚构的极端场景。我在实际开发中就遇到过一个订单系统在创建订单时先生成订单记录再扣减库存这两个操作中间有一个操作抛了异常导致订单记录存在但库存没扣最后盘点时发现数据对不上。最后排查下来就是因为当时的代码把这两个操作放在了两个独立的事务里。没有事务机制多个操作之间的原子性就无从保证。你只能在每次操作后做各种补偿检查但补偿逻辑本身就是新的bug来源。所以数据库引入了事务机制——把多个操作打包成一个不可分割的逻辑单元。1.2 事务的官方定义与软考视角事务Transaction是数据库管理系统执行过程中的一个逻辑工作单元它具有原子性Atomicity、一致性Consistency、隔离性Isolation、持久性Durability四个特性即ACID。这句话是软考教材里的标准表述很多考生死记硬背了但考试真考察时往往围绕的都不是“复制定义”而是四个特性的底层实现机制以及隔离级别对并发的影响这些衍生考点。举个例子软考中级软件设计师真题中常这样出事务的原子性由DBMS的哪个部分保证A日志文件 B锁机制 C恢复管理 D缓冲区管理很多人凭感觉选B锁机制但其实原子性靠的是日志准确说是undo日志。这道题的正确率历年都很低背后的原因就是考生只背了定义没有理解ACID各自对应数据库的哪部分功能模块。下面把四个特性逐一拆解每个都对应到具体的工程实现上。2. 原子性Atomicity要么全做要么全不做2.1 原子性的本质是“后悔药”原子性的意思是一个事务里的所有操作要么全部成功提交要么全部失败回滚不存在只执行了一部分的情况。注意这里的关键词是回滚——事务中已经执行了部分操作系统如何把这些操作撤销掉答案是undo日志回滚日志。MySQL的InnoDB存储引擎为每个事务维护一个undo log。事务中对记录做的每一次更新在修改数据页之前都会先把“修改前的数据快照”写入undo日志。如果后续事务需要回滚数据库就根据undo log里的记录把数据逐条恢复到修改前的状态。这个机制很像写文档时的“CtrlZ”。你每做一步操作编辑器都记住上一步的状态按一次撤销就回退一步。undo log就是数据库层的“撤销键”。2.2 工程实现中的细节undo log不是简单备份这里有个工程细节值得注意undo log记录的不是一条完整的旧记录而是反向操作。假设事务执行了一条UPDATE user SET age 30 WHERE id 1原来age是20。undo log里记录的是一条反向的更新指令“把id1的age改回20”。如果事务执行的是DELETEundo log就记录对应的INSERT反向信息。为什么这么设计因为如果记录的是完整旧数据快照回滚时要执行的是“覆盖写”当并发事务已经修改了同一条记录时覆盖写可能把其他事务的数据也覆盖掉。而记录反向操作回滚时只需要恢复自己改动的部分不会影响其他事务的成果。软考对这块的考察经常是给一个场景让你判断“如何实现回滚”。理解了undo log的反向记录机制这类题目就不难了。还有一个容易踩坑的点并不是只有显式执行ROLLBACK才会触发回滚。事务执行过程中报错、连接断开DBMS都会自动根据undo log回滚。甚至在你执行慢查询时数据库为了控制undo log膨胀发起的内部操作也可能间接触发某些会话的异常终止。这个原理在排查线上问题时特别有用。2.3 原子性相关的软考考点两类日志别搞混与原子性密切相关的是redo log。这两类日志总是成对出现但功能完全不同日志类型作用保证的ACID特性undo log记录修改前的状态用于事务回滚原子性、一致性redo log记录修改后的状态用于崩溃恢复持久性软考里经常出现这样的题系统崩溃后数据库重启如何保证已提交事务的数据不丢失答案就是redo log重放。已提交的事务已经把redo log刷到磁盘重启后根据redo log把数据页恢复到最新的状态。理解了这两类日志的分工考试遇到“崩溃恢复”相关题目就能很清楚地判断出答案了。3. 一致性Consistency数据永远是对的3.1 一致性的两层含义如果说原子性是微观层面的“要么全做要么全不做”一致性就是宏观层面的“数据永远满足业务规则”。一致性在软考和工程实践中有两层含义必须分开理解第一层数据库约束层面的完整性。比如主键不能重复、外键必须存在、字段非空约束、唯一索引等。这些约束由数据库自动检查违反约束的操作会被直接拒绝。这一层的一致性由数据库自己保证。第二层业务规则层面的一致性。比如“转账后总金额不变”“订单金额必须大于零”“一个商品的库存不能为负数”。这些规则数据库看不懂——它只知道你在改数字不知道数字的含义。所以这一层的一致性需要应用代码配合事务机制来保证。理解这个区别很重要。很多软考题目说“事务保证一致性”有人就理解为只要用了事务业务规则就不会被破坏。实际上事务只能保证你提交前的数据状态是合法的——如果应用代码里逻辑就是错误的事务也救不了你。3.2 一致性是原子性、隔离性、持久性的共同结果这里要澄清一个常见的理解误区。严格来说ACID四个特性并不是并列的关系。一致性是目标原子性、隔离性、持久性是手段。也就是说数据库通过原子性保证事务内部分操作不会留下中间状态通过隔离性保证并发事务不会互相干扰产生脏数据通过持久性保证已提交的数据不会丢失——这三者共同作用最终让数据从一个一致状态流转到另一个一致状态。这个理解方式对做软考多选题特别有用。有些题目问“以下哪些措施有助于保证事务的一致性”这时候就要从三个方向去思考原子性角度是否可能部分成功、隔离性角度并发是否可能产生相互干扰、持久性角度提交后是否可能丢失。三个方向都考虑到了答案自然就全面了。3.3 工程中的一致性设计约束、触发器和应用校验在真实项目中保证一致性的手段是分层的数据库层设计表时就把主键、外键、唯一约束、CHECK约束加好从结构上阻止脏数据写入。应用层在Service层做业务校验比如下单时校验库存是否足够、转账时校验余额是否充足。校验逻辑必须在同一个事务内避免校验通过后又被其他事务修改。存储过程/触发器层一些强一致性的场景可以把关键业务逻辑封装成存储过程在数据库内部完成校验和更新避免应用层和数据库层的网络延迟窗口。工程中一个常见的坑是应用层先校验库存够不够然后执行扣库存操作但校验和扣库存这两个操作之间另一个并发请求抢先扣掉了最后的库存。这就是典型的“并发下的一致性破坏”。解决思路是使用行锁SELECT FOR UPDATE或乐观锁版本号校验把校验和更新做成原子的。4. 隔离性Isolation并发事务互不干扰4.1 为什么要隔离脏读、不可重复读、幻读隔离性是ACID里最复杂的一个特性也是软考考题最密集的地方。事务的隔离性指的是多个事务并发执行时一个事务的中间状态不应该被其他事务看到。如果完全不隔离会出现三类经典问题脏读Dirty Read事务A修改了一条数据但还没提交事务B就读到了这个修改后的值。之后事务A回滚事务B读到的就是一条“不存在”的数据。注意这里名称有很强的误导性——“脏”不是指数据本身有问题而是指数据是未提交的中间状态。软考经常给出场景让你判断属于哪类问题很多考生把“事务A修改后未提交事务B读到修改后的值”判断成不可重复读这就是概念没理清。只要读到的是未提交的数据就是脏读。不可重复读Non-Repeatable Read事务A先读一条数据事务B修改并提交了这条数据事务A再读一次发现两次读到的值不一样。强调的是同一记录内容发生变化。注意这里事务B是已提交的但事务A在同一事务内两次读取结果不同。与脏读的区别在于脏读读的是未提交的数据不可重复读读的是已提交的数据。幻读Phantom Read事务A按照某个条件查询一批记录比如SELECT * FROM product WHERE price 100查到5条。这时事务B插入了一条符合条件的记录并提交。事务A再次执行同样查询发现多了一条仿佛出现了幻觉。幻读与不可重复读的区别是不可重复读针对的是已有记录的值变化幻读针对的是满足条件的记录数量变化。4.2 四种隔离级别逐级解析为了平衡并发性能和隔离性SQL标准定义了四个隔离级别。软考中经常以表格或场景题的形式考察下面用MySQL InnoDB的实际表现来说读未提交Read Uncommitted一个事务可以读到其他事务未提交的数据。这是隔离性最差的级别脏读、不可重复读、幻读都可能发生。实践中几乎不使用因为它的并发性能提升有限但数据正确性大打折扣。MySQL的InnoDB默认不使用这个级别。读已提交Read Committed一个事务只能读到其他事务已提交的数据。这个级别消除了脏读但仍然存在不可重复读和幻读。Oracle的默认隔离级别就是这个。每次查询都会生成一个新的快照所以同一事务内两次查询结果可能不同。可重复读Repeatable Read一个事务内多次读取同一数据结果保持一致。这个级别消除了脏读和不可重复读但理论上仍然存在幻读。MySQL InnoDB的默认隔离级别是可重复读而且InnoDB通过间隙锁Gap Lock和MVCC机制在这个级别下连幻读也基本消除了——这也是MySQL和标准SQL的一个差异点软考中经常拿这个做文章。串行化Serializable事务逐个执行完全串行化。这是隔离性最高的级别但并发性能最差基本等于把多线程打回了单线程。实际项目中除了个别要求极度严格的场景很少使用。4.3 一张表记住四种隔离级别的区别备考软考时这个表必须刻在脑子里隔离级别脏读不可重复读幻读并发性能读未提交可能可能可能最高读已提交不可能可能可能较高可重复读不可能不可能可能InnoDB基本消除中串行化不可能不可能不可能最低软考中级软件设计师考试中最常考的组合是“读已提交”和“可重复读”的区别。记准一句话读已提交解决脏读可重复读解决不可重复读串行化解决幻读。而MySQL的InnoDB在可重复读级别下通过间隙锁基本解决了幻读问题这是加分项。4.4 工程实现MVCC和锁如何配合InnoDB实现隔离性的底层机制是MVCC多版本并发控制配合锁。MVCC的核心思路是数据的修改不是直接覆盖旧值而是生成一个新版本。每个事务读取数据时通过版本链找到自己“能看到的版本”。这样读操作不需要加锁写操作也不用阻塞读操作大大提升了并发性能。具体来说InnoDB的每行数据都有两个隐藏列trx_id最近一次修改该行的事务ID和roll_pointer指向undo log版本链的指针。事务读取数据时根据事务隔离级别决定可见版本。读已提交级别下每次查询都重新生成一个ReadView快照所以能读到其他事务新提交的修改——这就造成了不可重复读。可重复读级别下事务第一次执行查询时生成ReadView之后一直复用这个快照所以事务内多次读取看不到其他事务的修改——这就保证了可重复读。加锁方面InnoDB支持行级锁和间隙锁。行级锁锁住一条具体记录间隙锁锁住一个范围间隙防止其他事务在范围内插入新数据。这就是为什么MySQL在可重复读下能消除幻读——间隙锁阻塞了符合条件的新记录插入。工程中一个常见问题是只要加锁就一定安全吗不一定。如果应用代码中使用了SELECT查询后在应用层判断结果再决定是否更新这个查询默认是不加锁的快照读非当前读并发下仍然可能出问题。必须显式使用SELECT ... FOR UPDATE锁定相关行才能保证判断和更新是原子的。4.5 隔离级别相关的软考真题套路这个考点在真实考试中几乎年年出现常见的出题套路包括给一个并发场景判断发生了什么问题——注意先判断读的是未提交数据还是已提交数据再判断是值变化还是数量变化。给定隔离级别判断能避免哪些问题——对照上表直接查就行。问InnoDB默认隔离级别——答案是可重复读而不是标准SQL定义的默认标准SQL没有硬性规定默认级别。问MySQL如何解决幻读——答案是MVCC快照读间隙锁当前读不要只答MVCC或只答间隙锁。在这里给一个特别容易出错的提示很多考生在判断隔离级别时习惯背“可重复读解决了不可重复读串行化解决了幻读”。但题目一旦换成MySQL InnoDB引擎这个结论就不完全对了。MySQL在可重复读级别已经基本消除了幻读——注意这个措辞是“基本”不是“完全”。在一些复杂场景下比如当前读配合特定操作顺序仍有理论上的幻读风险。题目如果问“InnoDB在可重复读下是否完全避免了幻读”答案是“否”因为它只能在快照读场景下避免在某些当前读场景下仍可能出现。5. 持久性Durability提交了就不能丢5.1 持久性靠的是redo log和双写机制持久性的定义很直观事务一旦提交对数据的修改就是永久的即使系统崩溃、断电数据也不会丢失。但要命的是InnoDB操作数据时并不是直接写磁盘而是先写内存中的Buffer Pool再由后台线程异步刷新到磁盘。如果数据还停留在内存里系统就崩溃了修改就会丢失。为了解决这个问题InnoDB引入了redo log重做日志。事务提交时把本次修改的数据页变化以追加方式写入redo log磁盘这个操作称为fsync。之后即使数据页还没来得及写盘重启后也可以通过redo log重放把数据页恢复到最新状态。这里有一个关键设计redo log的写入是顺序写而数据页的写入是随机写。顺序写性能远高于随机写所以redo log机制的引入把“每次提交必须刷数据页”变成了“每次提交只写日志”大大提升了事务提交的性能。工程上还有一个常被忽视的机制双写缓冲Doublewrite Buffer。redo log记录的是“数据页修改的物理操作”但如果数据页本身在写盘过程中出现半个页写成功、半个页写失败断电导致redo log也无法重放修复这种“页损坏”问题。双写缓冲先把完整的数据页复制到内存中的双写缓冲区然后一次写入系统表空间再写入数据文件。这个机制确保数据页写入的原子性。软考对持久性的考察相对简单基本就是问“如何保证已提交事务不丢失”——记住redo log这个核心答案再补充一句“通过WALWrite-Ahead Logging机制先写日志再写数据页”就够了。5.2 实际项目中的持久性配置刷盘策略怎么选MySQL的innodb_flush_log_at_trx_commit参数控制redo log的刷盘策略这个参数在软考大纲中虽然不是高频但面试中经常问值为1每次事务提交都执行fsync写盘最安全性能相对最差。值为2每次事务提交只写入操作系统缓存由系统调度刷盘性能好一些但主机断电时可能丢失最多1秒的已提交事务。值为0每秒刷一次盘性能最好但任何崩溃都可能丢失最多1秒的已提交事务。线上系统保证持久性必须设置为1。很多新人为了压接口性能把参数改成2结果主机一宕机丢了数据完全得不偿失。在软考案例题中如果题目描述“断电后丢失了最近几秒的已提交事务”排查方向就是这个参数配置问题。5.3 持久性与原子性的常见混淆这里必须强调一个在软考中反复出现的易混淆点。持久性强调的是崩溃恢复时不丢失已提交的事务数据而原子性强调的是事务执行过程中失败时回滚未提交的修改。两者一个向前恢复重放redo log一个向后撤销回放undo log方向完全相反。具体来说事务还没提交就崩溃重启后要回滚——这个操作靠undo log属于原子性。事务已经提交但数据页还没落盘就崩溃重启后要重放——这个操作靠redo log属于持久性。软考题目特别喜欢把这两种场景混在一起出题比如“系统崩溃后哪些操作根据undo log回滚哪些操作根据redo log重放”。只要记住了这个方向性这类题目就稳了。6. 隔离级别的工程选择与实战配置6.1 业务场景与隔离级别匹配什么时候用哪个隔离级别这是软考案例分析题经常涉及的问题也是工程中必须掌握的能力。读已提交适合读多写少的报表分析系统。报表查询通常跑很长时间如果期间有其他事务修改了数据读已提交级别下不同批次查询看到的数据可能不一样但对于报表这种不追求强一致性的场景这个代价可以接受。更重要的是读已提交下间隙锁不生效死锁概率低。可重复读适合对数据一致性要求较高的业务系统。电商订单、支付转账、库存管理这些场景事务内多次读取同一条数据必须结果一致。MySQL默认这个级别绝大多数互联网业务系统都跑在这个级别上。串行化适合并发度极低但一致性要求极高的场景。比如银行核心账务系统虽然有并发需求但宁愿慢不能错。这种场景可以接受串行化带来的吞吐量下降。6.2 通过SQL直接改变隔离级别实战中需要临时修改隔离级别可以直接执行SQL。拿MySQL举例修改当前会话的隔离级别只需一行命令-- 查看当前隔离级别 SELECT transaction_isolation; -- 设置当前会话隔离级别为读已提交 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 设置全局隔离级别为可重复读 SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;注意SESSION只影响当前连接GLOBAL影响之后新建的连接对已存在的其他连接不生效。线上环境修改隔离级别建议在维护窗口执行并提前评估对现有业务的影响。6.3 事务注解的实现原理软考热词里面出现了“事务注解”这是Java技术栈开发中最常用的事务控制方式。以Spring框架为例Transactional注解就是事务控制的门面。Service public class OrderService { Transactional(rollbackFor Exception.class) public void createOrder(OrderDTO orderDTO) { orderMapper.insert(orderDTO); stockMapper.reduceStock(orderDTO.getProductId()); } }原理解析Spring在启动时会扫描Transactional注解为标注的Bean生成动态代理对象。调用带事务注解的方法时实际走的是代理逻辑——进入方法前开启事务方法正常结束就提交方法抛出异常就回滚。rollbackFor Exception.class指定了回滚条件即遇到任何异常都回滚。这里有两个工程中必须注意的坑第一个坑Transactional只在代理对象方法调用时生效。如果同一个类中的方法A调用了方法B方法B上有Transactional但方法A没有Spring的代理不会拦截内部调用方法B的事务不会生效。这是Java开发中最常见的事务失效原因之一。第二个坑rollbackFor没有设置时默认只在遇到RuntimeException和Error时才回滚。如果方法抛出的是受检异常如IOException默认不会回滚。因此工程上面稳妥的做法是显式设置rollbackFor Exception.class。第三个坑方法不能是private或final的。Spring的代理无法覆盖私有方法也无法继承final方法所以事务注解在这些方法上不生效。遇到“方法明明加了事务注解却不回滚”的问题先检查这三个点。6.4 事务的传播行为高频面试考点事务的传播行为Propagation是Java事务面试题中的常客也是软考案例分析可能涉及的知识点。核心就是当一个事务方法调用另一个事务方法时两个事务应该是什么关系常用有这几个传播行为行为描述应用场景REQUIRED如果有事务则加入没有则新建默认绝大多数业务方法REQUIRES_NEW无论如何都新建一个事务日志记录、消息发送NESTED嵌套事务内部回滚不影响外部复杂业务流程中局部操作回滚NOT_SUPPORTED不支持事务有则挂起只读查询减少事务开销一个典型的场景是订单创建成功后要写一条操作日志。如果日志模块使用REQUIRES_NEW即使订单主事务回滚日志也能正常写入方便排查问题。如果日志模块与主事务共用同一个事务REQUIRED主事务回滚时日志也跟着被回滚问题定位就困难了。7. 分布式事务从单库到多系统的扩展7.1 为什么单机事务解决不了分布式问题微服务架构下同一个业务操作可能跨多个服务数据分散在不同的数据库甚至不同的物理机上。比如下单操作涉及订单服务写订单库、库存服务写库存库、支付服务调外部接口如果订单库写入成功、库存库写入失败整体数据就处于不一致状态。这就是典型的分布式事务问题。分布式事务不能直接套用单机事务机制。原因很简单单机事务依赖同一个数据库的undo log、redo log、锁机制协调多个操作分布式环境下不同数据库之间没有共享的日志系统无法通过简单的日志回滚实现全局原子性。7.2 强一致性方案两阶段提交2PC两阶段提交Two-Phase Commit是分布式事务最经典的强一致性方案软考系统架构师考试中属于必考内容。阶段一准备阶段Prepare。协调者Coordinator向所有参与者发送Prepare请求参与者执行事务操作但不提交把undo和redo日志写入磁盘然后向协调者回复“可以提交”或“准备失败”。阶段二提交阶段Commit。如果所有参与者都回复“可以提交”协调者发送Commit请求各参与者正式提交事务。如果任何一个参与者回复“准备失败”协调者发送Rollback请求所有参与者执行回滚。这个方案的优点是强一致性吞吐量方面有明确代价准备阶段的锁要一直持有到第二阶段结束跨节点通信延迟高而且协调者单点故障会导致整个事务阻塞。正因为这些缺陷业界更常用的是下面说的最终一致性方案。7.3 最终一致性方案TCC和消息事务相比强一致性互联网系统更常采用最终一致性方案。核心思路是允许系统在一段时间内处于中间状态但通过补偿机制保证最终达到一致状态。TCCTry-Confirm-Cancel方案把每个分布式操作拆成三个阶段Try阶段做业务检查预留资源。比如库存服务冻结100件商品不真正扣减。Confirm阶段确认执行把预留的资源真正扣减。Cancel阶段取消操作释放预留资源。如果某个分支的Confirm失败全局触发所有已成功分支的Cancel回补资源。TCC对业务侵入性很强需要为每个操作写三个方法开发成本高。但它的优势是无阻塞、性能好适合对一致性要求较高且业务逻辑可以拆分的场景。消息事务方案本地消息表是很多电商系统用的更轻量方案。基本思路是业务操作和消息写入放在同一个本地事务中。事务提交后通过MQ把消息发给其他系统。下游系统消费消息执行自己的业务。如果下游执行失败通过消费重试机制或者死信队列定时任务反复尝试。这个方案的优点是实现简单不用侵入业务逻辑写三套方法缺点是最终一致性的时间不确定取决于重试周期且可能产生消息重复消费需要下游做幂等处理。7.4 软考中的分布式事务考点从近年的软考真题来看分布式事务相关题目有升温趋势。常见考点总结如下2PC的流程和缺陷——重点记住协调者和参与者两个阶段的行为以及“阻塞”和“单点故障”两个核心缺陷。TCC与2PC的区别——TCC在应用层实现把事务控制从数据库层上移更灵活但侵入性更强。BASE理论与ACID的关系——BASEBasically Available, Soft state, Eventually consistent是分布式系统对ACID的妥协强调可用性和最终一致性。消息队列在分布式事务中的作用——核心是“异步解耦重试补偿”。工程上还有一个重要原则要提能不引入分布式事务就不引入。很多业务看似需要分布式事务但仔细分析后可以把多个本地操作合并到同一个服务中数据量允许时或者通过冗余数据、异步消息解耦把强一致降级为最终一致。分布式事务是复杂度的重要来源引入前必须评估收益。8. 高频考点与面试题的实战攻关8.1 针对“事务四种隔离级别”的万能解题法遇到判断隔离级别的题按下面三步走基本不会错先判断读到的数据是否来自未提交事务。是就是脏读问题发生在“读未提交”级别。如果读到的都是已提交数据再看两次读取同一记录是否值不同。值不同就是不可重复读问题发生在“读已提交”级别。如果值都一样再看满足条件的记录条数是否变化。数量变了就是幻读理论上发生在“可重复读”级别用串行化解决。举个例子事务A查询余额为1000事务B扣减余额并提交事务A再查余额变成了900。这说明两次读取同一记录值不同——不可重复读。要解决这个问题起步就要把隔离级别设为可重复读。这类题只要按这个路径推理正确答案就很明确了。8.2 事务相关的典型面试题拆解结合“java事务面试题”这个热词把面试中高频出现的事务问题整理为快查表高频问题回答要点事务是什么ACID是什么逻辑工作单元原子性、一致性、隔离性、持久性MySQL怎么实现ACIDundo log保证原子性redo log保证持久性锁MVCC保证隔离性约束应用逻辑保证一致性Transactional什么时候失效同类内部方法调用不经过代理、private/final方法、非RuntimeException未指定rollbackFor、异常被catch吞掉脏读、不可重复读、幻读的区别是什么未提交数据 vs 已提交数据值变化 vs 记录数量变化差异分布式事务怎么实现2PC强一致TCC、本地消息表最终一致8.3 高频错题陷阱提醒最后汇总几个软考真题中反复出现的易错点“事务的隔离级别越高并发性能越差”——这句话在大多数情况下正确但并不是绝对的。例如可重复读级别下如果查询走了合适的索引只需要加行锁某些场景下并发能力并不比读已提交差多少。考试出现“越隔离性能一定越差”的绝对化表述时要警惕。“MySQL的默认隔离级别是可重复读Oracle的默认隔离级别是读已提交。”这两个必须分清。软考经常给一张数据库对比表把这两个默认值搞反的大有人在。“事务一旦提交就永久生效”——已提交事务也可能因为主从切换丢数据。在MySQL半同步复制配置下如果主库提交后还没同步给从库就宕机新主库可能缺少这条提交记录。这个是分布式环境下的特殊场景案例题中如果提到“主从复制”就要注意这个坑。“可重复读解决了幻读”——不严谨。InnoDB在快照读场景下解决了幻读但在当前读SELECT ... FOR UPDATE、UPDATE、INSERT场景下确实消除了幻读。考题如果表述绝对“完全解决了幻读”属于不准确。最后分享一个我实际排查故障的心得每次遇到事务相关的问题不要一上来就查代码。先看隔离级别配置、看事务日志、看锁等待情况——用命令SHOW ENGINE INNODB STATUS查看当前锁等待和死锁信息。实践验证下来绝大多数事务问题都能通过日志快速定位远比凭空猜代码高效。备考软考时养成这种“先看机制、再定位代码”的思路对案例分析题特别有帮助。把上面这些内容吃透事务相关的基础知识就算真正过关了。这不仅仅是为了应对考试——工程中任何一个线上数据不一致的故障最终排查下来大概率都能追溯到事务机制的一个细节上。把ACID从口头的四个字母变成脑子里的四套机制这就是从“知道”到“掌握”的过程。篇幅有限就不再展开下一篇接着整理其他高频考点。
返回列表