mysql事务隔离与spring(代码片段)

不会写代码的丝丽 不会写代码的丝丽     2023-01-21     352

关键词:

Spring学习事务的时候看到很多只读事务 和事务隔离和事务传播和事务挂起,为了更好的理解学习了mysql这块的知识。

本文用的SQL命令

#------ 设置事务隔离登记-----

#读已提交
set session transaction isolation level read committed ;
#读未提交
set session transaction isolation level read uncommitted ;
#可重复度 mysql默认级别
set session transaction isolation level repeatable read  ;
#串行化
set session transaction isolation level serializable  ;

#------ 开启事务-----

#开启可读写事务
start transaction;
#开启只读事务
start transaction read only;
#开启读写事务 与start transaction;等价
start transaction read write;


# 提交事务
commit# 事务回滚
rollback#------事务挂起-----

#记录事务保存点 mm为保存点自定义的id
savepoint mm;
#回滚事务到mm 保存点.直接运行rollback全部回滚
ROLLBACK TO mm;
#删除保存点
RELEASE SAVEPOINT mm;

# 获取事务隔离类 如 read committed等
SHOW VARIABLES LIKE 'transaction_isolation';

事务

事务个人定义:某一任务序列的集合。

举例:
小明在银行取钱的一个序列

小明 插卡 输入密码 取款金额 ATM吐纸钞退卡

上面5个步骤合在一起我们就称为一个事务。

在Mysql我们可以理解多个Sql语句的合集。

如下Sql:

# 小明给小张转账
update User set money=money-23 where username='小明';
update User set money=money+23 where username='小张';

上述两个sql语句根据固定顺序构成了一事务。

我们之所以把一定任务序列构成事务,是因为我们希望任务序列要么全部执行成功,要么不执行。

举个例子:

# 小明给小张转账
update User set money=money-23 where username='小明';
#假设只执行上面的小明的 money=money-23 但是还没执行
#小张的money=money+23语句,此时出现了异常不能执行(比如断电) money=money+23
#那么我们希望 money=money-23 语句就行回滚,就像没发生过一样,小明
#和小张金额一起没有任何变化
update User set money=money+23 where username='小张';

未解决上面的问题可以采用mysql 为我们提供事务语句解决


# 开启事务
start transaction ;
# start transaction read only; 只读事务 后续讲解

#事务开启后如果没有执行 commit 那么数据并不会真正写入数据库中,
# 但sql语句会生产临时数据(临时数据后面在引用问题,当然不会扩展到mvvc内容,主要我太菜)。

# 小明给小张转账
update User set money=money-23 where username='小明';

update User set money=money+23 where username='小张';

#提交事务
commit ;

#根据需要回滚
# rollback;

数据库只有一个,但是数据库操却并发着。比如12306有无数个请求(无数个事务)在修改数据库,那么这么多事务并发操作必然存在问题。

我们知道在事务中SQL语句在没有提交事务的时候,虽然不会最终写入数据库,但会产生临时数据,这些临时数据会被其他事务读取。

问题1: 脏读

事务读取到其他事务未提交的临时数据。

问题2: 不可重复读

事务两次读取同一记录的数据不一致。

问题3:幻读

事务A 修改/删除/更新 了一些在其他事务已经删除或者插入的数据


上图中某个事务没有id=2的数据(此时其他事务插入的),但是更新id=2为的数据居然成功。

参考:
维基百科 Isolation https://en.wikipedia.org/wiki/Isolation_(database_systems)

针对事务并发导致的几个问题,数据库innodb提供几个事务隔离策略让我们针对上诉的问题针对性解决。read committed、read uncommitted、 repeatable read、serializable。

read uncommitted 读未提交

此隔离模式会导致事务可以读取其他未提交的事务的临时数据。
假设B事务修改了User表的小李的Age=20字段 但未提交事务,此时A事务将读取到小李的Age字段等于20。

模拟环境如下:
A事务、 B事务。
表结构:

