文章目录

  • 前言
  • 一、事务是什么?
  • 二、事务的特性
    • 2.1隔离性
    • 2.2事务的隔离级别
  • 三、@Transactional注解
      • @Transactional注解简介
      • 基本用法
      • 常用属性配置
      • 事务传播行为
      • 事务隔离级别
      • 异常处理与回滚
      • 性能优化建议
  • 四、 事务不生效的可能原因
      • 方法访问权限非public
      • 自调用问题
      • 异常被捕获未抛出
      • 数据库引擎不支持事务
      • 未启用事务管理
      • 特殊场景:final/static方法
  • 五、分布式事务考虑
  • 总结


前言

在开发过程中,遇到多个数据库操作的时候往往只知道加上@transactional注解(spring框架)但是没有系统学习数据库的事务知识以及各种用法,本文会举例介绍数据库中事务的概念以及用法


一、事务是什么?

在实际的项目开发过程中会涉及很多不可分割的数据库操作,如转账业务的进账和出帐,要么全部执行成功,要么全部失败回滚。这样一些的对数据库操作的集合就叫事务。现在假设数据库中有一个表accounts

balanceuser_id
10001
1002

在数据库中执行一段事务的sql如下:

-- 显式事务示例
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
COMMIT; -- 或 ROLLBACK 回滚

很多人会觉得这里的COMMIT或者ROLLBACK之前,数据库操作只是在内存中,并没有更改表中的数据,需要注意的是,这里的COMMIT或者ROLLBACK 之前,数据已经是持久化了,也就是写入了磁盘(数据库表中)。

二、事务的特性

根据对事务的理解,可以归纳出事务应该具有的特性
原子性(Atomicity):事务被视为不可分割的最小单元,要么全部提交成功,要么全部失败回滚。
一致性(Consistency):事务执行前后,数据库从一个一致状态转变为另一个一致状态。
隔离性(Isolation):多个事务并发执行时,一个事务的操作不应影响其他事务。
持久性(Durability):事务提交后,其对数据的修改是永久性的。

2.1隔离性

在上述特性中比较难理解的应该是隔离性,可以想象这样一个场景:
因为网络延迟或者条件异常,事务A未提交还没来得及回滚

-- 事务A
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;

事务B开始查询事务,查询的结果就是balance = balance - 100

-- 事务B
BEGIN TRANSACTION;
select balance WHERE user_id = 1 from accounts;
commit;

事务A回滚,此刻a,b两个事务间就发生了干扰,a的事务的提交影响了b事务对数据库的正常读取。

-- 事务A
ROLLBACK ;--回滚

所以隔离性就是指的的是a事务对与b事务的互不影响的程度。

2.2事务的隔离级别

事务的隔离级别也可以叫做事务的互不影响程度的级别。
读未提交(Read Uncommitted):影响程度最高,即使事务没有commit,另外的事务也可以读到,可能导致脏读。

-- ----------------------
-- 窗口1(事务A)
-- ----------------------
-- 设置隔离级别为读未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;-- 修改A账户余额(未提交)
UPDATE account SET balance = 800 WHERE name = 'A账户';
SELECT * FROM account;  -- 查看修改后结果:A账户800-- ----------------------
-- 窗口2(事务B)
-- ----------------------
-- 设置隔离级别为读未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;-- 查询A账户余额(读到未提交的数据)
SELECT * FROM account;  -- 结果:A账户800(脏读)-- 窗口1(事务A)回滚
ROLLBACK;-- 窗口2再次查询
SELECT * FROM account;  -- 结果:A账户恢复1000(脏读数据消失)

读已提交(Read Committed):只能读取已提交的数据,避免脏读但可能出现不可重复读(一个事务内的多次查询结果不一致)。即b开启事务
进行第一次查询,此时a事务开启事务,a更改完数据后,事务提交,b开启第二次查询,查询结果不一致。

