jdbc-3-事务以及隔离性验证

Posted 懒佯佯大哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jdbc-3-事务以及隔离性验证相关的知识,希望对你有一定的参考价值。

事务介绍

  • 什么是事务?

    • 事务指的是由一系列操作,将系统从一个状态变化为另一个状态
  • 事务的一致性?

    • 事务的一系列操作,要么全部成功,要么全部失败,不存在中间状态,称为事务性
    • 如果出现失败,则需要通过“回滚”rollback实现事务的一致性
  • 数据一旦提交,则不可回滚

  • 数据库事务注意点:

    • DDL操作一但执行,不可回滚
      • 即无法通过auto commit控制DDL操作
    • DML操作默认为自动提交,一旦commit,不可回滚
      • 可通过set auto_commit=false关闭自动提交
    • 连接关闭后,DML操作会被执行,不论是否有设置过auto commit
    • 故:如果需要实现事务,需要由同一个链接完成
  • 注意事项:

    • 如果使用了数据库连接池,那么在连接使用完成后,需要将auto commit恢复原样。

ACID

  • 事务的ACID特性:

    • 事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
    • 原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
    • 一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相*关的。
    • 隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
    • 持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
  • 事务并发问题:

    • 脏读dirty read:无效数据的读出,一般是指由于t1事务回滚,导致t2事务读取的值停留在t1回滚之前的无效数据
    • 不可重复读non-repeatable read:t2事务开启期间,同一个查询命令却反回不同的查询结果
    • 幻读phantom read:幻读是指当事务不是独立执行时发生的一种现象。
      • 典型场景:有t1和t2两个事务(一般将autocommit置为false,便于手动控制、复现事务问题,否则一次提交事务就结束了,观察不到现象),t1和t2同时修改某一个结果集,导致结果不可控:比如t1将某一列的值+1,但是t2又新插入了一行值,那么t1和t2在提交后发现,t1会查到有一行没有+1。
    • 更新丢失lost update:
      • 第一类更新丢失,回滚覆盖:一个事务的回滚,覆盖了其它事务的提交
      • 第二类更新丢失,提交覆盖:一个事务的提交,覆盖了另一个事务的提交
  • 数据库的事务能力:

    • 数据库需要有隔离运行各个事务的能力,避免出现并发问题
    • 事务之间的隔离程度,称为隔离级别,数据库里存在多种隔离级别。
      • 但隔离级别和并发型成反比:隔离性越高(一致性越好),但隔离性越弱
  • 隔离级别:

    • 一般,选择第2、3隔离级别:READ COMMITED、REPEATABLE READ

mysql设置隔离级别的操作:

查看隔离级别
  • SHOW VARIABLES WHERE Variable_name LIKE ‘%iso%’;
  • select @@tx_isolation; 查看隔离级别:当前正在生效的隔离级别
  • select @@global.tx_isolation; 查看全局隔离级别
  • select @@session.tx_isolation;查看会话隔离级别
  • global和session区别:
    • global:
      • 范围:全局生效(包括所有session),正在运行中的session不受到影响。
      • 权限:只有超级用户才能修改
      • 查看:需要登录退出,才能看到变化
    • session:
      • 范围:当前session(一次链接、一次cmd窗口)的所有事务,重新登录/连接后失效
      • 权限:都可修改
      • 查看:立马可查到:通过@@tx_isolation或者@@
    • 省略不写时
      • 范围:表示当前session的下一次(当前事务不受影响)事务生效
      • 权限:都可修改
      • 查看:查不到
  • 注意:数据库重启后,所有的隔离设置都会失效
  • 官方文档:https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_tx_isolation
  • 注意:5.x版本时,transaction_isolation是tx_isolation的别名
    • transaction_isolation was added in MySQL 5.7.20 as an alias for tx_isolation, which is now deprecated and is removed in MySQL 8.0. Applications should be adjusted to use transaction_isolation in preference to tx_isolation. See the description of transaction_isolation for details.
mysql> SHOW VARIABLES WHERE Variable_name LIKE '%iso%';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
| tx_isolation          | REPEATABLE-READ |
+-----------------------+-----------------+
2 rows in set (0.01 sec)

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.01 sec)

mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ       |
+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ        |
+------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> mysql> select @@xx.tx_isolation;
ERROR 1272 (HY000): Variable 'tx_isolation' is not a variable component (can't be used as XXXX.variable_name)

修改隔离级别,查看生效范围—打开两个窗口
  • 语法:MySQL 提供了 SET TRANSACTION 语句,该语句可以改变单个会话或全局的事务隔离级别。语法格式如下:
    • SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE
    • 示例1:set global transaction isolation level READ UNCOMMITTED;:全局
    • 示例2:set transaction isolation level READ UNCOMMITTED;:下一次事务
  • 默认为:REPEATABLE-READ
测试修改隔离级别:— 需要开启两个事务观察
  • 测试global修改:

  • 测试session级别修改:

验证不同级别的隔离性问题:— 需要开启两个事务观察

  • READ UNCOMMITTED:

  • READ COMMITTED:

  • REPEATABLE READ:没有复现出来,有点奇怪:从官网的描述来看,在该模式下,只要读了一次,那就会形成一个副本,但是没有提及幻读:

    • 官网:https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_consistent_read
    • 幻读的场景比较复杂,涉及到数据库锁相关的知识,目前未复现出来:https://blog.csdn.net/zl1zl2zl3/article/details/90755790
    • 待后续对sql的锁进行分析后再看,目前已知的是:mysql的innodb引擎一定程度上解决了幻读的问题

java代码测试隔离性:

  • 场景:这里只列出“读未提交+脏读”场景
  • 代码:测试场景参见下面代码里的注释
package PreparedStatementTest;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import utils.ConnGetUtil;

import java.sql.*;

/**
 * 测试隔离级别
 */
public class TestIsolation 

    private Connection connection;
    private PreparedStatement preparedStatement;
    private ResultSet resultSet;

    @Before
    public void setConnection() 
        try 
            connection = ConnGetUtil.getConnection();
         catch (Exception e) 
            System.out.println("获取连接失败");
            e.printStackTrace();
        
    

    @After
    public void close() 
        ConnGetUtil.closeConnection(connection, preparedStatement, resultSet);
    

    /**
     * t1事务进程
     *  验证场景:
     *      t1用例和t2用例同时执行,重现脏读的场景:
     *          t1事务:开启事务下,先更新balance,然后回滚rollback掉
     *          t2事务:开启事务下,每隔3s查询一次balance
     *  验证结果:
     *      可以观察到t2事务的balance在变化,出现了脏读场景
     *      
     *  备注:幻读、不可重复读暂时不管
     */
    @Test
    public void t1() throws Exception 
        // 4种隔离级别:1为读未提交、2位读已提交、4为可重复读、8为串行化读
        connection.setTransactionIsolation(1);
        connection.setAutoCommit(false);
        String sql = "update user_table set balance=9999 where user='lisi'";
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.execute();
        Thread.sleep(1000 * 5); // 15s
        connection.rollback(); // 回滚
    

    /**
     * t2事务
     */
    @Test
    public void t2() throws Exception 
        // 4种隔离级别:1为读未提交、2位读已提交、4为可重复读、8为串行化读
        connection.setTransactionIsolation(1);
        connection.setAutoCommit(false);
        preparedStatement = connection.prepareStatement("select balance from user_table where user='lisi'");
        for (int i = 0; i < 10; i++) 
            Thread.sleep(1000 * 3);
            ResultSet resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) 
                int anInt = resultSet.getInt(1);
                System.out.println("查询到balance为:" + anInt);
            
        
    


  • t2事务的输出:可以看到,在事务开启之间,查到的balance值是在不断变化,出现了脏读现象
查询到balance为:3333
查询到balance为:3333
查询到balance为:9999
查询到balance为:9999
查询到balance为:9999
查询到balance为:9999
查询到balance为:9999
查询到balance为:3333
查询到balance为:3333
查询到balance为:3333

  • 参考1:mysql 5.x版本的发布文档:https://dev.mysql.com/doc/relnotes/mysql/5.7/en/
  • 参考2:https://www.jb51.net/article/182017.htm

以上是关于jdbc-3-事务以及隔离性验证的主要内容,如果未能解决你的问题,请参考以下文章

分布式事务概念以及CAP理论

数据库事务特性以及隔离级别

数据库 : 事物以及隔离性导致的问题

事务的四大特性,传播行为以及应用场景

数据库的隔离性与隔离级别以及隔离级别产生的影响

数据库事务四大特性以及事务的隔离级别