-- auto-generated definition
create table User
(
    id       int auto_increment
        primary key,
    username varchar(255) null,
    birthday date         null,
    sex      varchar(255) null,
    address  varchar(255) null,
    money    int          null
);

A事务执行任务如下:


可见此隔离无法阻止脏读,同理可证明无法阻止幻读和不可重复度问题。

这在注意一个问题,事务B此时对User表id为1的数据进行了行锁。其他事物如果需要修改id为1的数据需要事务B提交事务才可。

read committed 读已提交

可以读取其他事务已提交的数据,对于其他未提交的事务的临时数据是无法查询。

此处可证明read committed能解决数据脏读问题。但对于幻读可重复读无法解决。

repeatable read 可重复读

多次重复读取可保持一致性

事务A修改id=1的数据时会自动同步B事务提交id=1新数据(假设事务B同时修改id=2id=1的数据,此时也不会刷新id=2的新数据,只是刷新id=1)



再次提一个小心点可重复读,修改一个数据时,此数据行若被其他事务修改,那么以其他事物已提交的数据为数据源做修改。

同理事务B插入新的数据行,事务A同样无法查询到。除非事务A使用update更新整个表。

但可重复读无法避免幻读现象

事务A中查询不到id=2的数据,然后执行更新id=2操作居然成功了

serializable 串行执行

一个事务只能等候另一个事务执行完成了才可以使用。

此隔离等级可解决幻读 重复读 脏读问题,但效率过差

只读事务

只读事务是指建立在四种隔离机制之上的。表示当前事务只能对表进行读取操作,其他删除/更新/插入会导致锁异常。

如执行以下下SQL语句

set session transaction isolation level read committed ;

start transaction read only ;

#不能执行
#insert User(id,username) values (2,'小帅');

#不能执行
#update  User set username=concat(username,' 放学') ;



#不能执行
#delete from User;

#因为设置了 read committed ; 查询其他事务已经提交的数据,和原来无差别
select *
from User;

commit ;

start transaction read only ;开启可读事务,mysql会对其进行优化处理 。但事务内部查询可见性依然由事务隔离级别决定。

事务的保存点

有时候我们希望在事务执行时可以控制部分区域回滚,可以让我们更细腻化的控制的。

其他命令这里直接给出结论读者可以自己进行验证。存在多个存储点时,执行rollback 将回滚全部,而rollback to XX 会回滚到指定点。RELEASE SAVEPOINT xx; 删除指定存储点位置 后面无法执行此idrollback to XX

Tip:Spring事务传播NESTED类型就是使用Savepoint

事务嵌套问题

事务开启后无法变更隔离级别,包含事务保存点savepoint之后调用set session transaction isolation level xxxx ;也无法更改

执行以下sql文件

set session transaction isolation level read uncommitted;
start transaction;

SHOW VARIABLES LIKE 'transaction_isolation';#返回read uncommitted

select *
from User;

#想修改为 读已提交
set session transaction isolation level read committed ;

SHOW VARIABLES LIKE 'transaction_isolation'; #虽然返回read committed 但是依然是read uncommitted;

#结果还是read uncommitted;
select *
from User;

#想修改为 读已提交
#并且在一次开始 依然没用
set session transaction isolation level read committed ;
start transaction;

#结果还是read uncommitted;
select *
from User;

commit;

效果图

Spring事务传播

事务类型说明
PROPAGATION_REQUIRED如果当前存在事务,那么使用当前事务,自身事务配置将 不生效,如果不存在使用自身配置信息创新事务
PROPAGATION_SUPPORTS如果当前存在事务,那么使用当前事务,如果没有那么以非事务运行
PROPAGATION_MANDATORY支持(使用)当前事务,如果不存在事务抛出异常
PROPAGATION_REQUIRES_NEW如果当前存在事务,则挂起当前事务,然后开启自身配置事务
PROPAGATION_NOT_SUPPORTED以非事务的方式运行,如果有事务存在,则挂起当前事务
PROPAGATION_NEVER已非事务的方式运行,如果有事务存在,则抛出异常
PROPAGATION_NESTED如果当前事务存在,则嵌套事务执行 使用savePoint(保存点)技术

