代码编织梦想

目录

一、什么是事务?

二、事务的四个特征(ACID)【面试常考项】

原子性(Atomicity)

一致性(Consistency)

隔离性(Isolation)

持久性(Durability)

三、MYSQL操作事务

1. 查看是否开启自动提交

2. 设置事务自动提交

3. 手动提交

4. 开启一条事务

5. 回滚

四、事务的并发问题

1. 隔离级别

2. 脏读

1. 打开数据库

2. 切换窗口B的隔离级别

3. 演示脏读

4. 重新设置窗口B的隔离级别

5. 再次演示脏读

3. 不可重复读

1. 切换窗口B 的隔离级别

2. 那怎么解决不可重复读的问题呢?

3. 重新设置窗口的隔离级别

4. 再次演示不可重复读

4. 幻读

1. 演示幻读

五、不可重复读和幻读的区别

六、Mybatis-Spring 配置事务

1. 环境配置

1. 使用的部分依赖,和版本

2. Mybatis-Spring 的配置文件

3. Spring 的配置文件

4. 开启事务管理

2. 使用

3. 事务的失效情况


一、什么是事务?

事务是访问并可能更新数据库中数据的一个执行单元,这个单元是由一条或多条语句组成,单元里的语句是相互依赖的。

这个单元里的SQL语句一起向系统提交,提交后要么都执行成功,要么都失败。例如:执行到一条SQL语句发生了报错,那么这个单元里已经执行成功的SQL语句也需要回滚操作,也就是返回初始状态

二、事务的四个特征(ACID)【面试常考项】

原子性(Atomicity)

原子是物理当中一种不可分割最小的单位。事务的原子性指事务是一个不可分割的最小执行单位(执行数据库操作)

一致性(Consistency)

事务必须使数据库从一个一致状态变到另一个一致状态。意味着事务完成后,所有的数据都符合预定的规则和约束。例如:转账的例子,张三给李四转账 100 元,张三的账户减去 100 元,李四的账户加 100 元,一致性就是其他事务看到的是  张三没有给李四转账,钱没有变化,张三给李四转账,张三的账户减 100 ,李四的账户加 100,不会出现 张三的账户减了 100,李四的账户没有加。

隔离性(Isolation)

隔离性意味着多个并发事务之间互不影响。即使多个事务同时进行,每一个事务都感觉像是系统中唯一的事务。也就是说,一个事务在读取数据时,不会看到其他未提交事务的更改,这避免了脏读、不可重复读和幻读等问题。

持久性(Durability)

当一个事务成功提交,数据的修改是永久性的,也就是会把数据写到物理存储中保存下来

三、MYSQL操作事务

MYSQL默认是开启事务,且一条 SQL 语句就会生成一个事务去执行,这个事务是自动提交的

1. 查看是否开启自动提交

0 表示关闭了自动提交

1 表示开启了自动提交

SELECT @@autocommit

2. 设置事务自动提交

SET @@autocommit = 0 // 关闭自动提交事务
SET @@autocommit = 1 // 开启自动提交事务

3. 手动提交

commit        手动提交

4. 开启一条事务

5. 回滚

start  transaction;        单独开启一条事务

rollback;        回滚操作

遇到报错后进行回滚

 这样数据不会被修改

四、事务的并发问题

为了避免事务的并发问题,数据库使用不同的事务隔离级别。

1. 隔离级别

在MySQL中,事务有4种隔离级别,分别为READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、REPEATABLE READ(可重复读)和SERIALIZABLE(串行化)。

隔离级别问题
READ UNCOMMITTED(读未提交)脏读
READ COMMITTED(读已提交)允许不可重复读
REPEATABLE READ(可重复读)允许幻读
SERIALIZABLE(串行化)串行化读,事务只能一个一个执行

例如,在“读已提交”(Read Committed)级别,一个事务只能读取已经提交的数据,这就防止了脏读的发生。

