深入理解数据库事务(超详细)

2023-11-20

一、事务的介绍

事务是一组操作的集合,事务会把所有操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。 

二、事务的基本操作

2.1  事务操作方式一

 例子:    转账场景(张三向李四转账)

  1. -- 1. 查询张三账户余额
  2. select * from account where name = '张三';
  3. -- 2. 将张三账户余额-1000
  4. update account set money = money - 1000 where name = '张三';
  5. -- 此语句出错后张三钱减少但是李四钱没有增加
  6. 模拟sql语句错误
  7. -- 3. 将李四账户余额+1000
  8. update account set money = money + 1000 where name = '李四';
  9. -- 查看事务提交方式
  10. SELECT @@AUTOCOMMIT;
  11. -- 设置事务提交方式,1为自动提交,0为手动提交,该设置只对当前会话有效
  12. SET @@AUTOCOMMIT = 0;
  13. -- 提交事务
  14. COMMIT;
  15. -- 回滚事务
  16. ROLLBACK;
  17. -- 设置手动提交后上面代码改为:
  18. select * from account where name = '张三';
  19. update account set money = money - 1000 where name = '张三';
  20. update account set money = money + 1000 where name = '李四';
  21. commit;

2.2  事务操作方式二

 开启事务:
  START TRANSACTION 或 BEGIN ;
提交事务:
  COMMIT;
回滚事务:
  ROLLBACK;

操作实例:

  1. start transaction;
  2. select * from account where name = '张三';
  3. update account set money = money - 1000 where name = '张三';
  4. update account set money = money + 1000 where name = '李四';
  5. commit;

2.3  实际开发的案例 

一个实际开发中常见的例子是银行系统中的转账业务。在进行资金转移时,我们需要保证转出和转入两个账户的金额变动是原子性的,要么全部成功,要么全部失败。

以下是一个示例代码片段:

// 假设有两个账户:accountA 和 accountB
String accountA = "A";
String accountB = "B";
double transferAmount = 100.0; // 转账金额

// 获取数据库连接
Connection connection = getConnection();
try {
    connection.setAutoCommit(false); // 设置手动提交事务

    // 查询账户 A 的余额
    double balanceA = queryBalance(accountA);

    // 查询账户 B 的余额
    double balanceB = queryBalance(accountB);

    if (balanceA >= transferAmount) { // 检查账户 A 的余额是否足够
        // 扣除账户 A 的金额
        updateBalance(connection, accountA, balanceA - transferAmount);

        // 增加账户 B 的金额
        updateBalance(connection, accountB, balanceB + transferAmount);

        connection.commit(); // 提交事务
        System.out.println("转账成功!");
    } else {
        System.out.println("转账失败:账户 A 余额不足!");
    }
} catch (SQLException e) {
    connection.rollback(); // 发生异常,回滚事务
    System.out.println("转账失败:" + e.getMessage());
} finally {
    connection.close(); // 关闭数据库连接
}

在上述示例中,我们使用connection.setAutoCommit(false)将自动提交事务的选项关闭,并手动控制事务的提交和回滚。当余额足够时,我们更新账户 A 和账户 B 的余额,并使用connection.commit()提交事务。如果发生异常或余额不足的情况,我们使用connection.rollback()回滚事务。

通过使用事务,我们可以确保转账过程中的数据一致性和可靠性。只有当两个账户的金额都成功更新后,才会执行事务的提交操作,否则会回滚到事务开始前的状态。

这是一个简单的示例,实际应用中可能涉及更多复杂的业务逻辑和错误处理。但这个例子展示了如何在实际开发中使用START TRANSACTIONBEGIN来管理事务,确保转账操作的一致性和可靠性。

2.4  Springboot事务的案例

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
    public void transferMoney(String fromUser, String toUser, double amount) {
        try {
            User from = userRepository.findByUsername(fromUser);
            User to = userRepository.findByUsername(toUser);

            // 检查余额是否足够
            if (from.getBalance() < amount) {
                throw new InsufficientBalanceException("Insufficient balance in the account");
            }

            // 扣除转出账户的金额
            from.setBalance(from.getBalance() - amount);
            userRepository.save(from);

            // 增加转入账户的金额
            to.setBalance(to.getBalance() + amount);
            userRepository.save(to);
        } catch (InsufficientBalanceException e) {
            // 处理余额不足的异常
            throw new TransactionFailedException("Transaction failed: " + e.getMessage());
        } catch (Exception e) {
            // 处理其他异常
            throw new TransactionFailedException("Transaction failed due to an unexpected error");
        }
    }
}

在上面的例子中,我们使用了更多的事务配置选项。@Transactional注解的propagation属性指定了事务的传播行为,这里使用的是Propagation.REQUIRED,表示如果当前没有事务,则创建一个新的事务;如果已经存在事务,则加入到当前事务中。isolation属性指定了事务的隔离级别,这里使用的是Isolation.READ_COMMITTED,表示读取已提交的数据。

