当前位置: 首页 > article >正文

深入理解 SQL 事务:原理、应用与 MyBatis 配置

一、引言

在数据库操作中,数据的一致性和完整性至关重要。SQL 事务作为保障数据可靠性的关键机制,在各类业务系统开发中扮演着不可或缺的角色。本文将深入探讨 SQL 事务,从其产生的必要性、定义、特性,到实际应用及在 MyBatis 中的配置,带您全面了解这一重要概念。

二、为什么需要事务

想象一个常见的银行转账场景:张三给李四转账 100 元。在数据库层面,这涉及两条 SQL 语句,一条是从张三账户扣除 100 元(UPDATE act SET money=money-100 WHERE id = 1),另一条是向李四账户增加 100 元(UPDATE act SET money=money+100 WHERE id = 2)。如果没有事务机制,这两条 SQL 语句可能会出现部分执行的情况。比如,扣除张三账户金额成功了,但在给李四账户增加金额时,由于系统故障或其他原因失败了,这就会导致数据不一致,出现张三钱少了,李四却没收到的问题。而引入 SQL 事务,就能保证这两条语句要么同时成功执行,要么同时失败,避免这类数据不一致的情况发生。

三、什么是事务

事务是一个完整的业务流程,是不可再分的工作单元。在 SQL 中,事务只和数据操作语言(DML)语句有关,像INSERT(插入)、UPDATE(更新)、DELETE(删除) 语句,这些操作构成了事务的具体内容。事务的边界由业务逻辑决定,不同的业务逻辑可能包含不同数量的 DML 语句,但都要作为一个整体来处理。例如在电商系统中,创建订单并扣减库存这一系列操作就可看作一个事务,其中创建订单可能是一条INSERT语句,扣减库存是一条UPDATE语句,它们必须共同成功或共同失败,以保证订单业务和库存数据的准确。

四、事务的四大特征(ACID)

(一)原子性(Atomicity)

原子性指一个事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。比如在前面提到的转账例子中,从张三账户扣款和给李四账户打款这两个操作必须作为一个原子操作,不能只执行其中一个。若其中任何一个操作失败,整个事务就会回滚,将数据库状态恢复到事务开始前,确保数据的一致性不会被破坏。

(二)持久性(Durability)

持久性表示事务一旦提交,它对数据库所做的改变就应该是永久性的,接下来的其他操作或故障不应该对其有任何影响。例如,在银行转账事务成功提交后,即使银行系统突然断电、服务器崩溃等,转账的结果也依然存在,不会丢失或恢复到转账前的状态。这是通过数据库的日志记录等机制来保证的,在事务提交时,相关的修改记录会持久化存储,以便在系统恢复时能依据记录保证数据的持久性。

(三)隔离性(Isolation)

隔离性要求事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。在多用户、高并发的数据库环境下,多个事务可能同时对相同的数据进行操作。例如多个用户同时下单购买同一款商品,每个用户的下单事务都应该相互隔离。不同的隔离级别(如读未提交、读已提交、可重复读、串行化)决定了事务之间的隔离程度。较低的隔离级别可能会出现脏读(一个事务读取到另一个事务未提交的数据)、不可重复读(一个事务中多次读取同一数据时,由于其他事务的修改导致读取结果不一致)等问题;而较高的隔离级别(如串行化)虽然能完全避免并发问题,但会降低系统的并发性能。

(四)一致性(Consistency)

一致性强调事务执行的结果必须使数据库从一个一致状态变到另一个一致状态。数据库中的数据要满足所有预先定义的完整性约束,如数据类型约束、主键约束、外键约束等。例如在一个员工信息表中,员工的年龄字段定义为整数类型且必须大于 0,如果一个事务要更新员工年龄,更新后的数据必须符合这些约束条件,否则事务就不能成功提交,以保证数据库的一致性。

五、添加事务

在 SQL 中,添加事务通常使用以下几个关键语句:

(一)START TRANSACTION(或BEGIN,不同数据库略有差异)

用于开启一个新的事务。例如:

START TRANSACTION;