介绍几个关键API:

 DataSourceUtils.getConnection(dataSources)

获取当前线程的数据源的Connection对象

PROPAGATION_REQUIRED和PROPAGATION_REQUIRES_NEW

//TestTx.java

@Component
public class TestTx 

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    DataSource dataSource;

    /**
     * 事务隔离级别为 读取已提交事务的数据
     * 传播级别:PROPAGATION_REQUIRED 如果当前事务存在事务那么自身事务配置不生效,直接加入到现有事务中
     * 如果没有事务那么使用自身的事务隔离级别创建事务
     */
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public void query() 

        List<User> query = jdbcTemplate.query("select * from User", new BeanPropertyRowMapper(User.class));

        System.out.println("TestTx ::" + query);

        print("TestTx.query", dataSource);
    
    public void print(String prefix, DataSource dataSources) 

        try 
            System.out.println("\\n\\n" + prefix + " : \\n" +
                    "getTransactionIsolation : " + DataSourceUtils.getConnection(dataSources).getTransactionIsolation() +
                    "\\nconn : " + DataSourceUtils.getConnection(dataSources) + "\\n\\n");
         catch (SQLException e) 
            e.printStackTrace();
        
	

    
//TestTx2.java
@Component
public class TestTx2 

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    DataSource dataSource;


    /**
     * 事务隔离级别:读取已经提交事务的数据
     * 传播:如果存在事务那么挂起,开启自己的新事务
     */
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
    public void query() 

        List<User> query = jdbcTemplate.query("select * from User", new BeanPropertyRowMapper(User.class));

        System.out.println("TestTx2::" + query);

        print("TestTx2.query", dataSource);
    
      public void print(String prefix, DataSource dataSources) 

        try 
            System.out.println("\\n\\n" + prefix + " : \\n" +
                    "getTransactionIsolation : " + DataSourceUtils.getConnection(dataSources).getTransactionIsolation() +
                    "\\nconn : " + DataSourceUtils.getConnection(dataSources) + "\\n\\n");
         catch (SQLException e) 
            e.printStackTrace();
        

    

测试一

//TestBean.java
@Component
public class TestBean 

    @Autowired
    TestTx testTx;
    @Autowired
    TestTx2 testTx2;


    /**
     * 开启事务 可以读取一些未提交的事务数据
     */
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public void testOne() 
        try 
        	//#22行
            testTx.query();
            //#24行
            testTx.query();
            //#26行
            testTx2.query();

         catch (Exception e) 
            e.printStackTrace();
        
    

testOne方法输出:

TestTx ::[Userid=688022, userName='新插入未提交', birthday=null, sex='null', address='null', Userid=688024, userName='张三', birthday=null, sex='null', address='null', Userid=688025, userName='张三', birthday=null, sex='null', address='null']


TestTx.query : 
getTransactionIsolation : 1
conn : com.mchange.v2.c3p0.impl.NewProxyConnection@7d32c62e [wrapping: com.mysql.cj.jdbc.ConnectionImpl@59ffa66d]


TestTx ::[Userid=688022, userName='新插入未提交', birthday=null, sex='null', address='null', Userid=688024, userName='张三', birthday=null, sex='null', address='null', Userid=688025, userName='张三', birthday=null, sex='null', address='null']


TestTx.query : 
getTransactionIsolation : 1
conn : com.mchange.v2.c3p0.impl.NewProxyConnection@7d32c62e [wrapping: com.mysql.cj.jdbc.ConnectionImpl@59ffa66d]


TestTx2::[Userid=688024, userName='张三', birthday=null, sex='null', address='null', Userid=688025, userName='张三', birthday=null, sex='null', address='null']


TestTx2.query : 
getTransactionIsolation : 2
conn : com.mchange.v2.c3p0.impl.NewProxyConnection@73f75fec [wrapping: com.mysql.cj.jdbc.ConnectionImpl@4d021f9e]

