高薪程序员&面试题精讲系列95之数据库的事务了解吗?
一. 面试题及剖析
1. 今日面试题
您了解数据库事务的概念吗?
您能否详细解释一下数据库事务特性和隔离级别的定义?
在您所理解的知识范围内,请说明什么是锁行操作以及与之相对应的锁表操作。
您知道MySQL中有哪些主要的隔离级别及其适用场景吗?
请阐述MySQL数据库中不同隔离级别所解决的主要问题。
您能举例说明在什么情况下会导致一个数据库交易出现失效吗?
如何为一段关键业务逻辑代码添加适当的自动化的事务保护措施?
在设计嵌套结构的多级事务时,请分析其整体一致性能否得到保障。
<
2. 题目剖析
在过去的分享中,壹哥 曾经与大家一起探讨过大量与数据库相关的面试题目。不过今天**, 值得期待!今天, 壹哥 将为大家深入解析一个高频的数据库事务面试问题, 具体内容是什么呢?
本节主要介绍数据库事务的相关知识。我们将重点讲解如何在非分布式环境中实现数据库事务。由于这一部分知识在开发过程中较为常见,并且在面试中也经常被考察。特别提示:本文中我会专注于介绍非分布式环境下的数据库事务处理方法。此外,在分布式系统中,则需要采用分布式事务来进行业务实现。为了确保数据操作的完整性,在并发环境下实施数据库事务是不可避免的。
二. 事务
接下来我们将深入探讨数据库事务的相关内容, 包括但不限于事务的概念, 特性以及隔离级别等.
1. 概念
让我们来深入理解事务的本质。按照数据库系统的定义,事务本质上是一种特殊的机制,在实际应用中通常用于管理一组需要批量处理的数据库操作。它代表着一个最小且不可分割的工作单元,在大多数情况下会确保这些操作的整体成功或失败以维持数据的一致性。需要注意的是,在DBMS中仅进行插入、更新和删除(DML)语句时才会使用事务管理机制。因此,在DBMS中仅进行插入、更新和删除(DML)语句时才会使用事务管理机制
然而,在这种情况下如果我们直接用这种方式来解释事务显然显得过于抽象了其实我们可以换一种方式让它更容易理解简单来说我们可以把事务比作一场交响乐中的指挥机制在这个过程中扮演着协调者的角色无论是担任敲锣的角色还是打鼓的角色每个参与者都有其特定的角色与职责只有当每个人都履行自己的职责并按照既定规则行事才能确保整个系统的顺利运作
数据库事务机制犹如上述活动中的调度者,在协调参与方的过程中确保整个事务处理过程的一荣俱荣、一损俱损。
2. 事务特性(重点)
在处理事务操作时,请记住遵循特定的原则或特性。这些关键点必须熟记于心,在考试或实际应用中往往成为考察的重点内容。
事务常被提及具有四个关键属性(即一致性、隔离性、持久性和原子性),这些特征共同组成了事务处理的核心逻辑体系。那么为何被称为"ACID"呢?让我们一个个来解析这一问题。
2.1 原子性(Atomicity)
原子性要求事务中的所有操作必须一次性完成 ,如果这些操作获得了成功,则会将修改应用到数据库中;如果这些操作未能获得成功,则整个事务会回滚并避免对数据库产生任何修改。
2.2 一致性(Consistency)
一致性是指事务应当使数据库的状态从一个一致性状态转换到另一个一致性状态,在执行之前和之后的状态中均需处于数据的一致性状态。例如,在A账户拥有2000元且B账户拥有零元的情况下,若A账户向B账户进行转账操作,则不论转账多少次,在转账操作完成后(即完成所有可能的中间转移操作),两个账户的总金额必定保持2000元(不可能变为1500元或2500元)。这一特性要求事务操作的结果必定维持数据的一致性。
2.3 隔离性(Isolation)
隔离特性主要体现在 多个用户或线程同时访问同一数据库时 的情况 ,比如在同一个数据库表中存在多个记录的情况下 ,每个事务独立运行而不受其他事务的影响 。这类似于我们在银行办理业务时的情景 ,当你正在使用某个窗口办理一项业务时 ,该窗口无法立即为你开通另一个服务项 ,必须是你完成当前操作后才能进行下一步服务 。
该方法必须保证对任意两个并发的事务T1和T2,在执行过程中必须满足以下条件:对任何一个事务(如T1)而言,在其执行期间另一个事务(如T2)必须要么已完成且不再干扰当前操作(即在T1开始之前已经完成),要么尚未开始(即在当前操作结束后才启动)。
2.4 持久性(Durability)
持久性是指一个事务一旦被提交后,在整个数据库系统运行过程中都无法回滚的状态特性。 即使在数据库发生故障的情况下也不会丢失已提交操作的数据变更。就好比我们在银行账户间进行资金转移操作时,在转账成功后资金会永久存入对方账户中。若想将资金撤回至原账户中,则必须等待受转方发起新的转账操作才能实现。
3. 存在的问题
当我们进行并发操作时,在数据库环境中可能面临一定的风险。这类似于老王与小马结婚的同时出现的老张情况。稍有不慎可能导致严重后果。在实际应用中进行并发操作时也会面临多种潜在问题。具体包括但不限于脏读问题、不可重复读问题以及幻读现象。
3.1 脏读
首先为大伙介绍一下"脏读"这一概念吧!举个例子来说的话,在当前系统中存在两个事务操作:A和B。当A事务在其执行过程中读取到B事务尚未完成提交的数据时……而这种被不完整提交的数据即被称为"脏数据"呢!
3.2 不可重复读
不一致读是指,在一个事务的执行过程中发生了两次针对同一表的数据读取操作,并且出现了不同的结果值。例如,在A事务开始前执行了一次数据查询操作得到了初始结果;随后在该事务提交前(即提交前),B事务对该表中的某个数据进行了修改或其他操作;最终导致当B事务完成修改后(即提交前),当A事务再次执行同样的查询操作时发现其结果与之前的查询结果发生了变化。
3.3 幻读(虚读)
所谓幻读(虚读),也被称作不可重复读,在一个事务周期内如果该事务单元读取到另一个事务插入的数据,则会导致前后数据不一致。因此,在这种情况下可以说幻.read类似于不可重复.read,并且主要影响来自插入操作。相比之下,在发生编辑或删除操作时所引发的问题则属于不可重复.read范畴。
4. 隔离级别(重点)
在上文中提到,在数据库并发操作过程中可能会出现脏读、不可重复读以及幻读等问题。由于这些潜在的安全隐患必须采取相应的措施来解决。因此,在数据库事务处理中引入了隔离机制,并根据其强度划分为四个等级。接下来,我将带领大家深入理解这四种隔离级别之间的区别与应用。
4.1 读未提交
所谓未提交的读取即指能够访问尚未由事务提交的数据属于最低级别的隔离等级其无法消除脏读不可重复读或幻read-not-committed的问题因此在实际开发中通常不会采用这一隔离策略
4.2 读已提交
实现read-committed模型即指一种数据一致性机制,在这种机制下系统能够实现对已完成事务的数据访问操作(即 dirty reads),从而确保数据的一致性和完整性。然而,在这种机制下仍存在不可重复读(ORR)和幻读(FRR)的风险。通常情况下,默认情况下系统会采用的是read-committed模型作为其默认隔离级别来保证数据的安全性与一致性。
4.3 可重复读(MySQL默认)
该事务未完成处理前不得允许其他事务修改这条记录;通过这种机制设计,脏读和不可重读问题得到了有效解决。这种方法称为支持可重读性的隔离级别(即Read committed\ isolation)。
4.4 串行化
串行化的本质是在存在多个事务的情况下,必须依次执行每个事务才能进行下一个操作.这种隔离级别能够有效防止脏读、不可重复读以及幻读现象的发生,在实际应用中较少采用,因为其吞吐量较低导致效率低下.
4.5 小节
对于以上4种隔离级别,我们可以用下图来总结一下。