-- ----------------------
-- 窗口1(事务A)
-- ----------------------
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION; --设置读已提交的隔离级别-- 修改A账户余额并提交
UPDATE account SET balance = 800 WHERE name = 'A账户';
COMMIT;  -- 提交事务-- ----------------------
-- 窗口2(事务B)
-- ----------------------
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;-- 第一次查询(事务A未提交时)
SELECT * FROM account;  -- 结果:A账户1000-- 此时事务A已提交...-- 第二次查询(事务A已提交)
SELECT * FROM account;  -- 结果:A账户800(不可重复读)ROLLBACK;

可重复读(Repeatable Read):确保同一事务多次读取结果一致(相当于事务开始时候进行了一次数据库快照,事务内的查询的是查询开启事务时刻的数据库快照的数据),避免不可重复读但可能出现幻读(同一事务内多次查询返回不同行数)。

-- ----------------------
-- 窗口1(事务A)
-- ----------------------
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;-- 修改A账户余额并提交
UPDATE account SET balance = 800 WHERE name = 'A账户';
COMMIT;-- ----------------------
-- 窗口2(事务B)
-- ----------------------
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;-- 第一次查询
SELECT * FROM account;  -- 结果:A账户1000-- 此时事务A已提交...-- 第二次查询(结果与第一次一致)
SELECT * FROM account;  -- 结果:A账户1000(可重复读)-- 提交事务B后,才能看到A的修改
COMMIT;
SELECT * FROM account;  -- 结果:A账户800

这里需要强调下为什么会出现幻读同一事务内多次查询返回不同行数)。
虽然不可重复读读的是快照读,但是使用update/insert/delete等时,是会先当前读,也就是说的是已提交的数据,此刻的快照读会更新为当前使用update/insert/delete等时数据库的快照,所以再之后的普通读select中的快照和最开始的快照的版本是不一样的了,读的数据也就不一样了。幻读例子如下:

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;-- 插入新记录
INSERT INTO account(name, balance) VALUES ('C账户', 500);
COMMIT;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;-- 第一次查询(返回2条记录)
SELECT COUNT(*) FROM account;  -- 此时事务A插入新记录并提交...-- 第二次查询(仍返回2条记录)
SELECT COUNT(*) FROM account;  -- 执行更新操作后再次查询(出现幻读)
UPDATE account SET balance = balance+100 WHERE name = 'A账户';
SELECT COUNT(*) FROM account;  -- 返回3条记录
COMMIT;

串行化(Serializable):最高隔离级别,完全禁止并发问题,所有事务完全同步性能最低(按照一定的顺序执行)。事务A插入数据,事务B在查询时被阻塞,避免幻读。

-- 事务A
-- ----------------------
-- 窗口1(事务A)
-- ----------------------
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;-- 查询当前数据
SELECT * FROM account;  -- 结果:A账户1000,B账户1000-- ----------------------
-- 窗口2(事务B)
-- ----------------------
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;-- 插入新数据(会被事务A阻塞,直到A提交或回滚)
INSERT INTO account (name, balance) VALUES ('C账户', 1000);-- 窗口1提交事务
COMMIT;-- 窗口2的插入操作继续执行(此时事务A已提交)
-- 提交后,窗口1再次查询会看到新数据(幻读解决)

三、@Transactional注解

@Transactional注解简介

@Transactional是Spring框架提供的声明式事务管理注解,用于简化数据库事务操作。通过在方法或类上添加该注解,可以自动管理事务的开启、提交、回滚等操作,无需手动编写事务代码。

基本用法

在Spring Boot项目中,需要在启动类或配置类上添加@EnableTransactionManagement以启用事务管理功能。随后可以在服务层方法上使用@Transactional注解:

@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Transactionalpublic void createUser(User user) {userRepository.save(user);}
}

常用属性配置

@Transactional提供多个属性用于定制事务行为:

#使用注解时候默认配置如下
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,timeout = -1,readOnly = false,rollbackFor = {RuntimeException.class, Error.class}
)

自定义注解配置