为了更直观的感受什么是脏读,不可重复读,幻读和串行化读,我们使用 CMD 命令打开两个窗口来进行演示

2. 脏读

事务A 读取了事务B还没有提交的数据,然后事务B 又进行了回滚操作,这时,事务A读取的数据就是脏数据。

1. 打开数据库

窗口A,窗口B 都连接数据库,然后切换你自己的数据库

2. 切换窗口B的隔离级别

MySQL默认隔离级别是REPEATABLE READ,该级别可以避免脏读。为了方便演示,把隔离级别修改为 READ UNCOMMITTED(读未提交)

// 查看隔离级别
SHOW VARIABLES LIKE 'transaction_isolation';
// 修改隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

3. 演示脏读

在窗口A 开启事务,修改张三的 money

注意:先不要提交事务

在窗口 B 查看信息

可以看到,在窗口B可以看到还未提交的数据

脏读演示好了,可以在窗口A 使用 roolback;命令回滚事务

那怎么解决脏读呢?使用 READ COMMITTED(读已提交)隔离级别可以避免脏读

4. 重新设置窗口B的隔离级别
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+----------------+
| Variable_name         | Value          |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
1 row in set, 1 warning (0.00 sec)

mysql>

5. 再次演示脏读

先在窗口B查询一下数据

mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
|       1 | zhangsan  | 123      |   900 |
|       2 | lisi      | 123      |   200 |
+---------+-----------+----------+-------+
2 rows in set (0.00 sec)

mysql>

在窗口 A 修改张三的 money

开启一个事务