输出分析:
id=688022, userName='新插入未提交', birthday=null, sex='null', address='null'为其他事务未提交的脏数据

结论一:
TestBean.java22行24行testTx.query();打印出相同的查询结果,且能读取到脏数据。证明自己的事务TestTxquery方法配置Isolation.READ_COMMITTED没有生效,而是采用TestBeantestOne方法的READ_UNCOMMITTED

结论二:
TestBean.java22行24行testTx.query();打印出相同Connection对象。加入其他事务的时候会使用同一Connection对象。

结论三:
TestBean.java的26行testTx2.query();没有打印出id=688022, userName='新插入未提交', birthday=null, sex='null', address='null'证明自身的Isolation.READ_COMMITTED生效。

结论四:
TestBean.java的26行testTx2.query();打印的Connection对象与 22行24行testTx.query();不相同。证明不同一个事务Connection对象不同.


测试二

@Component
public class TestBean 

    @Autowired
    TestTx testTx;
    @Autowired
    TestTx2 testTx2;

    /**
     * 开启事务 可以读取一些未提交的事务数据
     */
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public void testOne() 
        try 
            testTx.query();
            testTx.query();
            testTx2.query();

         catch (Exception e) 
            e.printStackTrace();
        
    


    /**
     * 与测试一不同的是 自身没有开启事务
     */
    public void testTwo() 
        try 
            testTx.query();
            testTx.query();
            testTx2.query();

         catch (Exception e) 
            e.printStackTrace();
        
    


testTwo输出:

TestTx ::[Userid=688024, userName='张三', birthday=null, sex='null', address='null', Userid=688025, userName='张三', birthday=null, sex='null', address='null']


TestTx.query : 
getTransactionIsolation : 2
conn : com.mchange.v2.c3p0.impl.NewProxyConnection@7d32c62e [wrapping: com.mysql.cj.jdbc.ConnectionImpl@59ffa66d]


TestTx ::[Userid=688024, userName='张三', birthday=null, sex='null', address='null', Userid=688025, userName='张三', birthday=null, sex='null', address='null']


TestTx.query : 
getTransactionIsolation : 2
conn : com.mchange.v2.c3p0.impl.NewProxyConnection@7577863f [wrapping: com.mysql.cj.jdbc.ConnectionImpl@73f75fec]


TestTx2::[Userid=688024, userName='张三', birthday=null, sex='null', address='null', Userid=688025, userName='张三', birthday=null, sex='null', address='null']


TestTx2.query : 
getTransactionIsolation : 2
conn : com.mchange.v2.c3p0.impl.NewProxyConnection@2a4e842f [wrapping: com.mysql.cj.jdbc.ConnectionImpl@73f75fec]

结论一:
三者使用不同Connection对象,且使用自己的隔离级别创建事务。

测试三

关于PROPAGATION_REQUIRES_NEW会把原事务挂起,PROPAGATION_REQUIRED会加入现有事务的测试说明。