@Transactional(propagation = Propagation.REQUIRED, --事务传播行为isolation = Isolation.DEFAULT, --隔离级别,数据库默认不可重读timeout = 30, --定义超时时间,超过自动给回滚readOnly = false, --不只读,可修改rollbackFor = {SQLException.class}, --遇到异常捕获后必须回滚noRollbackFor = {NullPointerException.class} --遇到NullPointerException异常时不回滚事务
)
public void updateUser(User user) {// 业务逻辑
}

事务传播行为

Spring定义了7种事务传播行为,常用选项包括:

  • REQUIRED:默认值,当前有事务则加入,没有则新建
  • REQUIRES_NEW:总是新建事务,暂停当前事务
  • NESTED:在当前事务中嵌套子事务
  • SUPPORTS:有事务则加入,没有则以非事务方式执行
  • NOT_SUPPORTED:以非事务方式执行,暂停当前事务
  • MANDATORY:必须在事务中调用,否则抛出异常
  • NEVER:不能在事务中调用,否则抛出异常

事务隔离级别

隔离级别控制事务间的可见性:

  • DEFAULT:使用数据库默认级别
  • READ_UNCOMMITTED:读未提交
  • READ_COMMITTED:读已提交
  • REPEATABLE_READ:可重复读
  • SERIALIZABLE:串行化

异常处理与回滚

默认只在运行时异常和Error时回滚,可通过以下属性调整:

  • rollbackFor:指定触发回滚的异常类型
  • noRollbackFor:指定不触发回滚的异常类型
  • rollbackForClassName:通过类名指定回滚异常
  • noRollbackForClassName:通过类名指定不回滚异常

性能优化建议

对于只读操作,建议明确设置readOnly=true

@Transactional(readOnly = true)
public User getUser(Long id) {return userRepository.findById(id);
}

避免在事务方法中进行远程调用或耗时操作,防止事务长时间占用连接。

四、 事务不生效的可能原因

在很多面试题会问事务不生效的原因, @Transactional 是基于Spring AOP实现的事务切面,所以失效本质上可以归因于AOP代理未生效或切面逻辑被阻断。Spring通过 @EnableTransactionManagement 开启事务支持,底层使用 TransactionInterceptor 拦截目标方法,生成代理对象(JDK动态代理或CGLIB代理)。只有通过代理对象调用方法时,事务切面才会生效;若直接调用目标对象(如 this.方法() ),会绕过代理,导致事务失效。

方法访问权限非public

Spring事务代理要求目标方法必须是public,若定义为private/protected/default,代理无法拦截方法调用。

@Transactional  // 失效
private void saveData() {// 操作数据库
}

自调用问题

同类中非事务方法调用事务方法,因直接调用this而非代理对象,导致拦截失效。

public class OrderService {public void createOrder() {this.saveOrder();  // 自调用,事务失效}@Transactionalpublic void saveOrder() {// 保存订单}
}

异常被捕获未抛出

spring默认仅对RuntimeException和Error回滚,若捕获异常且未重新抛出,事务管理器无法感知异常。示例:

@Transactional
public void update() {try {jdbcTemplate.update("...");} catch (DataAccessException e) {// 捕获后未抛出,事务不会回滚}
}

通过@Transactional注解指定需回滚的异常:

@Transactional(rollbackFor = IOException.class)

将受检异常转换为事务管理器可识别的类型:

catch (IOException e) {throw new RuntimeException("Wrapped exception", e);
}

数据库引擎不支持事务

如MySQL的MyISAM引擎不支持事务,需改用InnoDB。配置示例:

CREATE TABLE test (id INT PRIMARY KEY
) ENGINE=InnoDB;  // 必须为InnoDB

未启用事务管理

Spring Boot需配置@EnableTransactionManagement(默认自动开启),传统项目遗漏此注解会导致事务失效。示例缺失:

@SpringBootApplication
// 若手动配置需添加@EnableTransactionManagement
public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);}
}

特殊场景:final/static方法

代理无法增强final/static方法,导致事务失效。示例:

@Transactional
public final void process() {  // final方法失效// 业务逻辑
}

五、分布式事务考虑