这表示从该语句开始,后续的 DML 操作都将作为一个事务的组成部分。

(二)COMMIT

当事务中的所有操作都成功执行,且满足业务逻辑和数据一致性要求时,使用COMMIT语句来提交事务,将事务对数据库所做的修改永久保存到数据库中。示例如下:

UPDATE act SET money = 300 WHERE id = 1;
UPDATE act SET money = 100 WHERE id = 2;
COMMIT;

上述代码实现了张三给李四转账 100 元的操作,在两条UPDATE语句执行无误后,通过COMMIT提交事务,使转账操作生效。

(三)ROLLBACK

如果在事务执行过程中出现错误,如违反了数据库的约束条件、执行过程中数据库连接中断等,就需要使用ROLLBACK语句来回滚事务,撤销事务中已经执行的操作,使数据库恢复到事务开始前的状态。例如:

START TRANSACTION;
UPDATE act SET money = 300 WHERE id = 1;
-- 假设这里出现错误,比如违反了某个约束
ROLLBACK;

这样,前面执行的UPDATE操作就会被撤销,数据库状态不会发生改变。

以下是一个完整的转账事务示例:

-- 开始事务
START TRANSACTION;
-- 从张三账户扣除金额
UPDATE act SET money = money - 100 WHERE id = 1;
-- 向李四账户添加金额
UPDATE act SET money = money + 100 WHERE id = 2;
-- 提交事务
COMMIT;
-- 如果在执行过程中出现错误,可以使用 ROLLBACK 回滚事务
-- ROLLBACK;

六、Rollback 事务回滚

事务回滚(ROLLBACK)是事务机制中保障数据一致性的重要手段。当事务执行过程中遇到异常情况,如数据库约束冲突(试图插入重复的主键值)、磁盘空间不足导致写入失败等,或者业务逻辑判断某些操作不能继续进行时,就需要回滚事务。回滚操作会撤销事务开始后对数据库进行的所有修改,将数据库恢复到事务开始前的状态。

