事务

1. 事务概述

  • 在javaEE企业开发的应用领域,为了保证数据的完整性一致性, 必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术
  • 事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行
  • 事务的四个关键属性(ACID)
    • 原子性(atomicity): “原子”的本意是”不可再分”; 事务的原子性表现为一个事务中涉及到多个操作在逻辑上缺一不可.事务的原子性要求事务中的所有操作要么都执行, 要么都不执行
    • 一致性(consistency): “一致” 指的是数据的一致, 具体是指: 所有数据都处于满足业务规则的一致性状态. 一致性原则要求: 一个事务中不管涉及到多个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的.如果一个事物在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚
    • 隔离性(isolation): 在应用程序运行过程中, 事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事物都应该与其他事物隔离开来,防止数据损坏. 隔离性原则要求多个事务在并发执行过程中不会相互干扰
    • 持久性(durability): 持久性原则要求事务执行完成后,对数据的修改时永久的保存下来, 不会因各种系统错误或其他意外情况而受到影响. 通常情况下,事务对数据的修改应该被写入到持久化存储器中

介绍了那么多还是分不清事务的原子性和一致性有什么区别:

原子性:这个侧重点是事务执行的完整,一套事务下来,如果有一个失败,那整体失败。也就是要么大家一起成功,要么全都回滚

一致性:这个讲的是事务是按照预期生效的,也就是你举例的那个转账的,一致性的核心一部分是靠原子性实现的,而另一部分是逻辑实现。

举个例子吧: 转账:张三给李四转账100元。那数据库假设需要 张三扣100,李四加100,记录一条流水。 如果流水没记录成功,那整体回滚,张三也没转账成功,李四也没多钱。这就是原子性的体现。

而张三必须扣100,李四必须加100,这个就是一致性了,如果因为某些逻辑原因,导致张三扣了100,流水记录100转账,而李四只加了60。然后这3条操作都成功了,那原子性就符合了,但是一致性就不符合了

其实在实际应用中肯定不是这么简单的例子的。往往是类似,买东西扣库存这类的逻辑,主表里有库存,库存表里有库存,然后就因为设计缺陷,就算加了事务还是出现了主表库存对不上库存表库存的问题,这个就是一致性不满足的了。

简单理解:

  • ①A = A - 500 ② B=B+500 ①执行了,②必须执行体现了原子性,①②组成了事务
  • ①A = A - 500 ② B=B+300 如果这两行代码看成是一个事务,并且在某一时刻全执行完了,那么这个事务的原子性满足了,但却没有满足数据库的一致性。

2. 事务的并发问题

数据库事务并发问题:

​ 假设现在有两个事物: Transaction01 和 Transaction02并发执行,一个在读一个在写就会出现脏读、幻读、不可重复读的问题

脏读

​ 脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据

举个例子:

  1. Mary的原工资为1000, 财务人员将Mary的工资改为了8000(但未提交事务)

  2. Mary读取自己的工资 ,发现自己的工资变为了8000,欢天喜地!

  3. 而财务发现操作有误,回滚了事务,Mary的工资又变为了1000

像这样,Mary记取的工资数8000是一个脏数据

不可重复读

​ 是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。(行锁可以解决)

举个例子:

  1. 在事务1中,Mary 读取了自己的工资为1000,操作并没有完成
  2. 在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务
  3. 在事务1中,Mary 再次读取自己的工资时,工资变为了2000

幻读(表锁才能解决)

​ 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

举个例子:

目前工资为1000的员工有10人 1. 事务1,读取所有工资为1000的员工,记录数为10 2. 这时事务2向employee表插入了一条员工记录,工资也为1000 3. 事务1再次读取所有工资为1000的员工 共读取到了11条记录

可以参考这篇文章open in new window

说了那么多,估计还是区分不了幻读和不可重复读的区别:

  • 相同点:

    • 幻读和不可重复读都是读取了另一条已提交的事务(这点就和脏读不同)
    • 脏读是一定不能发生的
  • 不同点:

    • 不可重复读主要是说多次读取一条记录, 发现该记录中某些列值被修改过(所以行锁就可以解决 了)
    • 幻读主要是说多次读取一个范围内的记录(包括直接查询所有记录结果或者做聚合统计), 发现结果不一致(所以需要表锁)
    • 不可重复读的重点是修改: 同样的条件, 你读取过的数据,再次读取出来发现值不一样了 幻读的重点在于新增或者删除: 同样的条件, 第 1 次和第 2 次读出来的记录数不一样

3. 隔离级别

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响, 避免各种并发问题. 一个事物与其他事物隔离的程度程度成为隔离级别. SQL标准中规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度,隔离级别越高, 数据一致就越好,但并发性越弱

  • 读未提交: Read UnCommitted

    ​ 该隔离级别指即使一个事务的更新语句没有提交,但是别的事务可以读到这个改变,几种异常情况都可能出现。极易出错,没有安全性可言,基本不会使用

  • 读已提交: Read Committed

    ​ 该隔离级别指一个事务只能看到其他事务的已经提交的更新,看不到未提交的更新,消除了脏读和第一类丢失更新,这是大多数数据库的默认隔离级别,如Oracle,Sqlserver

  • 可重复读: Repeatable Read

    ​ 该隔离级别指一个事务中进行两次或多次同样的对于数据内容的查询,得到的结果是一样的,但不保证对于数据条数的查询是一样的

  • 串行话: Serializable

    ​ 意思是说这个事务执行的时候不允许别的事务并发执行.完全串行化的读,只要存在读就禁止写,但可以同时读,消除了幻读。这是事务隔离的最高级别,虽然最安全最省心,但是效率太低,一般不会用

  • 各个隔离级别解决并发问题的能力见下表

    脏读不可重复读幻读
    Read UnCommitted
    Read Committed
    Repeatable Read
    Serializable
  • 各个数据库产品对事务隔离级别的支持程度

    MySQLOracle
    Read UnCommitted×
    Read Committed
    Repeatable Read√(默认)×
    Serializable

4. 数据库中的锁

4.1 锁分类

  • 按锁的粒度划分:表级锁、行级锁、页级锁

  • 按锁级别划分:共享锁、排它锁、意向锁

  • 按加锁方式划分:自动锁、显示锁

  • 按使用方式划分:乐观锁、悲观锁