一、 什么是事务
1.1. 概念
事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列操作。这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行 。事务是一个
不可分割的工作逻辑单元。
事务是现代数据库理论中的核心概念之一。如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务。当所有的步骤像一个
操作一样被完整地执行,我们称该事务被提交。由于其中的一部分或多步执行失败,导致没有步骤被提交,则事务必须回滚到最初的系统状态。
1.2. ACID原则
事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)的缩
写。
Ø原子性(Atomicity):事务是一个完整的操作。事务的各步操作是不可分的(原子的);要么都执行,要么都不执行。
Ø一致性(Consistency):当事务完成时,数据必须处于一致状态 。
Ø隔离性(Isolation):对数据进行修改的所有并发事务是彼此隔离的,这表明事务必须是独立的,它不应以任何方式依赖于或影响其他事务
Ø永久性(Durability):事务完成后,它对数据库的修改被永久保持,事务日志能够保持事务的永久性。
事务的原子性表示事务执行过程中的任何失败都将导致事务所做的任何修改失效。一致性表示当事务执行失败时,所有被该事务影响的数据都应该恢复
到事务执行前的状态。隔离性表示并发事务是彼此隔离。持久性表示当系统或介质发生故障时,确保已提交事务的更新不能丢失。持久性通过数据库备
份和恢复来保证。
二、 数据库并发问题
当多个用户并发访问数据库中相同的数据时,可能会出现并发问题。如果没有锁定且多个用户同时访问一个数据库,则当他们的事务同时使用相同的数
据时可能会发生问题。并发问题包括:
Ø丢失或覆盖更新。
Ø未确认的相关性(脏读)。
Ø不一致的分析(非重复读)。
Ø幻读。
2.1. 丢失更新
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其他事务的存在。最后的更新将重写由其
他事务所做的更新,这将导致数据丢失。(事务T1读取了数据,并执行了一些操作,然后更新数据。事务T2也做相同的事,则T1和T2更新数据时可能会覆盖对方的更新,从而引起错误。)
例如,两个编辑人员制作了同一文件的电子复本。每个编辑人员独立地更改其复本,然后保存更改后的复本,这样就覆盖了原始文件。最后保存其更改
复本的编辑人员覆盖了第一个编辑人员所做的更改。如果在第一个编辑人员完成之后第二个编辑人员才能进行更改,则能避免该问题。
2.2. 未确认的相关性(脏读)
当第二个事务选择其他事务正在更新的行时,会发生未确认的相关性问题。第二个事务正在读取的数据还没有确认并且可能由更新此行的事务所更改。
(事务T1更新了数据还未提交,这时事务T2来读取相同的数据,则T2读到的数据其实是错误的数据,即脏数据。基于脏数据所作的操作是不可能正确的)
例如,一个编辑人员正在更改电子文件。在更改过程中,另一个编辑人员复制了该文件(该复本包含到目前为止所做的全部更改)并将其分发给预期的
用户。此后,第一个编辑人员认为目前所做的更改是错误的,于是删除了所做的编辑并保存了文件。分发给用户的文件包含不再存在的编辑内容,并且
这些编辑内容应认为从未存在过。如果在第一个编辑人员确定最终更改前所有人都不能读取更改的文件,则能避免该问题。
2.3. 不一致的分析(非重复读)
当第二个事务多次访问同一行而且每次读取不同的数据时,会发生不一致的分析问题。不一致的分析和未确认的相关性类似,因为其他事务也是正在更
改第二个事务正在读取的数据。然而,在不一致的分析中,第二个事务读取的数据是由已进行了更改的事务提交的。而且,不一致的分析涉及多次(两
次或更多)读取同一行,而且每次信息都由其他事务更改;因而该行被非重复读取。(不可重复读取是指同一个事务多次操作相同的数据部分)(Aread,thenmodifybutnotcommit,thenreadagain,A不可重复读取)
例如,一个编辑人员两次读取同一文件,但在两次读取之间,作者重写了该文件。当编辑人员第二次读取文件时,文件已更改。原始读取不可重复。如
果只有在作者全部完成编写后编辑人员才能读取文件,则能避免该问题。
2.4. 幻像读
当对某行执行插入或删除操作,而该行属于某个事务正在读取的行的范围时,会发生幻像读问题。事务第一次读的行范围显示出其中一行已不复存在于
第二次读或后续读中,因为该行已被其他事务删除。同样,由于其他事务的插入操作,事务的第二次或后续读显示有一行已不存在于原始读中。(幻像是指一个事务更新了另一个事务未提交的数据,另一个事务无法重复其原始读取;)(Aread,thenmodifybutnotcommit,thenBread,B幻像)
例如,一个编辑人员更改作者提交的文件,但当生产部门将其更改内容合并到该文件的主复本时,发现作者已将未编辑的新材料添加到该文件中。如果
在编辑人员和生产部门完成对原始文件的处理之前,所有人都不能将新材料添加到文件中,则能避免该问题。
三、 并发问题的解决方案
当锁定用作并发控制机制时,他能解决并发问题。这使所有事务得以在彼此完全隔离的环境中运行,不过所有时候都能有多个正在运行的事务。可串行
性是通过运行一组并发事务达到的数据库状态,等同于这组事务按某种顺序连续执行时所达到的数据库状态。
3.1. sql-92 隔离级别
尽管可串行性对于事务确保数据库中的数据在所有时间内的正确性相当重要,然而许多事务并不总是需求完全的隔离。例如,多个作者工作于同一本书
的不同章节。新章节能在任意时候提交到项目中。不过,对于已编辑过的章节,没有编辑人员的批准,作者不能对此章节进行所有更改。这样,尽管有
未编辑的新章节,但编辑人员仍能确保在任意时间该书籍项目的正确性。编辑人员能查看以前编辑的章节及最近提交的章节。
事务准备接受不一致数据的级别称为隔离级别。隔离级别是个事务必须和其他事务进行隔离的程度。较低的隔离级别能增加并发,但代价是降低数据的
正确性。相反,较高的隔离级别能确保数据的正确性,但可能对并发产生负面影响。
sql-92 定义了下列四种隔离级别:
Ø未提交读(事务隔离的最低级别,仅可确保不读取物理损坏的数据)。
Ø提交读(sql server 默认级别)。
Ø可重复读。
Ø可串行读(事务隔离的最高级别,事务之间完全隔离)。
如果事务在可串行读隔离级别上运行,则能确保所有并发重叠事务均是串行的。
下面四种隔离级别允许不同类型的行为。如表所示:
隔离级别
|
脏读
|
不可重复读
|
幻读
|
未提交读
|
是
|
是
|
是
|
提交读
|
否
|
是
|
是
|
可重复读
|
否
|
否
|
是
|
可串行读
|
否
|
否
|
否
|
事务必须运行于可重复读或更高的隔离级别以防止丢失更新。当两个事务检索相同的行,然后基于原检索的值对行进行更新时,会发生丢失更新。如果
两个事务使用一个 update 语句更新行,并且不基于以前检索的值进行更新,则在默认的提交读隔离级别不会发生丢失更新。
一、 乐观锁与悲观锁
4.1. 悲观锁【PessimisticLocking】
一、概念:
顾名思义就是采用一种悲观的态度来对待事务并发问题,我们认为系统中的并发更新会非常频繁,并且事务失败了以后重来的开销很大,这样以来,我
们就需要采用真正意义上的锁来进行实现。悲观锁的基本思想就是每次一个事务读取某一条记录后,就会把这条记录锁住,这样其它的事务要想更新,
必须等以前的事务提交或者回滚解除锁。
假如我们数据库事务的隔离级别设置为读取已提交或者更低,那么通过悲观锁,我们控制了不可重复读的问题,但是不能避免幻影读的问题,因为要想
避免我们就需要设置数据库隔离级别为Serializable,而一般情况下我们都会采取读取已提交或者更低隔离级别,并配合乐观或者悲观锁来实现并发控
制,所以幻影读问题是不能避免的,如果想避免幻影读问题,那么你只能依靠数据库的serializable隔离级别(幸运的是幻影读问题一般情况下不严
重)。
悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能 真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机
制,也无法保证外部系统不会修改数据)。
二、实现方式:
在JDBC中使用悲观锁,需要使用selectfor update语句,假如我们系统中有一个Account的类,我们可以采用如下的方式来进行:
Select * from Account where ...(where condition).. for update.
当使用了for update语句后,每次在读取或者加载一条记录的时候,都会锁住被加载的记录,那么当其他事务如果要更新或者是加载此条记录就会因为
不能获得锁而阻塞,这样就避免了不可重复读以及脏读的问题,但是其他事务还是可以插入和删除记录,这样也许同一个事务中的两次读取会得到不同
的结果集,但是这不是悲观锁锁造成的问题,这是我们数据库隔离级别所造成的问题。
最后还需要注意的一点就是每个冲突的事务中,我们必须使用select for update 语句来进行数据库的访问,如果一些事务没有使用
select for update语句,那么就会很容易造成错误,这也是采用JDBC进行悲观控制的缺点。
4.2. 乐观锁【OptimisticLocking】
一、 概念:
乐观锁是在同一个数据库事务中我们常采取的策略,因为它能使得我们的系统保持高的性能的情况下,提高很好的并发访问控制。乐观锁,顾名思义就
是保持一种乐观的态度,我们认为系统中的事务并发更新不会很频繁,即使冲突了也没事,大不了重新再来一次。它的基本思想就是每次提交一个事务
更新时,我们想看看要修改的东西从上次读取以后有没有被其它事务修改过,如果修改过,那么更新就会失败。(因此能够解决第二类丢失修改问
题)。
因为乐观锁其实并不会锁定任何记录,所以如果我们数据库的事务隔离级别设置为读取已提交或者更低的隔离界别,那么是不能避免不可重复读问题的
(因为此时读事务不会阻塞其它事务),所以采用乐观锁的时候,系统应该要容许不可重复读问题的出现。
需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户
更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应
调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。
二、实现方式:
大多是基于数据版本 ( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数
据库表增加一个“version” 字段来实现。
读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提 交数据的版本数据与数据库表对应记录的当前版本信息进行比对,
如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
假如系统中有一个Account的实体类,我们在Account中多加一个version字段,那么我们JDBC Sql语句将如下写:
Select a.version....from Account as a where (where condition..)
UpdateAccount set version = version+1.....(another field) where version =?...(anothercontidition)
这样以来我们就可以通过更新结果的行数来进行判断,如果更新结果的行数为0,那么说明实体从加载以来已经被其它事务更改了,所以就抛出自定义
的乐观锁定异常(或者也可以采用Spring封装的异常体系)。具体实例如下:
int rowsUpdated = statement.executeUpdate(sql);
If(rowsUpdated==0){
throws newOptimisticLockingFailureException();
}
在使用JDBC API的情况下,我们需要在每个update语句中,都要进行版本字段的更新以及判断,因此如果稍不小心就会出现版本字段没有更新的问
题,相反当前的ORM框架却为我们做好了一切,我们仅仅需要做的就是在每个实体中都增加version或者是Date字段。
一、 JDBC事务管理
5.1. 提交和回滚
在JDBC的数据库操作中,一项事务是由一条或是多条表达式所组成的一个不可分割的工作单元。我们通过提交commit()或是回退rollback()来结束事务
的操作。关于事务操作的方法都位于接口java.sql.Connection中。
首先我们要注意,在JDBC中,事务操作默认是自动提交。也就是说,一条对数据库的更新表达式代表一项事务操作。操作成功后,系统将自动调用
commit()来提交,否则将调用rollback()来回退。
其次,在JDBC中,可以通过调用setAutoCommit(false)来禁止自动提交。之后就可以把多个数据库操作的表达式作为一个事务,在操作完成后调用
commit()来进行整体提交。倘若其中一个表达式操作失败,都不会执行到commit(),并且将产生响应的异常。此时就可以在异常捕获时调用rollback()进
行回退。这样做可以保持多次更新操作后,相关数据的一致性。
5.2. JDBC API支持的事务隔离级别
Ø static int TRANSACTION_NONE =0;
说明不支持事务。
Ø static intTRANSACTION_READ_UNCOMMITTED = 1;
说明一个事务在提交前其变化对于其他事务来说是可见的。这样脏读、不可重复的读和虚读都是允许的。
Ø static intTRANSACTION_READ_COMMITTED = 2;
说明读取未提交的数据是不允许的。这个级别仍然允许不可重复的读和虚读产生。
Ø static intTRANSACTION_REPEATABLE_READ = 4;
说明事务保证能够再次读取相同的数据而不会失败,但虚读仍然会出现。
Ø static int TRANSACTION_SERIALIZABLE= 8;
是最高的事务级别,它防止脏读、不可重复的读和虚读。
JDBC根据数据库提供的默认值来设置事务支持及其加锁,运行在TRANSACTION_SERIALIZABLE模式下的事务可以保证最高程度的数据完整性,但事
务保护的级别越高,性能损失就越大。上述设置随着值的增加,其事务的独立性增加,更能有效地防止事务操作之间的冲突,同时也增加了加锁的开
销,降低了用户之间访问数据库的并发性,程序的运行效率也会随之降低。因此得平衡程序运行效率和数据一致性之间的冲突。一般来说,对于只涉及
到数据库的查询操作时,可以采用
TRANSACTION_READ_UNCOMMITTED方式;对于数据查询远多于更新的操作,可以采用
TRANSACTION_READ_COMMITTED方式;对于更新操作较多的,可以采用TRANSACTION_REPEATABLE_READ;在数据一致性要求更高的场合
再考虑最后一项,由于涉及到表加锁,因此会对程序运行效率产生较大的影响。
假设我们现在有一个Connection对象con,那么设置事务级别的方法如下:
con.setTransactionLevel(TRANSACTION_SERIALIZABLE) ;
你也可以使用getTransactionLevel()方法来获取当前事务的级别:
con.getTransactionLevel();
在默认情况下,JDBC驱动程序运行在"自动提交"模式下,即发送到数据库的所有命令运行在它们自己的事务中。这样做虽然方便,但付出的代价是程
序运行时的开销比较大。我们可以利用批处理操作减小这种开销,因为在一次批处理操作中可以执行多个数据库更新操作。但批处理操作要求事务不能
处于自动提交模式下。为此,我们首先要禁用自动提交模式。
例子代码:
package my.transaction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class MyTestTransaction {
public final static String DRIVER = "oracle.jdbc.driver.OracleDriver"; // 数据库驱动
public final static String URL = "jdbc:oracle:thin:@localhost:1521:orcl"; // url
public final static String DBNAME = "lsw"; // 数据库用户名
public final static String DBPASS = "lsw"; // 数据库密码
// JavaBean中使用JDBC方式进行事务处理
public int TransactioTest() throws SQLException {
Connection conn = null;
try {
Class.forName(DRIVER); // 注册驱动
conn = DriverManager.getConnection(URL, DBNAME, DBPASS); // 获得数据库连接
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
conn.setAutoCommit(false);// 更改JDBC事务的默认提交方式(自动提交事务);
int err = 0;
err += executeUpdate("delete student where stuid=555", conn);
err += executeUpdate("insert into student values(555,'xxx',54)", conn);
err += executeUpdate("insert into student values(555,'yyy',54)", conn);
err += executeUpdate("insert into student values(555,'zzz',54)", conn);
// 没有异常时
if (err == 0) {
conn.commit();// 提交JDBC事务
System.out.println("已经提交JDBC事务");
} else {
conn.rollback();// 回滚事务
System.err.println("已经回滚事务");
}
conn.setAutoCommit(true);// 恢复JDBC事务的默认提交方式
return err;
} catch (Exception exc) {
exc.printStackTrace();
return -1;
} finally {
conn.close();
}
}
int executeUpdate(String sql, Connection conn) {
try {
PreparedStatement ps = conn.prepareStatement(sql);
ps.executeUpdate();
return 0;//执行正确
} catch (SQLException e) {
return 1;//执行失败
}
}
/**
* @param args
*/
public static void main(String[] args) {
MyTestTransaction mtt = new MyTestTransaction();
try {
int r = mtt.TransactioTest();
if (r == 0) {
System.out.println("ok");
} else {
System.err.println("no");
}
} catch (SQLException e) {
System.err.println("err");
e.printStackTrace();
}
}
}
分享到:
相关推荐
3、了解Spring事务管理的3个核心接口; 4、了解Spring事务管理的两种方式; 5、掌握基于XML和Annotation的声明式事务管理的使用。 二、 实验内容 1、在MySQL中创建以下三张表,其中account为账户表,book为书籍信息...
javaEE 实验三 Spring JDBC与事务管理, 一、实验目的 1、掌握Spring JDBC的配置; 2、掌握JdbcTemplae类中增删改查方法的使用; 3、了解Spring事务管理的3个核心接口; 4、了解Spring事务管理的两种方式; 5、掌握...
javaee-jdbc,jdbc 编程,事务控制和回滚
非常常用的JavaEE四层架构下的纯JDBC事务控制简单解决方案。Eclipse工程。详情参看:http://blog.csdn.net/qjyong/archive/2010/04/08/5464835.aspx
JDBC超轻量级事务管理器 介绍: 简单即美。 简短的: ULTM为您提供了JDBC数据源的基本事务API。 您要做的就是准备一个DataSource ,并将其传递给该库,以换取TxManager (以控制事务)和专用的托管DataSource您应...
十年软件开发经验: 参与完成辽宁某高校远程教学管理系统、慧文信息门户系统、日本麦卡尔超市管理系统、崎玉市外来人口登记系统、深海视频会议管理系统、仙台市宫城县日常事务系统等项目的设计和开发。六年软件培训...
第4章 事务管理 4.1 概述 4.2 标准 4.3 事务的互用性 4.4 本地事务优化 4.5连接共享 4.6 JDBC和JMS部署问题 4.7 双相提交支持 4.8 系统管理工具 第5章 资源,命名和注入 5.1 概述 5.2 JNDI命名上下文环境 ...
Java/JavaEE 学习笔记 作者在杰普学习时的学习笔记,是J2ee初学者必备手册,是大家学习J2EE开发的很好的参考笔记。 Java/JavaEE 学习笔记 内容目录: Unix 学习笔记..........7 一、Unix前言............7 二、...
Spring简介 Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架 为了解决企业应用开发的复杂性而创建 ...对JavaEE开发中一些难用的API(JDBC JavaMail WebService等) 都提供了封装 使这些API应用难度大大降低
1、了解JDBC模块作用及事务管理两种方式(XML方式与注解方式) 2、熟悉JDBC配置及事务管理的3个核心接口 3、掌握JdbcTemplate类中的常用方法及声明式事务的使用 二:实验内容 基于Spring中的JdbcTemplate及声明式...
熟练掌握 Mysql 数据库select、insert、update、delete、modify等基本操作和聚合函数、子集、排序、分组、索引查询、子查询、多表连查、外连接、内连接、实体完整性、域完整性、引用完整性、及事务隔离性等;...
javaweb ...知识点 Javascript知识点 jQuery知识点 14 Bootstrap知识点 17 ...事务(JDBC) 67 Json数据格式(重要) 70 Js原生Ajax和Jquery的Ajax 71 监听器Listener 74 邮箱服务器 79 过滤器Filter
9.2.5 事务管理和异常处理 359 9.3 使用NetBeans开发EJB 359 9.3.1 使用NetBeans开发Session Bean 359 9.3.2 使用NetBeans开发MDB 362 9.4 本章小结 363 第10章 Java持久化API(JPA) 364 10.1 实体简介 365 10.1.1 ...
本课题会开发一个计算机协会社团信息管理系统,从而实现计算机协会社团信息管理系统内部各种繁琐事务的管理。追求低碳生活,摆脱以前纸质化的办公模式,节约人力物力从而达到提高办事效率的目的。随着计算机技术的...
14.2.5管理hibernate事务 282 14.3项目实战——学生成绩查询系统 283 本章小结 292 课后练习 293 第15章图书馆管理系统 294 15.1系统功能解析 294 15.2系统数据库设计 295 15.2.1数据库分析 295 15.2.2数据...
该案例把 JavaWEB 之前学习的大部分技术融合到一起,具体内容涉及:JavaSE、SQL、JDBC、DBUtils、C3P0、JavaScript、jQuery、Ajax、JavaWEB、JSON 等。 通用翻页、使用 AOP 编程思想实现事务操作、面向接口编程、...
JDBC集成,事务集成,单元和集成测试。Hibernate集成,Java持久化API集成 深入讲解Java EE服务及技术集成。包括JNDI集成,EJB3.0集成.线程池和任务调度集成。Java消息服务集成,Java Mail集成,远程服务集成。Java...
二、 JavaEE 部分 1、 Spring ①IoC 与 Bean 配置、 管理 ②AOP 与事务、 权限控制 ③S2SH 整合开发 ④Spring, JPA 整合 2、 Hibernate ①ORM 与持久化映射 ②延迟加载、 性能优化 ③HQL 查询、 条件查询、 SQL ...
JDBC集成,事务集成,单元和集成测试。Hibernate集成,Java持久化API集成 深入讲解Java EE服务及技术集成。包括JNDI集成,EJB3.0集成.线程池和任务调度集成。Java消息服务集成,Java Mail集成,远程服务集成。Java...
JDBC集成,事务集成,单元和集成测试。Hibernate集成,Java持久化API集成 深入讲解Java EE服务及技术集成。包括JNDI集成,EJB3.0集成.线程池和任务调度集成。Java消息服务集成,Java Mail集成,远程服务集成。Java...