mysql> rollback;
Query OK, 0 rows affected (0.01 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update user set money = 1000 where user_id = 1;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
|       1 | zhangsan  | 123      |  1000 |
|       2 | lisi      | 123      |   200 |
+---------+-----------+----------+-------+
2 rows in set (0.00 sec)

mysql>

然后在窗口 B 可以看到,没有查询到 窗口 A 未提交的数据

说明 READ COMMITTED(读已提交)隔离级别可以解决脏读的问题

演示完毕,使用 rollback;命令回滚

mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
|       1 | zhangsan  | 123      |   900 |
|       2 | lisi      | 123      |   200 |
+---------+-----------+----------+-------+
2 rows in set (0.00 sec)

mysql>

3. 不可重复读

不可重复读是指在访问数据库的数据时,一个事务对同一个数据进行多次读取时,期间其他事务可能对数据进行了更新,所以读取的结果可能不同

这通常发生在事务隔离级别为“读已提交”(Read Committed)时。

1. 切换窗口B 的隔离级别

切换 隔离级别为 READ COMMITTED(读已提交),然后开启事务,查询张三的信息

mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+----------------+
| Variable_name         | Value          |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
1 row in set, 1 warning (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where user_id = 1;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
|       1 | zhangsan  | 123      |   900 |
+---------+-----------+----------+-------+
1 row in set (0.00 sec)

mysql>

在窗口A更新张三的信息

mysql> update user set money = 2000 where user_id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
|       1 | zhangsan  | 123      |  2000 |
|       2 | lisi      | 123      |   200 |
+---------+-----------+----------+-------+
2 rows in set (0.00 sec)

mysql>

在窗口B再次查询张三的信息

可以看到,在窗口 B 一个事务的两次查询的结果不一样,其实不可重复读并不算错误,但在有些情况下却不符合实际需求。

不可重复读演示完毕,使用 COMMIT 手动提交事务

mysql> select * from user where user_id = 1;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
|       1 | zhangsan  | 123      |  2000 |
+---------+-----------+----------+-------+
1 row in set (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

2. 那怎么解决不可重复读的问题呢?

为了避免不可重复读,可以使用更高一级的事务隔离级别“可重复读”(Repeatable Read)。

在该级别下,一旦事务开始,它会为所有读取操作创建一个快照,这个快照包含了事务开始时的数据状态。

这样一来,即使有其他事务修改并提交了数据,当前事务内的读取操作总是基于开始时的快照,从而保证了数据的重复读取结果一致。

3. 重新设置窗口的隔离级别

修改窗口B的隔离级别为 REPEATABLE READ(可重复读)

mysql> SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW VARIABLES LIKE 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set, 1 warning (0.00 sec)

mysql>

4. 再次演示不可重复读

在窗口B开启一个事务,查询张三的信息

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user where user_id = 1;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
|       1 | zhangsan  | 123      |  2000 |
+---------+-----------+----------+-------+
1 row in set (0.00 sec)

mysql>

在窗口A 修改张三的信息

mysql> update user set money = 3000 where user_id = 1;
Query OK, 1 row affected (0.03 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
|       1 | zhangsan  | 123      |  3000 |
|       2 | lisi      | 123      |   200 |
+---------+-----------+----------+-------+
2 rows in set (0.00 sec)

mysql>

 再次在窗口 B 查询张三的信息

可以看到,在可重复读的隔离级别下,其他事务对数据的更新不会影响这个事务的读取

演示完毕,使用 commit; 提交事务

mysql> select * from user where user_id = 1;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
|       1 | zhangsan  | 123      |  2000 |
+---------+-----------+----------+-------+
1 row in set (0.00 sec)

mysql>

4. 幻读

指同一个事务两次读取的数据不一致,对于这个事务来说,突然多出几条数据,就好像出现了幻觉。 这是其他事务对数据进行了插入或者删除导致的

注意:可重复读 隔离级别也会发生幻读这个问题,但是一般在多个事务并发的时候才可能会出现,这里为了方便演示,设置 隔离级别为 读已提交

1. 演示幻读

在窗口 B 开启一个事务,查询数据表的所有数据

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
|       1 | zhangsan  | 123      |  3000 |
|       2 | lisi      | 123      |   200 |
+---------+-----------+----------+-------+
2 rows in set (0.00 sec)

mysql>

在窗口A 新增一条数据

mysql> insert into user value('3','test','123',1000);
Query OK, 1 row affected (0.02 sec)

mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
|       1 | zhangsan  | 123      |  3000 |
|       2 | lisi      | 123      |   200 |
|       3 | test      | 123      |  1000 |
+---------+-----------+----------+-------+
3 rows in set (0.00 sec)

mysql>

在窗口B 再次查询表中所有数据

可以看到,表中也显示了新增的数据

mysql> select * from user;
+---------+-----------+----------+-------+
| user_id | user_name | user_pwd | money |
+---------+-----------+----------+-------+
|       1 | zhangsan  | 123      |  3000 |
|       2 | lisi      | 123      |   200 |
|       3 | test      | 123      |  1000 |
+---------+-----------+----------+-------+
3 rows in set (0.00 sec)

mysql>

五、不可重复读和幻读的区别

不可重复读和幻读的本质上是一样的,两次读取到的数据不一致,但是 不可重复读 是两次读取的同一条记录的结果不一致,幻读是两次读取表中的记录数量不一致

不可重复读重点在于 UPDATE 和 DELETE,而幻读的重点在于 INSERT

六、Mybatis-Spring 配置事务

Sping 声明式事务管理,本次使用的是 Mybatis-Spring 环境

1. 环境配置

1. 使用的部分依赖,和版本
    <!-- MyBatis与Spring的集成库,用于简化MyBatis在Spring环境中的使用 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.1.2</version>
    </dependency>

    <!-- Spring的事务管理器 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.3.30</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.3.23</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.3.23</version>
    </dependency>
    
    <!-- Spring的JDBC抽象层,提供统一的数据库访问API,隐藏了数据库连接的细节 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.3.30</version>
    </dependency>
    <!-- Druid是一个数据库连接池 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.8</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.14</version>
    </dependency>
    <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
      <version>8.0.32</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.3.30</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId> <!--Spring 的ioc容器-->
      <version>5.3.30</version>
    </dependency>

2. Mybatis-Spring 的配置文件
    <!--配置数据源,druid-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/"/>
        <property name="username" value="root"/>
        <property name="password" value="sql123"/>
    </bean>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--开启事务注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager" />

3. Spring 的配置文件
    <!--开启包扫描路径-->
    <context:component-scan base-package="com"/>
    

    <!--开启自动代理-->
    <aop:aspectj-autoproxy/>

4. 开启事务管理

创建配置类,使用 @EnableTransactionManagement 注解开启 Spring 声明式事务管理 

2. 使用

@Transactional 的实现依赖于Spring AOP(面向切面编程)。当一个被@Transactional注解的方法被调用时,Spring AOP会在方法执行前后插入代理逻辑来管理事务

在 Service 层的方法上 使用 @Transactional 注解

3. 事务的失效情况

    try {
            //你的代码
        } catch (Exception e) {
            // 在捕获事务的异常后,如果对异常进行了处理,那么事务就会失效,不会回滚
            // 解决方案是 抛出异常,让上一级处理
            throw new RuntimeException(e);
        }

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/LearnTech_123/article/details/140928792

mysql 查询正在执行的事务以及等待锁 常用的sql语句-爱代码爱编程

使用navicat测试学习: 首先使用set autocommit = 0;(取消自动提交,则当执行语句commit或者rollback执行提交事务或者回滚) 在打开一个执行update 查询 正在执行的事务: SELECT * FROM information_schema.INNODB_TRX

mysql 事务_unique-you的博客-爱代码爱编程

场景构思 假设该场景发生于一个银行转账背景下,月中,又到了发工资的日子。学校打算给A老师发放一个月的工资。(此处,我们假设转账都是由人工操作的),整个过程本应该如下: 学校财务核对A老师工资单确认学校账上还有这么多钱向银行提出转账申请,银行扣除学校财务卡上的指定金额银行向A老师工资卡打入指定金额银行汇报双方交易完成但是,当这个过程执行完第3步的时候,突然大

mysql事务-爱代码爱编程

参考了MySQL的事务详解 事务详解 1、事务定义2、转账操作理解事务3、事务四大特征(ACID)4、关于事务的一些术语5、和事务相关的两条重要的SQL语句(TCL)6、事务开启的标志?事务结束的标志?7

学习笔记 -- MySQL事务并发问题的解决方式及MVCC简述-爱代码爱编程

MySQL事务并发问题的解决方式 1. 实现可重复读 实现可重复读也称为解决不可重复读问题,亦即隔离级别中的Repeatable Read隔离级别。 2. MVVC(多版本并发控制) 2.1 什么是多版本并发控制 MVCC MVCC,全称是Multi-Version Concurrency Control,即多版本并发控制。MVC

mysql笔记-事务-爱代码爱编程

1.定义: 事务是一组sql语句成的逻辑处理单元,这些操作要么全做要么全不做,是一个不可分割的工作单位。数据库默认事务时自动提交的,也就是发一条sql 它就执行一条。如果想多条sql放在一个事务中执行,则需要使用事务进行处理。当我们开启事务,并且没有提交,mysql会自动回滚事务,或者我们使用rollback命令手动回滚事务。2.事务的四大特性(ACID

【mysql】事务学习笔记_mysql结束事务语句-爱代码爱编程

文章目录 一、事务是什么?二、事务的特性1.原子性(Atomicity)2.一致性(Consistency)3.隔离性(Isolation)4.持久性(Durability) 三、事务语句四、事务隔离

mysql之事务详解_mysql事务-爱代码爱编程

1.什么是事务 事务是一个原子操作。是一个最小执行单元。可以甶一个或多个SQL语句组成在同一个事务当中,所有的SQL语句都成功执行时,整个事务成功,有一个SQL语句执行失败,整个事务都执行失败。 2.事务特性 原子性(