@Component
public class TestTx 

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    DataSource dataSource;

    /**
     * 添加用户
     * 传播级别:PROPAGATION_REQUIRED 如果当前事务存在事务那么自身事务配置不生效(readonly=true也会失效 一样可以插入,
     * isolation跟随当前事务 自身也失效,此处可以参考 之前查询测试),直接加入到现有事务中.
     * 如果没有事务那么使用自身的事务隔离级别创建事务
     *
     * @param user
     */
    @Transactional(isolation = Isolation.READ_UNCOMMITTED, propagation = Propagation.REQUIRED, readOnly = true)
    public void addUser(User user) 

        jdbcTemplate.update("insert into User( username)  values (?) ", user.getUserName());

        print("TestTx.addUser", dataSource);

    

    @Transactional(isolation = Isolation.READ_UNCOMMITTED, propagation = Propagation.REQUIRED)
    public void addUserWithExeption(User user) 
        jdbcTemplate.update("insert into User(username)  values (?) ", user.getUserName());
        print("TestTx.addUserWithExeption", dataSource);
 		int i = 1 / 0;
    

    /**
     * 事务隔离级别为 读取已提交事务的数据
     * 传播级别:PROPAGATION_REQUIRED 如果当前事务存在事务那么自身事务配置不生效,直接加入到现有事务中
     * 如果没有事务那么使用自身的事务隔离级别创建事务
     */
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public void query() 

        List<User> query = jdbcTemplate.query("select * from User", new BeanPropertyRowMapper(User.class));

        System.out.println("TestTx ::" + query);

        print("TestTx.query", dataSource);
    

    public void print(String prefix, DataSource dataSources) 

        try 
            System.out.println("\\n\\n" + prefix + " : \\n" +
                    "getTransactionIsolation : " + DataSourceUtils.getConnection(dataSources).getTransactionIsolation() +
                    "\\nconn : " + DataSourceUtils.getConnection(dataSources) + "\\n\\n");
         catch (SQLException e) 
            e.printStackTrace();
        

    



测试类

//TestBean.java
@Component
public class TestBean 

    @Autowired
    TestTx testTx;


    /**
     * 与测试一不同的是 自身没有开启事务
     */
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public void testThree() throws Exception 


        testTx.addUser(new User("李四"));

        testTx.addUserWithExeption(new User("王五"));


    

这里直接说结论 李四 王五都没有插入。两种使用同一个Connection

捕获回滚异常问题,请看下图

//TestB

事务与mysql隔离级别(代码片段)

事务定义:比如ABCD四个业务,作为一个事务,他们要么一起都执行完毕,要么都不执行。(只要有一个不成功,那么所有的都不可以成功)四个特性ACID原子性(Atomicity)整个事务中的所有操作,要么全都完成,要么全部不完成。事务... 查看详情

mysql事务管理(代码片段)

文章目录CURD什么是事务为什么会出现事务事务的版本支持事务提交方式事务常见操作方式事务隔离级别理解隔离性查看与设置隔离性隔离级别读未提交【ReadUncommitted】读提交【ReadCommitted】可重复读【RepeatableRead】串行化【Serializ... 查看详情

mysql高级篇——事务的隔离级别与简单应用(代码片段)

...发所存在的问题1.1脏写1.2脏读1.3不可重复读1.4幻读2.SQL中事务的隔离级别3.案例实操3.1查看与修改MySQL的隔离级别3.2读未提交-举例3.3 读已提交-举例3.4 可重复读-举例3.5 幻读-举例1.数据并发所存在的问题针对事务的隔离性和并发... 查看详情

mysql———事务和事务的隔离性(代码片段)

文章目录事务的ACID隔离与隔离级别事务隔离的实现事务的启动方式事务的启动时机:两种操作的“读”!  事务在现阶段数据库中很常见,因为现版本InnoDB代替了MySQL的原生存储引擎MyISAM,这与它不支持事务有很大... 查看详情

mysql的事务事务的隔离级别事务的保存点(代码片段)

文章目录💨往期精彩知识👇一、什么是事务?二、事务的开启关闭提交操作三、事务的保存点四、事务的特性五、事务的隔离级别作者:KJ.JK💨往期精彩知识👇💖Spring中的创建对象的三种方式、第三... 查看详情

mysql事务隔离(代码片段)

阅读目录事务的介绍隔离级别1READUNCOMMITTED(未提交读)2READCOMMITTED(提交读)3REPEATABLEREAD(可重复读)SERIALIZABLE(可串行化)事务隔离的实现事务启动的方式事务的介绍事务就是一组原子性的sql查询,或者说是一个独立的工作单元。简... 查看详情

mysql事务详解(代码片段)

🏆今日学习目标:🍀Spring事务和MySQL事务详解✅创作者:林在闪闪发光⏰预计时间:30分钟🎉个人主页:林在闪闪发光的个人主页 🍁林在闪闪发光的个人社区,欢迎你的加入: 林在闪闪发光... 查看详情

