事务是最小的逻辑执行单元,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另外一种一致性状态。数据库
事务具备四个重要特性,即原子性(Atomicity)、一致性(Consistency)、隔离性 (Isolation)和持久性 (Durability)。安全
本文首先叙述了数据库中事务的本质及其四大特性(ACID)的内涵,而后重点介绍了事务隔离性的动机和内涵,并介绍了数据库为此所提供的事务隔离级别以及这些事务隔离级别能解决的事务并发问题。介于并发安全与并发效率的平衡,咱们通常不会一味地提升事务隔离级别来保证事务并发安全性,而是经过结合其余机制(包括笔者提到的乐观锁和悲观锁机制)来解决数据库事务并发问题。bash
通常而言,用户的每次请求都对应一个业务逻辑方法,而且每一个业务逻辑方法每每具备逻辑上的原子性。此外,一个业务逻辑方法每每包括一系列数据库原子访问操做,而且这些数据库原子访问操做应该绑定成一个总体,即要么所有执行,要么所有不执行,经过这种方式咱们能够保证数据库的完整性。也就是说,事务是最小的逻辑执行单元,是数据库维护数据一致性的基本单位。session
总的来讲,事务是一个不可分割操做序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另外一种一致性状态。多线程
原子性(Atomicity)并发
原子性是指事务包含的全部操做要么所有成功,要么所有失败回滚。 所以,事务的操做若是成功就必需要彻底应用到数据库,若是操做失败则不会对数据库有任何影响,也就是说,事务是应用中不可再分的最小逻辑执行体。性能
一致性(Consistency)ui
**一致性是指事务执行的结果必须使数据库从一种一致性状态变到另外一种一致性状态,也就是说,一个事务执行以前和执行以后数据库都必须处于一致性状态。**拿转帐来讲,假设用户A和用户B二者的钱加起来一共是5000,那么无论A和B之间如何转帐,转几回帐,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。spa
隔离性 (Isolation) — 与事务并发直接相关hibernate
隔离性是指并发执行的事务之间不能相互影响。也就是说,对于任意两个并发的事务 T1 和 T2,在事务 T1 看来,T2 要么在 T1 开始以前就已经结束,要么在 T1 结束以后才开始,这样每一个事务都感受不到有其余事务在并发地执行。
持久性 (Durability)
**持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即使是在数据库系统遇到故障的状况下也不会丢失提交事务的操做。**换句换说,事务一旦提交,对数据库所作的任何改变都要记录到永久的存储器中(一般就是保存到物理数据库)。
以上介绍完了事务的基本概念及其四大特性(简称ACID),如今重点来讲明下事务的隔离性。**咱们知道,当多个线程都开启事务操做数据库中的数据时,数据库系统要能进行隔离操做以保证各个线程获取数据的准确性。**也就是说,事务的隔离性主要用于解决事务的并发安全问题,那么事务的隔离性解决了哪些具体问题呢?
一、事务并发带来的问题
脏读
**脏读是指在一个事务处理过程当中读取了另外一个事务未提交的数据。**好比,当一个事务正在屡次修改某个数据,而当这个事务对数据的修改还未提交时,这时一个并发的事务来访问该数据,就会形成数据的脏读。看下面的例子:
公司发工资了,领导把5000元打到singo的帐号上,可是该事务并未提交,而singo正好去查看帐户,发现工资已经到帐,是5000元整,很是高兴。但是不幸的是,领导发现发给singo的工资金额不对,是2000元,因而迅速回滚了事务,修改金额后,将事务提交,最后singo实际的工资只有2000元,singo空欢喜一场。
**出现的上述状况就是咱们所说的脏读,即对于两个并发的事务(事务A:领导给singo发工资、事务B:singo查询工资帐户),事务B读取了事务A还没有提交的数据。**特别地,当隔离级别设置为 Read Committed 时,就能够避免脏读,可是仍可能会形成不可重复读。特别地,大多数数据库的默认级别就是Read committed,好比Sql Server , Oracle。
不可重复读
**不可重复读是指:对于数据库中的某个数据,一个事务范围内屡次查询却返回了不一样的数据值,这是因为在查询间隔该数据被另外一个事务修改并提交了。**例如,事务 T1 在读取某一数据,而事务 T2 立马修改了这个数据而且提交事务,当事务T1再次读取该数据就获得了不一样的结果,即发生了不可重复读。**不可重复读和脏读的区别是,脏读是某一事务读取了另外一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。**看下面的例子:
singo拿着工资卡去消费,系统读取到卡里确实有2000元,而此时她的老婆也正好在网上转帐,把singo工资卡的2000元转到另外一帐户,并在singo以前提交了事务,当singo扣款时,系统检查到singo的工资卡已经没有钱,扣款失败,singo十分纳闷,明明卡里有钱,为什么……
上述状况就是咱们所说的不可重复读,即两个并发的事务(事务A:singo消费、事务B:singo的老婆网上转帐),事务A事先读取了数据,事务B紧接着更新了数据并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。当隔离级别设置为Repeatable read时,能够避免不可重复读。这时,当singo拿着工资卡去消费时,一旦系统开始读取工资卡信息(即事务开始),singo的老婆就不可能对该记录进行修改,也就是singo的老婆不能在此时转帐。特别地,MySQL的默认隔离级别就是 Repeatable read。
幻读
**幻读是事务非独立执行时发生的一种现象,即在一个事务读的过程当中,另一个事务可能插入了新数据记录,影响了该事务读的结果。**例如,事务 T1 对一个表中全部的行的某个数据项执行了从“1”修改成“2”的操做,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值仍是为“1”而且提交给数据库。这时,操做事务 T1 的用户若是再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉同样,这就是发生了幻读。**幻读和不可重复读都是读取了另外一条已经提交的事务(这点与脏读不一样),所不一样的是不可重复读查询的都是同一个数据项,而幻读针对的是数据记录插入/删除问题,两者关注的问题点不太相同。**看下面的例子:
singo的老婆工做在银行部门,她时常经过银行内部系统查看singo的信用卡消费记录。有一天,她正在查询到singo当月信用卡的总消费金额为80元,而singo此时正好在外面胡吃海塞后在收银台买单,消费1000元,即新增了一条1000元的消费记录并提交了事务,随后singo的老婆将singo当月信用卡消费的明细打印到A4纸上,却发现消费总额为1080元,singo的老婆很诧异,觉得出现了幻觉,幻读就这样产生了。
当隔离级别设置为Serializable(最高的事务隔离级别)时,不只能够避免脏读、不可重复读,还能够避免幻读。但同时代价也花费最高,性能很低,通常不多使用,由于在该级别下并发事务将串行执行。
二、小结
总的来讲,事务的隔离性主要用于解决事务并发安全问题。上面提到的脏读、不可重复读和幻读三个典型问题都是在事务并发的前提下发生的,不一样的是三者的问题关注点略有不一样。
更进一步地说,不可重复读关注的是数据的更新带来的问题,幻读关注的是数据的增删带来的问题。
不一样数据库的事务隔离级别不尽相同。好比咱们在上一节提到,MySQL数据库支持下面的四种隔离级别,而且默认为 Repeatable read 级别;而在Oracle数据库中,只支持Serializable 级别和 Read committed 这两种级别,而且默认为 Read committed 级别。MySQL数据库为咱们提供了四种隔离级别,分别为:
从上图中能够看出,**以上四种隔离级别中最高的是 Serializable级别,最低的是 Read uncommitted级别。**固然,隔离级别越高,事务并发就越安全,但执行效率也就越低。好比,Serializable 这样的级别就是以锁表的方式(相似于Java多线程中的锁)保证并发事务的串行执行,但这时执行效率也降到了最低,因此,选用何种隔离级别实质上是一种并发安全与并发效率的平衡,应该根据实际状况而定。特别地,在MySQL数据库中,默认的事务隔离级别为 Repeatable read(可重复读),下面咱们看看如何在MySQL数据库中操做事务的隔离级别。
MySQL默认事务隔离级别查看
在MySQL数据库中,咱们能够经过如下方式查看当前事务的隔离级别:
select @@tx_isolation;
复制代码
MySQL事务隔离级别修改
在MySQL数据库中,咱们能够分别经过如下两种方式设置事务的隔离级别,分别为:
set [glogal | session] transaction isolation level 隔离级别名称;
复制代码
或
set tx_isolation='隔离级别名称';
复制代码
使用JDBC对设置数据库事务的隔离级别
设置数据库的隔离级别必定要是在开启事务以前。特别地,使用JDBC对数据库的事务设置隔离级别时,咱们应该在调用Connection对象的setAutoCommit(false)方法以前调用Connection对象的setTransactionIsolation(level)去设置当前连接的隔离级别以下所示:
至于参数level,可使用Connection接口的字段,如如下代码所示:
特别地,经过这种方式设置事务隔离级别只对当前连接有效。**对于使用MySQL命令窗口而言,一个窗口就至关于一个连接,当前窗口设置的隔离级别只对当前窗口中的事务有效;**对于JDBC操做数据库来讲,一个Connection对象至关于一个连接,而对于Connection对象设置的隔离级别只对该Connection对象有效,与其余连接Connection对象无关。
一、乐观锁
乐观锁,虽然名字中带“锁”,可是乐观锁并不锁住任何东西,而是在提交事务时检查这条记录是否被其余事务进行了修改:若是没有,则提交;不然,进行回滚。相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。若是并发的可能性并不大,那么乐观锁定策略带来的性能消耗是很是小的。乐观锁采用的实现方式通常是记录数据版本。
二、悲观锁
**悲观锁,正如其名,它指的是对数据被外界修改持保守(悲观)态度,所以,在整个数据处理过程当中,将数据处于锁定状态。悲观锁的实现每每依靠数据库提供的锁机制,也只有数据库层提供的锁机制才能真正保证数据访问的排他性,不然即便在本系统中实现了加锁机制,也没法保证外部系统不会修改数据。**悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。和乐观锁相比,悲观锁则是一把真正的锁了,它经过SQL语句“select for update”锁住select出的那批数据,这时若是其余事务来更新这批数据时会等待。
三、小结
悲观锁和乐观锁都是一种解决并发控制问题的思想。特别地,在数据库并发控制方面,悲观锁与乐观锁有如下几点区别:
总的来讲,悲观锁相对乐观锁更安全一些,可是开销也更大,甚至可能出现数据库死锁的状况,建议只在乐观锁没法工做时才使用。