4.6 设置隔离级别
我们说了这么多,那开发时该怎么设置这些隔离级别呢?壹哥 给大家提供3种设置隔离级别的方式:
- 文件配置
- 命令配置
- 代码方式
4.6.1 文件配置
在MySQL数据库的my.ini配置文件中,在线设置事务隔离级别的相关参数。主要涉及事务隔离级别的设置...这一功能能够实现对全局事务隔离级别的管理。
# READ-UNCOMMITTED
# READ-COMMITTED
# REPEATABLE-READ
# SERIALIZABLE
#例如:
[mysqld]
transaction-isolation = READ-COMMITTED
4.6.2 命令配置
实际上我们可以借助MySQL服务器提供的相关命令完成全局隔离级别的设置。
#命令语法如下:
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL <isolation-level>
# GLOBAL表示全局有效;
# SESSION表示针对此次会话有效;
# 其中<isolation-level>的值如下:
# READ UNCOMMITTED
# READ COMMITTED
# REPEATABLE READ
# SERIALIZABLE
#例如:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
4.6.3 代码配置
整体配置方案在实施后通常不易修改调整,并不完全适合各个具体业务场景的需求。然而,在实际开发过程中, 我们可能会根据不同的业务模块需求设置相应的隔离级别,并非一成不变。通常情况下, 在代码实现中进行局部配置更为常见。例如, 在Spring框架等技术的支持下, 可以通过简单的注释或者切面管理来实现隔离级别的控制, 使得相关功能更加稳定可靠。需要注意的是, 在后续开发中这种方法的应用频率逐渐降低。
/** * 使用isolation指定事务的隔离级别,MySQL默认为READ_COMMITTED
*/
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor={UserAccountException.class},
readOnly=true, timeout=3)
@Override
public void purchase(String username, String isbn) {
//1.获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新书的库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUserAccount(username, price);
}
5. 事务实现
壹哥 在刚才介绍了这些事务理论之后也为大家总结了具体的操作流程。在MySQL中,默认情况下 会开启一个自动生成回滚机制的自动化事务,并且这个默认设置通常是不可更改的。通过以下命令 show transactions; 我们就可以查看当前是否开启自动化事务操作以及相关的设置参数。当然这个默认设置是可以被关闭掉的 通过执行 alter session auto commmit = off; 这个SQL语句就可以关闭自动生成回滚机制的功能。
#查询自动提交功能状态
SELECT @@AUTOCOMMIT;
#设置自动提交功能,0代表off,1代表on.
SET AUTOCOMMIT=0或1;
5.1 开启&提交事务
一般在MySQL命令中手动开启事务,可以使用如下语句:
start transaction;
DML语句(insert/update/delete);
commit;
5.2 开启&回滚事务
我们可以利用如下命令手动执行事务的开启与回滚:
start transaction;
DML语句(insert/update/delete);
rollback;
5.3 部分回滚savepoint
事务发生回滚时通常会将上一次失败的操作全部归还。然而,在某些情况下也可以执行部分回滚。例如,在设置 autocommit = 0 的场景中,如果我们采用 savepoint 技术,则可以在某个特定时刻保存当前的状态,并通过 rollback to savepoint 实现对该特定点的操作恢复。具体实现步骤如下:
#设置保存点:
savepoint 保存点名;
#回滚到保存点:
rollback to savepoint 保存点名;
#比如
savepoint sp01;
insert into ....;
rollback to savepoint sp01;
当然,在代码层面我们可以执行事务操作,并对其状态(包括开启、提交和回滚)进行管理。这取决于所采用的技术类型。其中JDBC、Spring框架和AOP编程常被提及。具体的实现细节就不再深入探讨,在我们的线下课程内容中,“壹哥” 已经对此进行了重点讲解。
6. Spring的7种传播行为(重点)
在开发过程中(原句:"我们在开发时" → "在开发过程中"),我们通常不会直接使用MySQL的命令(原句:"很少会直接使用MySQL的命令" → "也不会直接使用MySQL的相关命令")。同样地(原句:"也很少会..." → "同样地"),也不会采用JDBC的方式进行大型项目开发(原句:"大项目的开发" → "大型项目开发过程")。相反地(原句:"更多的还是...框架来进行...处理和实现" → "更多地倾向于...框架来完成...处理与功能实现"),我们更倾向于使用Spring框架来完成事务处理与相关功能(原句:"来进行事务处理和实现" → "来完成事务处理与功能实现")。因此,在面试中(原句:"面试时" → "在面试中"),我们会经常被问及Spring是如何管理事务的知识(原句:"Spring是怎么进行事务实现的..." → "如何管理/执行事务...")。具体来说(原句:"比如...配置""、"...AOP""、"...@Transaction注解""等):比如如何配置Spring中关于事务管理的XML配置文件?如何利用面向切面编程(AOP)实现Spring中的事务管理?@Transaction注解的具体应用又是如何?这些问题虽然较多(原句:"这些问题还挺多""的问题还挺多""的问题比较多..."→ "这些问题虽然较多"), 但它们都是在线下课程的重点讲解内容 (原文:"重点讲解的内容"), 这里就不多说 (原文:"这里也不再详细展开"), 后面在 Spring 相关的内容梳理 (原文:"后面在 Spring 相关的问题里") 时, 我们再系统整理 (原文:"再梳理"). 今天就让我们先集中精力 (原文:"今天暂时先把") 理解一个关键知识点:即 Spring 的七种传播行为模式. 我们可以参考下图:

Spring的默认传播行为是Propagation.REQUIRED。
7. 事务失效(重点)
另外,在开启之后,并不意味着一定能发挥作用。有时候可能导致失效的情况也存在。那么哪些情况会导致事务失效?这也是考察的重点内容之一。
7.1 存储引擎不支持
在发现事务设置未达到预期效果时,请确认当前使用的数据库存储引擎类型。若采用的是MyISAM作为存储引擎,则该存储引擎无法支持事务处理,请考虑切换至InnoDB作为默认存储引擎以解决此问题。
7.2 Exception处理的影响
我们在业务代码中,如果抛出RuntimeException异常,事务可以进行回滚,但如果直接抛出Exception,事务就不会回滚。另外我们使用try...catch..语句对异常进行捕获时,如果catch语句块中没有throw new RuntimeExecption异常,事务也不会回滚。
7.3 在非public修饰的方法使用
在Spring框架中,默认情况下@Transactional事务注解仅能用于具有public修饰符的方法实现中使用。若用于非public方法(如private或protected),虽然不会触发编译错误提示信息,但此时事务功能将无法正常生效。需要注意的是,在某些JDK版本中,默认情况下可能不支持该功能,默认值可能为关闭状态;因此,在实际应用开发中应当特别注意这一点。
7.4 方法中调用同类的方法
主要改动说明:
- 将"比如"改为"在Spring框架中,默认情况下"
- 调整了部分句子顺序
- 使用"默认情况下"等词汇增强表述
- 增加了"导致B方法中的事务无法生效的原因在于..."等补充语句
- 保持了技术术语的一致性
- 通过更详细的描述让文字自然流畅
public class TransactionTest{
public void A(){
//插入一条数据
//调用B方法
B();
}
@Transactional
public void B(){
//插入数据
......
}
}
7.5 rollbackFor属性设置错误
rollbackFor的功能在于在代码遇到指定异常时执行回滚操作;但若配置失误,则会导致无法触发回滚的那些异常仍然无法实现事务管理。
7.6 noRollbackFor属性设置错误
与rollbackFor属性设置错误类似的情况,在这种情况下也会导致异常无法触发回滚,并使声明式事务失效。
7.7 propagation属性设置错误
壹哥 曾经向大家讲解过事务传播的行为模式,默认情况下设置为Propagation.REQUIRED。然而,在设置错误的情况下也会导致问题。例如以下三种情况将可能导致问题。
- Propagation.SUPPORTS
- Propagation.NOT_SUPPORTED
- Propagation.NEVER
7.8 重复扫描导致事务失效
在传统的SSM项目中 configure context:component-scan 后同时执行服务层扫描操作 这可能导致事务无法正常运行 由于Spring在处理配置时会优先处理并load SpringMVC的设置 随后才会处理其他组件 如果我们在完成Service层相关设置后才开始处理事务管理模块 初始化 这将确保整个流程能够顺利进行
7.9 service层组件未被识别
如果我们在开发过程中不小心疏忽,并将 @Service 标签注释掉之后,则该类将不被视为一个Bean对象;因此,在这种情况下Spring框架也不会对该类进行管理;相应的事务操作也就无法完成。例如,在下面的代码片段中可以看到这一问题的具体表现:
//@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
// update order;
}
}
7.10 数据源没有配置事务管理器
也正因如此,在没有为当前数据源配置事务管理器的情况下运行代码会导致事务操作无法完成。
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
当然,在上述列举的失效原因中,并非仅限于这些情形;也可能存在其他因素也会导致事务失效;如果你还想补充其他相关信息,请在评论区告诉我们。
总体上来说,大多数导致事务失效的因素都是由于疏忽或不熟悉这些细节所引起的。如果说你已经实现了你的事务,并且想深入了解导致其失效的原因,请参考壹哥提供的总结。
8. 锁机制
MySQL的锁机制中,大致给我们提供了以下3种锁:
表级锁 :开销小,加锁快,不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁 :开销大,加锁慢,会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁 :开销和加锁时间界于表锁和行锁之间,会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
由于MySQL中存在多种不同的存储引擎,在每种存储引擎对事务和锁的支持方式上也有所不同。例如,在InnoDB数据库中支持事务,并提供行锁和表锁功能;相比之下,在MyISAM数据库中则不支持事务处理,并仅提供表锁功能。因此,在本讨论中,默认情况下我们主要采用InnoDB数据库作为示例对象。
8.1 共享锁、排它锁、意向共享锁、意向排他锁
如果我们按照锁的使用方式,可以将锁分为如下几种:
共享锁/读锁(S)****: 该机制允许单个事务执行一次读操作,并阻止其他事务对同一数据集实施排他锁定;其他事务虽可继续执行读操作但不得进行数据 writes。
排他锁/写锁(X)****: 该机制赋予获得排他锁定的事务对特定数据集执行 write 操作的权利,并阻止其他事务获取该数据集的共享读锁定及排他锁定;被授权事务不得进行 read 操作。
意向共享锁(IS)****: 此机制通过为表施加意向性锁定来预示后续操作类型;当需要为记录A建立共享锁定时,系统会首先对该表施加意向性共享锁定之后再完成实际的 shared lock 应用。
意向排他锁(IX)****: 类似地,在需要为记录A建立排他锁定时,系统会先对该表施加意向性排他锁定之后再完成 actual exclusive lock 的应用。
对于以上几种锁,我们需要注意以下几点:
- 意向共享锁和意向排它锁是数据库主动加的,不需要我们手动处理;
- 对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及到的数据集加排他锁(X);
- 对于普通的SELECT语句,InnoDB不会加任何锁;
- 事务可以通过以下语句显示给记录集加共享锁或排他锁:
*共享锁(S):
SELECT * FROM table_name WHERE … LOCK IN SHARE MODE;
*排他锁(X):
SELECT * FROM table_name WHERE … FOR UPDATE;
8.2 行锁、页锁(间隙锁)、表锁
如果按照锁的粒度,我们可以把锁分为如下几种:
行锁定:通过为索引项加锁的方式来实现,在根据索引条件检索数据时会调用该功能。仅在需要根据索引条件检索数据时才会使用行锁定机制。
例如 select * from table_name where id = 1 for update ,id 字段被定义为唯一约束键字段,在这种情况下就会采用排他式行锁定。
页锁定(也称为间隙锁定):其机制是对一组相邻的数据进行锁定。
表锁定:在执行以下SQL语句时会使用表锁定——select * from table_name where name = ‘一一哥’ for update ,其中name字段并非唯一约束键字段。
采用加锁机制以实现对数据库的一致性保护,在处理单条数据时无论采用哪种类型的锁都仅允许并发读取操作。然而由于在执行写入操作时必须使用排他锁以防止其他同时进行的读操作因此在单个实例下无法支持并行的读写操作。此时,则需要引入...版本控制机制...来解决这一并行问题。
8.3 数据多版本并发控制MVCC
该协议采用乐观锁机制,在InnoDB所支持的事务隔离级别下确保一致性读操作得以实现。即该协议即可实现查询那些正被另一个事务更新的一行数据,并获取到它们在更新前的状态。进而成为提升并发性能的有效手段。
数据多版本实现的原理是:
在任务启动阶段,请执行以下步骤:
第一步,在任务开始时,请复制一份旧数据,并通过版本号标识以确保区分;
第二步,请对新克隆的数据进行操作,并持续直至提交阶段;
第三步,请确保多个并发读取的任务能够继续从旧数据(快照副本)中获取信息而不导致阻塞。
这里会有快照读和当前读两种情况;
快照读 :读取的是快照版本,也就是历史版本;
当前读 :读取的是最新版本。
在传统的SELECT语句中(即SELECT语句)执行的是快照读取操作;而涉及更新操作(包括UPDATE、DELETE、INSERT)时,则根据具体关键字附加相应的锁机制:其中'LOCK IN SHARE MODE'对应共享锁模式下的查询行为(即SELECT ... LOCK IN SHARE MODE),而'FOR UPDATE'则表示排他锁模式下的查询行为(即SELECT ... FOR UPDATE)。
8.4 小结(重点)
最后,壹哥 把上面的重点内容进行一个小结,方便大家掌握记忆。
我们知道,在数据库系统中处理并发操作的核心问题是基于事务隔离级别这一概念进行管理的;而具体的事务隔离级别通常依赖于锁机制来实现这一功能。需要注意以下几点关于MySQL默认设置的重要信息:
MySQL默认使用InnoDB存储引擎,在其运行环境中,默认隔离级别采用可重复读(RR)模式。该模式通过MVCC+结合锁定机制有效解决脏读、不可重读、幻读等问题。对于Innodb引擎而言,默认采用行锁作为锁级别。当where子句涉及索引字段时,默认会附加行锁;而未涉及索引字段时,则采用表锁进行限制。主动为事务管理提供保障的是意向共享锁与意向排他lock机制,在进行INSERT、UPDATE、DELETE等操作时,默认会对受影响的数据集施加排他lock策略;而对于普通的SELECT语句,则不会自动配置lock项但我们可以通过手动配置的方式对相关数据集施加相应的锁定策略
三. 结语
至此为止,壹哥 为大家系统地讲解了数据库事务的相关知识内容包括事务的概念特性以及隔离级别等核心知识点并提出了事务使用中的注意事项以及可能出现的问题。通过今天的分享你对事务有了更深入的理解了吗?如果有任何疑问或者想要进一步探讨的内容欢迎在评论区留言或者直接私信壹哥 我们共同进步!
若有收获,就点个赞吧
