浅谈oracle数据库隔离级别

1、数据库事务介绍

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。


2、数据库并发事务出现的问题

a.脏读

脏读是指一个事务读取了未提交事务执行过程中的数据。
当一个事务的操作正在多次修改数据,而在事务还未提交的时候,另外一个并发事务来读取了数据,就会导致读取到的数据并非是最终持久化之后的数据,这个数据就是脏读的数据。
最典型的例子就是银行转账,从A账户转账100到B账户,脚本命令为

update account set money = money + 100 where username = 'B';
update account set money = money - 100 where username = 'A';

在这个事务执行过程中,另外一个事务读取结果发现B账户中的钱已经到账,提示B钱已到账,B就进行了下一步的操作。但是最终转账事务失败,导致操作回滚。实际上B并未收到钱,但是进行了下一步的操作,造成了损失,这就是脏读。

b. 不可重复读

不可重复读是指对于数据库中的某个数据,一个事务执行过程中多次查询返回不同查询结果,这就是在事务执行过程中,数据被其他事务提交修改了。
不可重复读同脏读的区别在于,脏读是一个事务读取了另一未完成的事务执行过程中的数据,而不可重复读是一个事务执行过程中,另一事务提交并修改了当前事务正在读取的数据。

c. 虚读(幻读)

幻读是事务非独立执行时发生的一种现象,例如事务T1批量对一个表中某一列列值为1的数据修改为2的变更,但是在这时,事务T2对这张表插入了一条列值为1的数据,并完成提交。此时,如果事务T1查看刚刚完成操作的数据,发现还有一条列值为1的数据没有进行修改,而这条数据其实是T2刚刚提交插入的,这就是幻读。
幻读和不可重复读都是读取了另一条已经提交的事务(这点同脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

3、SQL规范定制了四种隔离级别

◆未授权读取(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。

◆授权读取(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。

◆可重复读取(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻影数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。

◆序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

下面的表格总结了各种隔离级别和各自的缺点
在这里插入图片描述

4、项目中遇到的相关问题和解决办法

问题点:现有2个方法testA和testB。A的方法是查询余额后现成休眠10s后给用户增加100元,B方法是查询余额之后现成休眠5s给用户减200元。【在同一个时间节点上运行方法A和B】
相关代码如下:

/**
 * @author zhang on 2018/9/18.
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class SqlTest {

    private static final String ILENDID = "318790";

    private static final BigDecimal addMoney = new BigDecimal("100");

    private static final BigDecimal subtractMoney = new BigDecimal("200");

    @Autowired
    VerifyMapper mapper;

    @Test
    @Transactional
    @Rollback(false)
    public void testA() {
        //查询佣金余额
        BigDecimal icommission = mapper.queryTest(ILENDID);
        System.out.println("=============testSql icommission=============" + icommission);
        System.out.println("执行testSql之前时间:" + LocalTime.now());
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //增加100
        mapper.updateTest(ILENDID, icommission.add(addMoney));
        System.out.println("=============update icommission=============" + mapper.queryTest(ILENDID));
        System.out.println("执行testSql之后时间:" + LocalTime.now());
    }


    @Test
    @Transactional
    @Rollback(false)
    public void testB() {
        //查询佣金余额
        BigDecimal icommission = mapper.queryTest(ILENDID);
        System.out.println("~~~~~~~~~~~testSql2 icommission~~~~~~~~~~~" + icommission);
        System.out.println("执行testSql2之前时间:" + LocalTime.now());
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //开始时间
        //扣除200
        mapper.updateTest(ILENDID, icommission.subtract(subtractMoney));
        System.out.println("=============update icommission=============" + mapper.queryTest(ILENDID));
        System.out.println("执行testSql2之后时间:" + LocalTime.now());
    }
}

@Select("select icommission from tb_lend_account where ilendid = #{ilendid}")
BigDecimal queryTest(@Param("ilendid") String ilendid);

@Update("update tb_lend_account set icommission = #{money} where ilendid = #{ilendid}")
int updateTest(@Param("ilendid") String ilendid,@Param("money") BigDecimal money);

备注:由于使用的是junit测试,使用事物时默认是不改变数据库中数据,所以添加@Rollback注解
后台数据库用户当前余额:5000
运行后发现:5100
正常情况下:4900=5000+100-200
后台打印的日志:
在这里插入图片描述
理论解决方法

a:spring中@Transactional隔离级别设置成SERIALIZABLE【❌】
@Test
@Transactional(isolation = Isolation.SERIALIZABLE)
@Rollback(false)
public void testA() {
       //省略
}
//方法B同理
b:oracle悲观锁:直接使用行锁概念(for update)【✔️】
@Select("select icommission from tb_lend_account where ilendid = #{ilendid} for update")
BigDecimal queryTest(@Param("ilendid") String ilendid);
c:oracle乐观锁:使用version控制【✔️】
测试结果:楼主用的oracle数据库,测试后惊奇发现,方法a并没有实现,b和c方法可以实现。

查询相关内容才明白,Oracle采用了一种乐观的方法来实现串行化,它认为你的事务想要更新的数据不会被其他事务所更新。如果使用SERIALIZABLE隔离级别,只要保证以下几点就能很在成效。
1.一般没有其他人修改相同的数据
2.需要事务级读一致性
3.事务都很短

mysql没有测试过,有兴趣的话自己可以测试下哦~

友情提示:悲观锁建议测试使用,生产环境中推荐使用乐观锁。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值