mysql事务(代码片段)

文章目录二、MySQL事务2.1、事务的概念2.2、事务的ACID特点①原子性(Atomicity)②一致性(Consistency)③隔离性(Isolation)ⅰ查询全局事务隔离级别:ⅱ查询会话事务隔离级别ⅲ设置全局事务隔离级别ⅳ... 查看详情

mysql事务(代码片段)

文章目录二、MySQL事务2.1、事务的概念2.2、事务的ACID特点①原子性(Atomicity)②一致性(Consistency)③隔离性(Isolation)ⅰ查询全局事务隔离级别:ⅱ查询会话事务隔离级别ⅲ设置全局事务隔离级别ⅳ... 查看详情

mysql的事务和引擎,注意细品(代码片段)

mysql事务和引擎一、MySQL事务(一)、MySQL事务的概念(二)、事务的ACID特点1、ACID特点2、数据不一致产生的结果:(三)、事务的隔离1、MySQL事物隔离级别1.1查询全局事务隔离级别1.2查询会话事务隔离级别1.3设... 查看详情

mysql事务隔离级别(代码片段)

MySQL的事务必须满足A(原子性)C(一致性)I(隔离性)D(持久性)原则。其中,隔离性是为了尽量减少并发事务彼此之间的影响,最高的隔离级别可以保证并发事务之间互不影响。  在... 查看详情

面试官问:mysql锁与事物隔离级别你知道吗?(代码片段)

...法、MySQL性能优化篇一些内容。我们再来聊聊MySQL的锁与事务隔离级别,分上下两篇,本篇重点讲MySQL的行锁与事务隔离级别。锁定义锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除了传统的计算资源(... 查看详情

mysql高级篇——事务的隔离级别与简单应用(代码片段)

...发所存在的问题1.1脏写1.2脏读1.3不可重复读1.4幻读2.SQL中事务的隔离级别3.案例实操3.1查看与修改MySQL的隔离级别3.2读未提交-举例3.3 读已提交-举例3.4 可重复读-举例3.5 幻读-举例1.数据并发所存在的问题针对事务的隔离性和并发... 查看详情

mysql事物隔离级别及搜索引擎(代码片段)

...事物隔离级别及搜索引擎一.MySQL事物隔离级别1.1查询全局事务隔离级别1.2查询会话事务隔离级别1.3设置全局事务隔离级别1.4设置会话事务隔离级别二.事务控制语句三.MySQL存储引擎3.1存储引擎概念介绍3.2MySQL常用的存储引擎:3.3lnnoD... 查看详情

netcore中数据库事务隔离详解——以dapper和mysql为例(代码片段)

原文:NetCore中数据库事务隔离详解——以Dapper和Mysql为例NetCore中数据库事务隔离详解——以Dapper和Mysql为例事务隔离级别准备工作Readuncommitted读未提交Readcommitted读取提交内容Repeatableread(可重读)Serializable序列化总结事务隔离级别.NE... 查看详情

mysql事务隔离级别(代码片段)

...别可以通过MySQL的视图来实现。读未提交读未提交是一个事务仅修改了数据但还未提交时,本次修改可以便可被其他事务查询到变更后的值。读未提交隔离级别下,其他事务进行查询时,直接返回记录上的最新值,... 查看详情

面试官:说一下mysql事务隔离级别?(代码片段)

MySQL事务隔离级别是为了解决并发事务互相干扰的问题的,MySQL事务隔离级别总共有以下4种:READUNCOMMITTED:读未提交。READCOMMITTED:读已提交。REPEATABLEREAD:可重复读。SERIALIZABLE:序列化。1.四种事务隔离级... 查看详情

mysql事务(代码片段)

文章目录MySQL事务一、事务的两种操作1.手动提交事务2.自动提交事务二、事务四大特性三、事务隔离级别MySQL事务一、事务的两种操作1.手动提交事务语句功能starttransaction;或者BEGIN;开启事务commit;提交事务rollback;回滚事务手动提... 查看详情