在转账过程中,我们首先检查转出账户的余额是否足够,如果不足则抛出自定义的InsufficientBalanceException异常。然后,我们分别更新转出账户和转入账户的余额,并将它们保存到数据库中。如果在转账过程中发生了异常,我们会捕获并处理它们,然后抛出自定义的TransactionFailedException异常。

这个案例展示了如何在Spring Boot中使用事务来确保转账操作的原子性,并处理一些常见的异常情况。根据实际需求,你可以根据业务逻辑进行更多的定制和扩展。

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)@Transactional注解的参数配置,用于指定事务的传播行为和隔离级别。

  • propagation = Propagation.REQUIRED:表示如果当前没有事务,就创建一个新的事务;如果已经存在事务,则加入到当前事务中。这是最常用的传播行为,它确保了一组操作要么都成功要么都回滚。

  • isolation = Isolation.READ_COMMITTED:表示事务的隔离级别为"读取已提交"。在这个隔离级别下,一个事务只能读取到已提交的数据。这可以避免脏读(读取到未提交的数据)。

在上述例子中,@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)将事务的传播行为设置为默认的Propagation.REQUIRED,并将隔离级别设置为Isolation.READ_COMMITTED。这意味着每次调用transferMoney方法时,将创建一个新的事务(如果没有现有事务),并且该事务只能读取到已提交的数据。

三、事务的四大特性

  • 原子性(Atomicity):事务是不可分割的最小操作但愿,要么全部成功,要么全部失败
  • 一致性(Consistency):事务完成时,必须使所有数据都保持一致状态
  • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行
  • 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的

四、并发事务问题

问题 描述
脏读 一个事务读到另一个事务还没提交的数据
不可重复读 一个事务先后读取同一条记录,但两次读取的数据不同
幻读 一个事务按照条件查询数据时,没有对应的数据行,但是再插入数据时,又发现这行数据已经存在

常用的并发控制机制包括:

  1. 锁(Locking):使用锁来控制并发事务对数据的访问和修改,确保在某个事务读取或修改数据时,其他事务不能同时进行相同的操作。

  2. 事务隔离级别(Isolation Level):通过设置不同的事务隔离级别,定义了事务之间的可见性、并发控制的粒度等规则。

  3. 多版本并发控制(MVCC):每个事务在读取数据时,会看到一个特定的版本,而不是直接读取最新的数据。这样可以避免脏读、不可重复读和幻读问题。

  4. 时间戳排序(Timestamp Ordering):使用时间戳来对事务进行排序,根据时间戳来判断事务的执行顺序,确保事务按照正确的顺序读取和修改数据。

实际开发中,为了解决并发事务问题,需要根据具体情况选择适当的并发控制机制和事务隔离级别,并进行合理的设计和优化。同时,也需要进行充分的测试和验证,确保系统在高并发环境下依然能够保持数据的一致性和可靠性。

五、事务的隔离级别

并发事务隔离级别:

隔离级别 脏读 不可重复读 幻读
Read uncommitted(读未提交)
Read committed(读已提交) ×
Repeatable Read(可重复读)(默认的隔离级别) × ×
Serializable(串行化) × × ×
  • √表示在当前隔离级别下该问题会出现
  • Serializable 性能最低;Read uncommitted 性能最高,数据安全性最差

注意:事务隔离级别越高,数据越安全,但是性能越低。


 查看事务隔离级别:
  SELECT @@TRANSACTION_ISOLATION;
设置事务隔离级别:
  SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE };
     SESSION 是会话级别,表示只针对当前会话有效,GLOBAL 表示对所有会话有效

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入理解数据库事务(超详细) 的相关文章