对于跨服务的事务操作,@Transactional只能管理本地事务。分布式场景可考虑:

  1. Seata框架
  2. 消息队列最终一致性
  3. TCC模式
  4. SAGA模式

总结

在开发过程中遇到过在同一条件下查询出不同的数据结果,最后发现是对事务的理解使用欠缺导致。这个问题在开发过程中通过还挺常见的,不仅仅是在面对事务的时候加个@Transactional注解就完事了。
本文梳理了数据库事务的基本概念和隔离级别,同时对spring中的事务框架做了简单的介绍,觉得有用请点下赞吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/87692.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/87692.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/87692.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

替代进口SCA7606【智芯微】国产高精度电流传感器 工业新能源电网专用

SCA7606(智芯微)产品解析与推广文案一、产品概述SCA7606 是 智芯微电子(ZXMICRO) 推出的一款 高精度数字隔离式电流传感器芯片,采用 霍尔效应数字输出 技术,专为 工业控制、新能源、智能电网 等领域的电流检…

Java 与 Vue 全栈开发:“一课一得“ 学习笔记系统实战

一、项目背景与核心价值 "一课一得" 是一个面向学习者的笔记管理平台,旨在帮助用户系统化记录、整理和回顾学习内容。项目采用前后端分离架构:前端基于 Vue.js 构建交互式界面,后端使用 Java Spring Boot 实现业务逻辑&#xff0c…

百度文心大模型 4.5 开源深度测评:技术架构、部署实战与生态协同全解析

声明:本文只做实际测评,并非广告 1.前言 2025 年 6 月 30 日,百度做出一项重大举措,将文心大模型 4.5 系列正式开源,并选择国内领先的开源平台 GitCode 作为首发平台。该模型也是百度在2025年3月16日发布的自研的新一…

力扣_链表_python版本

