数据库事务
操作数据库一般是通过SQL语句实现的,大多数情况下,一条SQL语句就能实现所需要的数据处理。但是,在有些情况下,需要通过多条SQL语句才能实现所需要的数据处理。例如,从A账号转账100元到B账号,数据库则需要先执行“A账号减少100元”的SQL语句,再执行“B账号增加100元”的SQL语句。
一旦出现这种通过多条SQL语句才能实现的数据处理,则可能会存在中间环节出现问题而导致数据不一致的情况,如“A账号减少100元”执行成功,但“B账号增加100元”执行失败时,则会造成100元凭空消失的情况。
为了解决这类“数据不一致”问题,需要利用数据库事务。数据库事务是把多条SQL语句划分为一个整体,这些操作要么全部执行,要么全部不执行。
第三方软件使用数据库事务的流程如图4.75所示。其中,提交事务指的是正常结束本次事务,事务中的数据操作都会生效;回滚事务指的是回滚本次事务中的所有数据操作,本次事务中的数据操作都不会生效。以Java为例,通过JDBC构造一个数据库事务的代码如代码4.52所示。

图4.75 第三方软件使用数据库事务的流程
代码4.52 通过JDBC构造一个数据库事务
//连接数据库,详细说明见4.4.4小节中的代码4.44
Connection connection = DriverManager.getConnection(
"jdbc:mysql://ip:port/xxx",
"userName",
"password");
try{
//关闭自动提交事务,可以变相理解为开启事务操作,但实际上开启事务的指令由数据库驱动
自动发送,自动发送的时机一般在第一次发送SQL语句的时候
connection.setAutoCommit(false);
//执行SQL语句1
…
//执行SQL语句n
//所执行的SQL语句都成功,提交事务
connection.commit();
} catch(SQLException e) {
//发生错误,回滚事务
connection.rollback();
}
数据库事务有4个特性,分别是原子性、一致性、隔离性和持久性,这4个特性一般也被称为ACID特性。下面是这4个特性的详细说明。
·原子性(Atomicity):事务中的全部操作是不可分割的,要么全部执行,要么全部不执行。
·一致性(Consistency):几个并行执行的事务,其执行结果必须与按顺序串行执行的结果相一致。
·隔离性(Isolation):事务的执行不受其他事务的干扰,这种属性也称为串行化。例如,有两个并发的事务T1和T2,在最高隔离等级下,T1执行的时机,要么在T2执行之前,要么在T2执行完毕之后。
·持久性(Durability):对于任意一个已完成的事务,必须保证该事务对数据库的改变是永久性的,即使数据库出现故障。
在最极端的情况下,数据库事务之间是完全隔离的,即多个数据库事务不会同时被执行,而是按一定的顺序串行执行。虽然这样能避免数据不一致的情况,但同时数据库的处理性能也会被限制(不能同时执行多个数据操作)。因此,数据库事务不能被滥用,当多条SQL语句不会造成“数据不一致”的情况时(如多条查询SQL语句),则不需要使用数据库事务。
注意: 数据库事务不是所有数据库引擎都支持的,例如InnoDB(MySQL默认引擎)支持数据库事务,而MySIAM则不支持。默认情况下,MySQL和Oracle都支持数据库事务。另外,单条SQL语句也可以简单地理解为是一个事务,但实际上也需要根据具体的数据库引擎而定。
1.数据库事务隔离等级
数据库事务之间完全隔离是非常浪费性能的,对于“读操作”而言,在很多情况下,所读取的数据都不是正在执行的数据库事务的修改目标,有些时候,这些所读取的数据也无须特别准确。因此,数据库为了提升数据库事务的性能,提供了可设置的数据库事务隔离等级。在非最高事务隔离等级下,在一个事务的执行过程中,允许其他事务执行“读操作”,但是其他事务的“写操作”仍然会被阻塞。
在介绍具体的数据库事务隔离等级之前,先介绍一下“读操作”被允许并发执行后可能会发生的一些问题,包括脏读、不可重复读和幻读。
·脏读:指的是一个事务读取了另一个事务未提交的数据,而未提交的数据可能会被撤销。
·不可重复读:指的是对于某一数据,一个事务中对其查询多次,但返回的结果不是相同的。这是由于在查询间隔中,所查询的数据可能被另一事务修改并提交了。
·幻读:指的是对于某一数据集,一个事务中对其查询多次,但返回的数据集不是相同的。这是由于在查询间隔中,可能是另一事务新增或删除了某条数据。
注意: 以上问题都可能造成“数据不准确”的情况,但是一些数据即使不准确也不会产生很大的影响。例如,后端应用程序接收了一个“获取某种普通信息列表”的请求,在程序获取数据的时候刚好有其他事务在更新数据,则程序获取的信息不是最新的,但是影响却不大。
在了解了以上问题后,下面介绍数据库事务隔离等级。数据库事务隔离等级被定义在SQL92标准当中,但实际上,不同的数据库对数据库事务隔离等级的划分是有区别的。以MySQL为例(符合SQL92标准),数据库事务隔离等级有4个级别,分别是读未提交、读已提交、可重复读和串行化。
·读未提交(Read Uncommitted):最低的数据库事务隔离等级。此隔离等级的事务在执行过程中,允许其他事务读取该事务未提交的“新数据”。此隔离等级下,脏读、不可重复读和幻读都可能发生。
·读已提交(Read Committed):此隔离等级的事务在执行过程中,允许其他事务读取该事务已提交的“新数据”。此隔离等级下,避免了脏读,而不可重复读和幻读仍可能会发生。
·可重复读取(Repeatable Read):MySQL默认的隔离等级,此隔离等级保证某一事务对某一数据的多次查询结果是一致的。此隔离等级下,能避免脏读和不可重复读,而幻读仍可能会发生。
·串行化(Serializable):此隔离等级保证事务完全隔离,多个数据库事务会被强制按一定的顺序串行执行。此隔离等级下,能避免脏读、不可重复读和幻读。但这个级别的隔离可能会造成大量的性能浪费和超时,在实际应用中不建议使用。
一般情况下,数据库事务隔离等级保持“可重复读取”级别即可(MySQL默认的隔离等级)。当然,数据库事务隔离等级也可以对单个事务进行设置,但一般不建议这么做,除非存在某些特别的需求。
2.Spring Boot中使用数据库事务
在Spring Boot中使用数据库事务的操作步骤如下:
(1)开启事务管理器。需要在xxApplication.java文件中添加注解,如代码4.53所示,其中,@EnableTransactionManagement为开启事务管理器的注解。
代码4.53 修改后的xxApplication.java文件
…
@SpringBootApplication
@EnableTransactionManagement //开启事务管理器
public class xxApplication {
public static void main(String[] args) {
…
}
}
…
(2)在业务层(Service层)中标记需要使用数据库事务的方法,如代码4.54所示,其中,@Transactional为数据库事务标记。
注意: @Transactional中可以设置其他参数,也可以手动设置回滚的节点(不回滚所有操作),这里不展开介绍。
代码4.54 在Service层代码中添加事务标记
…
@Service("XXX")
public class XXXService {
…
//标记数据库事务,当异常发生时,会自动撤销所有数据库操作。函数中不能用catch捕获
异常
@Transactional(rollbackFor=Exception.class)
public JSONObject ServiceFunction(JSONObject requestParam, JSONObject
returnParam){
//调用Dao层函数1(执行SQL语句)
xxxDao.update_1(…);
//调用Dao层函数2(执行SQL语句)
xxxDao.update_2(…);
return returnParam;
}
}
本文给大家讲解的内容是大型网站架构的技术细节:后端架构数据库--数据库事务
- 下篇文章给大家讲解的内容是大型网站架构的技术细节:后端架构数据库-分布式事务
- 感谢大家的支持!