随机推荐

  • C++ primer目录

    目录 第1章 快速入门 1 1 编写简单的C 程序 1 2 初窥输入 输出 1 2 1 标准输入与输出对象 1 2 2 一个使用IO库的程序 1 3 关于注释 1 4 控制结构 1 4 1 while语句 1 4 2 for 语句 1 4
  • IT痴汉的工作现状26-好项目,坏项目

    塞翁失马焉知非福 淮南子 人间训 祸兮 福之所倚 福兮 祸之所伏 老子 命运就是这样 当他给你关闭一扇门的同时也为你打开了另一扇门 同样 当他给你打开一扇门的同时也为你关闭了一扇门 有些事情 我们要用辩证的观点去看 人生如此 项目亦如此 伟
  • 数学建模——论文排版

    目录 一 参考文献的排版 1 三种方案 通常使用方案一 方案一有两种方法 2 参考文献排版要点总结 二 附录的排版 具体方法 补充 代码高亮 三 表格标题自动编号 进阶做法 四 公式编辑软件的介绍 1 LaTeX 较难 有时间可学 2 wo
  • AI会议排名_周志华

    AI会议排名 周志华 http blog sina com cn s blog 631a4cc40100xl7d html 南京大学周志华教授写的一个很经典的帖子 不过IJCAI能不能算成是no 1的会议有待商榷 不过总体还算客观 说明 纯
  • Dubbo远程传输协议详解

    前言 上次小编为大家带来了Dubbo调用及容错机制详解 不知道大家有没有去看小编最后留下的问题 欢迎对文章进行评论也希望大家和小编多多交流 今天接着为大家带来Dubbo的内容 传输协议 上次调用机制中并没有涉及Dubbo传输的协议 这次容小
  • 多线程下载文件(支持暂停、取消、断点续传)

    多线程下载文件 支持暂停 取消 断点续传 多线程同时下载文件即 在同一时间内通过多个线程对同一个请求地址发起多个请求 将需要下载的数据分割成多个部分 同时下载 每个线程只负责下载其中的一部分 最后将每一个线程下载的部分组装起来即可 涉及的知
  • 看完这篇 教你玩转渗透测试靶机Vulnhub——HarryPotter:Aragog(1.0.2)

    Vulnhub靶机HarryPotter Aragog渗透测试详解 Vulnhub靶机介绍 Vulnhub靶机下载 Vulnhub靶机安装 Vulnhub靶机漏洞详解 信息收集 漏洞发现 漏洞利用 数据库语句查询 SSH登入 备份文件提权
  • unable to install breakpoint in com... $ $FastClassBySpringCGLIB$ $12fabbfc due to missing line numb

    问题 unable to install breakpoint in com FastClassBySpringCGLIB 12fabbfc due to missing line number attributes Modify comp
  • linux定时执行shell脚本

    一 cron调度进程 c r o n是系统主要的调度进程 可以在无需人工干预的情况下运行作业 有一个叫做 c r o n t a b的命令允许用户提交 编辑或删除相应的作业 每一个用户都可以有一个c r o n t a b文件 来保存调度信
  • 量化投资学习-36:选股的基本方式

    1 选择的总原则 1 强者恒强 热点龙头 2 超跌反弹 星空雷达 2 策略总原则 1 主策略 1 2 辅策略 N 3 候选指标 趋势 支撑线 压力线 短期趋势通道 长期趋势通道 布林线 震荡 MACD底特征 KDJ震荡超卖 9转序列低9 能
  • 4大主流CPU处理器技术架构

    推荐阅读 浅谈linux 内核网络 sk buff 之克隆与复制 深入linux内核架构 进程 线程 了解Docker 依赖的linux内核技术 导读 RISC 精简指令集计算机 是一种执行较少类型计算机指令的微处理器 起源于80年代的MI
  • 【C++】STL中list容器内部元素的移动和交换

    文章目录 前言 一 list是什么 二 元素移动 1 插入 删除 2 切除 拼接 三 元素交换 1 元素值交换 2 元素 节点 交换 总结 前言 提示 list insert list erase list splice std iter
  • 【ESP32】反复重启

    ESP32开发 反复重启 串口输出如下所示 rst 0xc SW CPU RESET boot 0x13 SPI FAST FLASH BOOT configsip 188777542 SPIWP 0xee clk drv 0x00 q d
  • 使用 AJAX,局部刷新 GridView 进行数据绑定的简单实现

    很多用户都有这样需求 比如 点击按钮 刷新 GridView 中的数据 而不是这个页面刷新 使用简单的 XMLHttpRequest 就可以直接实现 具体代码如下 ASPX 代码 lt
  • C语言实现随机发纸牌

    C语言实现随机发纸牌 为避免重复发牌 设二维数组sign 4 13 记载是否发过纸牌 其中行下表表示花色 列下标表示点数 设字符串指针数组card n 存储随机发的n张纸牌 例如card 0 梅花2 按照以下方法以此发出每一张牌 首先产生一
  • Python异常捕获

    在 Python 中 try 和 except 语句用于捕获和处理异常 except 子句可以用来捕获不同类型的异常 Exception 这是 Python 中所有异常的基类 可以捕获几乎所有异常类型 ValueError 当函数收到不适当
  • 使用css 动画实现,水波纹的效果

    每日鸡汤 每个你想要学习的瞬间都是未来的你向自己求救 需求 实现水波纹动画效果 要求中心一个圆点 然后有3个圈 一圈一圈的向里面缩小 说实话我第一个想到了给3个圈设置不同的宽高 然后设置动画0 100 一次缩小宽高 但是 我转念一想 我是不
  • Intellisense and NAnt .build files in VS.NET

    Intellisense and NAnt build files in VS NET This has been blogged about before here and there but I wanted to share it a
  • 最大k乘积问题--动态规划

    问题 问题描述 设x是一个n位十进制整数 如果将x划分为k段 则可得到k个整数 这k个整数的乘积称为x的一个k乘积 试设计一个算法 对于给定的x和k 求出x的最大k乘积 编程任务 对于给定的x和k 编程计算x的最大k 乘积 示例 Sampl
  • 深入理解数据库事务(超详细)

    一 事务的介绍 事务是一组操作的集合 事务会把所有操作作为一个整体一起向系统提交或撤销操作请求 即这些操作要么同时成功 要么同时失败 二 事务的基本操作 2 1 事务操作方式一 例子 转账场景 张三向李四转账 1 查询张三账户余额 sele