一、206. 反转链表代码: class Solution:def reverseList(self, head):dummy ListNode()cur headwhile cur:last cur.nextcur.next dummy.nextdummy.next curcur lastreturn dummy.next二、92. 反转链表 IIclass Solution:def reverseBetween(self, head: Opt…

[netty5: WebSocketProtocolHandler]-源码分析

在阅读这篇文章前,推荐先阅读:[netty5: MessageToMessageCodec & MessageToMessageEncoder & MessageToMessageDecoder]-源码分析 WebSocketProtocolHandler WebSocketProtocolHandler 是 WebSocket 处理的基础抽象类,负责管理 Web…

[2025CVPR]一种新颖的视觉与记忆双适配器(Visual and Memory Dual Adapter, VMDA)

引言 多模态目标跟踪(Multi-modal Object Tracking)旨在通过结合RGB模态与其他辅助模态(如热红外、深度、事件数据)来增强可见光传感器的感知能力,尤其在复杂场景下显著提升跟踪鲁棒性。然而,现有方法在频…

理想汽车6月交付36279辆 第二季度共交付111074辆

理想汽车-W(02015)发布公告,2025年6月,理想汽车交付新车36279辆,第二季度共交付111074辆。截至2025年6月30日,理想汽车历史累计交付量为133.78万辆。 在成立十周年之际,理想汽车已连续两年成为人民币20万元以上中高端市…

MobileNets: 高效的卷积神经网络用于移动视觉应用

摘要 我们提出了一类高效的模型,称为MobileNets,专门用于移动和嵌入式视觉应用。MobileNets基于一种简化的架构,利用深度可分离卷积构建轻量级的深度神经网络。我们引入了两个简单的全局超参数,能够有效地在延迟和准确性之间进行…

SDP服务发现协议:动态查询设备能力的底层逻辑(面试深度解析)

SDP的底层逻辑揭示了物联网设备交互的本质——先建立认知,再开展协作。 一、SDP 核心知识点高频考点解析 1.1 SDP 的定位与作用 考点:SDP 在蓝牙协议栈中的位置及核心功能 解析:SDP(Service Discovery Protocol,服务发现协议)位于蓝牙协议栈的中间层,依赖 L2CAP 协议传…

CppCon 2018 学习:GIT, CMAKE, CONAN

提到的: “THE MOST COMMON C TOOLSET” VERSION CONTROL SYSTEM BUILDING PACKAGE MANAGEMENT 这些是 C 项目开发中最核心的工具链组成部分。下面我将逐一解释每部分的作用、常见工具,以及它们如何协同构建现代 C 项目。 1. VERSION CONTROL SYSTEM&am…

使用tensorflow的线性回归的例子(五)

我们使用Iris数据,Sepal length为y值而Petal width为x值。import matplotlib.pyplot as pltimport numpy as npimport tensorflow as tffrom sklearn import datasetsfrom tensorflow.python.framework import opsops.reset_default_graph()# Load the data# iris.d…

虚幻基础:动作——蒙太奇

能帮到你的话,就给个赞吧 😘 文章目录 动作——蒙太奇如果动作被打断,则后续的动画通知不会执行 动作——蒙太奇 如果动作被打断,则后续的动画通知不会执行

[工具系列] 开源的 API 调试工具 Postwoman

介绍 随着 Web 应用的复杂性增加,API 测试已成为开发中不可或缺的一部分,无论是前端还是后端开发,确保 API 正常运行至关重要。 Postman 长期以来是开发者进行 API 测试的首选工具,但是很多基本功能都需要登陆才能使用&#xff…

【力扣 简单 C】746. 使用最小花费爬楼梯

目录 题目 解法一 题目 解法一 int min(int a, int b) {return a < b ? a : b; }int minCostClimbingStairs(int* cost, int costSize) {const int n costSize; // 楼顶&#xff0c;第n阶// 爬到第n阶的最小花费 // 爬到第n-1阶的最小花费从第n-1阶爬上第n阶的花费…

python+django开发带auth接口

pythondjango开发带auth接口 # coding utf-8 import base64 from django.contrib import auth as django_authfrom django.core.exceptions import ObjectDoesNotExist from django.http import JsonResponsefrom sign.models import Eventdef user_auth(request):"&quo…

RBAC权限模型如何让API访问控制既安全又灵活?

url: /posts/9f01e838545ae8d34016c759ef461423/ title: RBAC权限模型如何让API访问控制既安全又灵活? date: 2025-07-01T04:52:07+08:00 lastmod: 2025-07-01T04:52:07+08:00 author: cmdragon summary: RBAC权限模型通过用户、角色和权限的关联实现访问控制,核心组件包括用…

安达发|告别低效排产:APS高级排程如何助力电池企业智造升级?

在全球能源转型的背景下&#xff0c;动力电池、储能电池等市场需求快速增长&#xff0c;电池制造企业面临着订单波动大、工艺复杂、交期严格等挑战。传统的手工排产或基于ERP的简单计划模式已难以满足高效、精准的生产需求。APS高级排程通过智能算法优化生产计划&#xff0c;实…

数据结构20250620_数据结构考试

试卷01 天津金海通软件笔试题 选择题(4*416) 对于双向循环链表,在p指针所指的结点之后插入s指针所指结点的操作应为 p->nexts; s->prip; p->next->pris; s->nextp->nextp->nexts; p->next->pris; s->prip; s->nextp->nexts->pri …

4. 寻找正序数组的中位数

题目&#xff1a; 给定两个大小分别为 m 和 n 的正序&#xff08;从小到大&#xff09;数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。 算法的时间复杂度应该为 O(log (mn)) 。 示例&#xff1a; 输入&#xff1a;nums1 [1,3], nums2 [2] 输出&#xff1a…

DeepSeek飞机大战小游戏HTML5(附源码)

用DeepSeek帮忙生成的飞机大战小游戏网页版&#xff0c;基于HTML5。 提示词prompt 帮我做一个网页版的飞机大战游戏 html5的游戏功能说明 玩家控制&#xff1a; 使用键盘方向键或WASD移动飞机 空格键发射子弹 移动设备支持触摸控制 游戏机制&#xff1a; 敌机会从屏幕顶部随机位…