事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);事务的四大特性:
事务是数据库的逻辑工作单位,事务中包含的所有操作要么都执行,要么都不执行。
事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。
一个事务的执行不能受其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的,接下来的其它操作或故障不应该对其执行结果有任何影响。
事务并发5类问题(如果数据库没有做任何并发处理的情况下):
第一类丢失更新:(撤销丢失)两个事务更新相同数据,如果一个事务提交,另一个事务回滚,第一个事务的更新会被回滚
脏读:第二个事务查询到第一个事务未提交的更新数据,第二个事务根据该数据执行,但第一个事务回滚,第二个事务操作脏数据
虚读(幻读):一个事务查询到了另一个事务已经提交的新数据,导致多次查询数据不一致,认可第二次读取到的数据即可
不可重复读:一个事务查询到另一个事务已经修改的数据,导致多次查询数据不一致,认可
第二类丢失更新:(覆盖丢失)多个事务同时读取相同数据,并完成各自的事务提交,导致最后一个事务提交会覆盖前面所有事务对数据的改变
一般情况,数据库都会处理一些事务并发的问题,数据库提供了不同的事务隔离级别来处理不同的事务并发问题,事务隔离级别定义如下:
READ_UNCOMMITED:允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读(相当于没有做任何事务隔离)
READ_COMMITTED:允许在并发事务已经提交后读取。可防止脏读,但幻读和 不可重复读仍可发生(Oracle 默认级别)
REPEATABLE_READ:对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生。(MySQL 默认级别)
SERIALIZABLE:完全服从 ACID 的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的。(Oracle 支持)数据库的隔离级别除了 SERIALIZABLE,都不能处理第一类丢失更新和第二类丢失更新;所以,数据库提供了锁机制来防止第一类丢失更新和第二类丢失更新;
悲观锁顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞,直到它拿到锁。
传统的关系型数据库里边就用到了很多这种锁机制,比如:行锁,表锁,读锁,写锁等,都是在做操作之前先上锁。
悲观锁指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度。因此,在整个数据处理过程中,将数据处于锁定状态。
悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
基于 JDBC 实现的数据库加锁如下:
select * from account where name="Erica" for update
乐观锁顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于 write_condition 机制的其实都是提供的乐观锁。
应用程序要实现乐观锁,我们可在对象中增加一个 version 属性保存版本信息,例如:
public class Account { private int version; public void setVersion(int version) { this.version = version; } public int getVersion() { return version; } }