在程序代码中,通常会结合异常处理机制来实现事务回滚。以 Java 和 JDBC 为例:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class TransactionExample {
    public static void main(String[] args) {
        Connection connection = null;
        try {
            // 建立数据库连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/your_database", "username", "password");
            // 开启事务
            connection.setAutoCommit(false);

            Statement statement = connection.createStatement();
            // 执行第一条SQL语句
            statement.executeUpdate("UPDATE act SET money = money - 100 WHERE id = 1");
            // 假设这里出现异常
            int i = 1 / 0; 
            // 执行第二条SQL语句
            statement.executeUpdate("UPDATE act SET money = money + 100 WHERE id = 2");

            // 提交事务
            connection.commit();
        } catch (SQLException | ArithmeticException e) {
            // 捕获异常并回滚事务
            if (connection != null) {
                try {
                    connection.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            // 关闭连接
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

在上述代码中,由于故意制造了一个除零异常,导致第二条UPDATE语句无法正常执行,catch块捕获到异常后,调用connection.rollback()方法回滚事务,撤销第一条UPDATE语句对数据库的修改,保证数据不会因为部分执行而出现不一致。

七、MyBatis 对事务的配置

MyBatis 是 Java 中常用的持久层框架,它对事务的管理有多种方式。

(一)使用 JDBC 事务管理

MyBatis 默认使用 JDBC 事务管理。在这种方式下,MyBatis 会从数据源获取一个连接,事务的开启、提交和回滚都由 JDBC 的Connection对象来控制。以下是简单示例:

SqlSession sqlSession = sqlSessionFactory.openSession();
try {
    // 此时事务开始,MyBatis从数据源获取连接,默认自动提交为false
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = new User();
    user.setAge(18);
    userMapper.insert(user);
    // 提交事务
    sqlSession.commit();
} catch (Exception e) {
    // 回滚事务
    sqlSession.rollback();
} finally {
    // 关闭SqlSession,归还连接到数据源
    sqlSession.close();
}

在上述代码中,通过SqlSession对象来控制事务,openSession()方法获取的SqlSession关联了一个数据库连接,在try块中进行数据库操作,成功则调用commit()提交事务,失败则在catch块中调用rollback()回滚事务,最后在finally块中关闭SqlSession

二)使用 Spring 整合 MyBatis 的事务管理

在基于 Spring 的项目中,通常会使用 Spring 的事务管理来统一管理 MyBatis 的事务。首先需要在 Spring 配置文件中配置事务管理器,例如使用DataSourceTransactionManager

 

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="DataSource" ref="dataSource"/>
</bean>

然后在需要事务管理的方法或类上添加事务注解,如@Transactional

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Transactional
    public void insertUser() {
        User user = new User();
        user.setAge(18);
        userMapper.insert(user);
        // 这里如果出现异常,事务会自动回滚
        // 不需要手动调用rollback,Spring会根据异常情况处理
    }
}

使用 Spring 的事务管理可以更方便地进行事务的声明式管理,通过注解就能轻松控制事务的范围和属性,并且能与 Spring 的其他功能(如 AOP、依赖注入等)无缝集成,提高开发效率和代码的可维护性。

八、总结

SQL 事务通过其 ACID 特性,为数据库操作提供了强大的数据一致性和完整性保障。从简单的转账业务到复杂的电商、金融系统,事务机制都不可或缺。掌握事务的添加、回滚以及在 MyBatis 等框架中的配置方法,能帮助开发者编写出更可靠、更健壮的应用程序,确保在各种复杂场景下数据的准确性和可靠性。在实际开发中,根据业务需求合理选择事务的隔离级别、正确处理事务回滚等,都是需要不断实践和积累经验的重要方面。

九、如何确保事务的一致性?

1、遵循事务的 ACID 特性

  • 原子性保障:确保事务中的所有操作构成一个不可分割的整体,要么全部执行成功,要么全部回滚。比如银行转账事务,从账户 A 扣款和向账户 B 打款这两个操作必须同生共死。数据库管理系统通常会提供相关机制,如日志记录等,当出现异常时利用日志信息撤销已执行的部分操作,实现回滚,保证原子性,进而为一致性奠定基础。
  • 隔离性保障:并发环境下,控制好事务之间的隔离程度。合理设置事务隔离级别,像读未提交、读已提交、可重复读和串行化等。较低隔离级别可能引发脏读、不可重复读等问题影响一致性;较高隔离级别能避免这些问题,但可能降低并发性能。可以根据业务对数据一致性和并发性能的要求来选择合适的隔离级别,比如在金融业务中,对数据一致性要求极高,可选用较高的隔离级别 ;而在一些对实时性要求高但对偶尔的数据不一致容忍度稍高的业务场景,可选择较低隔离级别。
  • 持久性保障:事务提交后,其对数据库的修改应永久保存。数据库通过日志文件、磁盘存储等机制来实现,如预写日志(WAL),先将事务操作记录到日志中,再更新数据文件。即使系统出现故障,在恢复时也能依据日志将数据恢复到事务提交后的状态,保证一致性不会因系统故障而被破坏。

2、合理使用数据库约束

定义并应用各种数据库约束条件,如数据类型约束、主键约束、外键约束、唯一约束、检查约束等。以银行账户表为例,金额字段设为数值类型且不能为负,通过检查约束实现;客户表和订单表通过外键约束关联,确保数据引用的一致性。当事务执行中试图违反这些约束时,数据库会阻止操作并抛出错误,回滚事务,保证数据始终符合业务规则和逻辑,维持一致性。

3、并发控制

  • 锁机制:数据库提供多种锁,如共享锁(读锁)、排他锁(写锁)。事务在读取数据时加共享锁,允许多个事务同时读;在修改数据时加排他锁,阻止其他事务读写,避免并发修改导致的数据不一致。但要注意锁的粒度和使用时长,过大的锁粒度或长时间持有锁会降低并发性能,可能引发死锁问题。
  • 多版本并发控制(MVCC) :为每个数据版本维护一个时间戳或版本号。事务读取数据时,根据自身的时间戳或版本号获取对应版本数据,无需加锁,提升并发性能。写操作则创建新的数据版本。在并发场景下,不同事务能同时读取和修改数据,且相互不干扰,保证数据一致性。例如在电商商品浏览和库存更新场景中,MVCC 能让大量用户同时浏览商品(读操作)的同时,库存更新(写操作)也能正常进行 。

4、分布式事务处理

  • 两阶段提交协议(2PC) :引入事务协调者,将事务分为准备阶段和提交阶段。准备阶段,协调者询问参与者是否可提交事务,参与者执行事务操作但不提交,并反馈结果;提交阶段,若所有参与者都准备成功,协调者通知提交,否则通知回滚。能保证强一致性,但存在协调者单点故障、性能瓶颈(长时间锁定资源)等问题。
  • 三阶段提交协议(3PC) :在 2PC 基础上增加预提交阶段,解决协调者单点故障问题,减少参与者阻塞时间,提升系统容错性和性能,但仍无法完全避免不一致情况。
  • TCC(Try - Confirm - Cancel) :把事务拆分为尝试(Try)、确认(Confirm)、取消(Cancel)三个阶段。Try 阶段尝试执行并预留资源;Confirm 阶段确认提交;Cancel 阶段在失败时释放预留资源。具有较高并发度,不过需要业务代码实现三个阶段的逻辑,开发量较大。
  • Saga 模式 :将长事务拆分为多个本地短事务,由协调器管理。若某个短事务失败,按相反顺序调用补偿事务回滚已完成操作。适合长周期事务,并发度高,但一致性相对较弱 ,需定义好正常操作和补偿操作。
  • 基于消息中间件的方案 :如本地消息表、事务消息等。本地消息表通过在本地数据库建表记录消息,保证本地事务和消息发送一致性;事务消息借助消息中间件的半消息机制,解决生产端消息发送与本地事务执行的原子性问题,最终实现数据的最终一致性 。

5、应用程序层面的控制

开发人员在编写业务逻辑代码时,要正确处理事务。合理界定事务边界,避免事务范围过大或过小;对可能出现的异常进行全面捕获和处理,在异常发生时及时回滚事务;在涉及多个数据库操作的事务中,确保操作顺序和逻辑正确,符合业务规则。例如在电商下单业务中,应用程序要按正确顺序处理库存扣减、订单生成、支付处理等操作,并在出现问题时回滚整个事务 。


http://www.kler.cn/a/594501.html

相关文章:

  • 循环神经网络(Recurrent Neural Network, RNN)与 Transformer
  • xxl-job 执行器端服务器的简单搭建
  • 【2025】基于springboot+vue的教务/课程/成绩管理系统设计与实现(源码、万字文档、图文修改、调试答疑)
  • ChatGPT、DeepSeek、Grok 与大数据:智能 AI 在数据时代的角色与未来
  • 使用Python在Word中创建、读取和删除列表 - 详解
  • 在.Net Core(.Net5)中使用开源组件SqlTableDependency来监听ms sqlserver的数据库数据变化
  • 谈谈 TypeScript 中的联合类型(union types)和交叉类型(intersection types),它们的应用场景是什么?
  • Android NDK --- JNI从入门到基础的全面掌握 (上)
  • JSON 解析中需要清理的危险字符
  • 【Linux】VMware17 安装 Ubuntu24.04 虚拟机
  • JavaScript 中的模块化开发:从 CommonJS 到 ES6 Modules
  • 如何处理 React 应用中的状态管理?
  • 什么是 React 的 Fragment?
  • OpenCSG GUI模型:引领Compute Use自动化新时代
  • 【SpringCloud】Eureka、LoadBalancer和Nacos
  • 调研报告:Hadoop 3.x Ozone 全景解析
  • NLP 与常见的nlp应用
  • 计算机视觉技术探索:美颜SDK如何利用深度学习优化美颜、滤镜功能?
  • 【Axure视频教程】数字滚动效果
  • ChatTTS 开源文本转语音模型本地部署 API 使用和搭建 WebUI 界面