由并发性导致事务处理的问题
脏读 dirty
Transaction
reads:当事务读取还未被提交的数据时,就会发生这种事件。举例来说:
1 修改了一行数据,然
后 Transaction
行。如果 Transaction
2 在 Transaction
1 还未提交修改操作之前读取了被修改的
1 回滚了修改操作,那么 Transaction
2 读取的数据就
可以看作是从未存在过的。
不可重复的读 non-repeatable
reads:当事务两次读取同一行数据,但每次得到的数据
都不一样时,就会发生这种事件。举例来说:Transaction
1 读取一行数据,然
后 Transaction
2 修改或删除该行并提交修改操作。当 Transaction
1 试图重
新读取该行时,它就会得到不同的数据值(如果该行被更新)或发现该行不再存在(如果该行被
删除)。
虚读 phantom read:如果符合搜索条件的一行数据在后面的读取操作中出现,但该行数
1 读取满足某种搜
据却不属于最初的数据,就会发生这种事件。举例来说:Transaction
索条件的一些行,然后 Transaction
的一个新行。如果 Transaction
2 插入了符合 Transaction
1 的搜索条件
1 重新执行产生原来那些行的查询,就会得到不同的行。
事务场景是这样的:
对于同一个银行帐户 A 内有 200 元,甲进行提款操作 100 元,乙进行转帐操作 100 元到 B 帐
户。如果事务没有进行隔离可能会并发如下问题:
1、第一类丢失更新:首先甲提款时帐户内有 200 元,同时乙转帐也是 200 元,然后甲乙同时
操作,甲操作成功取走 100 元,乙操作失败回滚,帐户内最终为 200 元,这样甲的操作被覆盖
掉了,银行损失 100 元。
2、脏读:甲取款 100 元未提交,乙进行转帐查到帐户内剩有 100 元,这是甲放弃操作回滚,
乙正常操作提交,帐户内最终为 0 元,乙读取了甲的脏数据,客户损失 100 元。
3、虚读:和脏读类似,是针对于插入操作过程中的读取问题,如丙存款 100 元未提交,这时
银行做报表进行统计查询帐户为 200 元,然后丙提交了,这时银行再统计发现帐户为 300 元了,
无法判断到底以哪个为准?
大家好像觉得统计这个东西肯定是时时更新的,这种情况很正常;但是如果统计是在一个事务
中的时候就不正常了,比如我们的一个统计应用需要将统计结果分别输出到电脑屏幕和远程网络
某台计算机的磁盘文件中,为了
提高性能和用户响应我们分成 2 个线程,这时先完成的和后完成的统计数据就可能不一致,我
们就不知道以哪个为准了。
4、不可重复读:甲乙同时开始都查到帐户内为 200 元,甲先开始取款 100 元提交,这时乙在
准备最后更新的时候又进行了一次查询,发现结果是 100 元,这时乙就会很困惑,不知道该将
帐户改为 100 还是 0。
和脏读的区别是,脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提
交的数据。
5、第二类丢失更新:是不可重复读的一种特例,如上,乙不做第二次查询而是直接操作完成,
帐户内最终为 100 元,甲的操作被覆盖掉了,银行损失 100 元。感觉和第一类丢失更新类似。
在多个事务并发做数据库操作的时候,如果没有有效的避免机制,就会出现种种问题。大体上有
三种问题,归结如下:
1、丢失更新
如果两个事务都要更新数据库一个字段 X,x=100
事务 A
事务 B
读取 X=100
读取 X=100
写入 x=X+100
写入 x=X+200
事务结束 x=200
事务结束 x=300
最后 x==300
这种情况事务 A 的更新就被覆盖掉了、丢失了。
丢失更新说明事务进行数据库写操作的时候可能会出现的问题。
2、不可重复读
一个事务在自己没有更新数据库数据的情况,同一个查询操作执行两次或多次的结果应该是一致
的;如果不一致,就说明为不可重复读。
还是用上面的例子
事务 A
事务 B
读取 X=100
读取 X=100
读取 X=100
写入 x=X+100
读取 X=200
事务结束 x=200
事务结束 x=200
这种情况事务 A 多次读取 x 的结果出现了不一致,即为不可重复读。
再有一情况就是幻影
事务 A 读的时候读出了 15 条记录,事务 B 在事务 A 执行的过程中删除(增加)了 1 条,事务
A 再读的时候就变成了 14(16)条,这种情况就叫做幻影读。
不可重复读说明了做数据库读操作的时候可能会出现的问题。
3、脏读(未提交读)
防止一个事务读到另一个事务还没有提交的记录。
如:
事务 A
读取 X=200
事务 B
读取 X=100
写入 x=X+100
事务回滚 x=100
读取 X=100
事务结束 x=100
x 锁 排他锁 被加锁的对象只能被持有锁的事务读取和修改,其他事务无法在该对象上
加其他锁,也不能读取和修改该对象
s 锁 共享锁 被加锁的对象可以被持锁事务读取,但是不能被修改,其他事务也可以在上面
再加 s 锁。
封锁协议:
一级封锁协议:
在事务修改数据的时候加 x 锁,直到事务结束(提交或者回滚)释放 x 锁。一级封锁协议
可以有效的防止丢失更新,但是不能防止脏读不可重复读的出现。
二级封锁协议:
在一级封锁的基础上事务读数据的时候加 s 锁,读取之后释放。二级封锁协议可以防止丢失
更新,脏读。不能防止不可重复读。
三级封锁协议:
在一级封锁的基础上事务读数据的时候加 s 锁,直到事务结束释放。二级封锁协议可以防止
丢失更新,脏读,不可重复读。
JDBC 事务的级别
为了解决与“多个线程请求相同数据”相关的问题,事务之间用锁相互隔开。多数主流的数据库支
持不同类型的锁;因此,JDBC API 支持不同类型的事务,它们由 Connection 对象指派或确
定。在 JDBC API 中可以获得下列事务级别:
TRANSACTION_NONE 说明不支持事务。
TRANSACTION_READ_UNCOMMITTED 说明在提交前一个事务可以看到另一个事务的变化。这样
脏读、单读和虚读都是允许的。
TRANSACTION_READ_COMMITTED 说明读取未提交的数据是不允许的。这个级别仍然允许单读
和虚读产生。
TRANSACTION_REPEATABLE_READ 说明事务保证能够再次读取相同的数据而不会失败,但虚
读仍然会出现。
TRANSACTION_SERIALIZABLE 是最高的事务级别,它防止脏读、单读和虚读。
conn.setTransactionLevel(TRANSACTION_SERIALIZABLE) ;
if(conn.getTransactionLevel() == TRANSACTION_SERIALIZABLE) {
System.out.println("Highest Transaction Level in operation.") ;
}
更高级的 J2EE 框架提供了 JTA(Java Transaction API)来实现可控制的事务管理模型。这个
API 允许用一种独立于事务管理器的方法来划分事务。JTA 需要 J2EE 软件开发包的支持,
实现了 Java 事务服务 JTS(Java Transaction Service)的事务管理器,代码调用 JTA 方法,
然后由该方法调用底层的 JTS 例程。
附:
1、如下的 SQL 语句不允许出现在事务中:
ALTER DATABASE
修改数据库
BACKUP LOG
备份日志
CREATE DATABASE
创建数据库
DISK INIT
创建数据库或事务日志设备
DROP DATABASE
删除数据库
DUMP TRANSACTION
转储事务日志
LOAD DATABASE
装载数据库备份复本
LOAD TRANSACTION
装载事务日志备份复本
RECONFIGURE
更新使用 sp_configure 系统存储过程更改的配置选项的当前配置(sp_configure 结
果集中的 config_value 列)值。
RESTORE DATABASE
还原使用 BACKUP 命令所作的数据库备份
RESTORE LOG
还原使用 BACKUP 命令所作的日志备份
在指定的表或索引视图中,对一个或多个统计组(集合)有关键值分发的信息进行更
UPDATE STATISTICS
新
2、在 Tomcat 中使用 JDBC 与 JTA
在 Tomcat 中使用 JDBC 与 JTA
因为需要将项目从 IBM WebSphere Application Server 移植到 Tomcat
上开发,所以研究了一下在 Tomcat 中通过 JNDI 查找和使用 JDBC 及 JTA 的
方法。
Tomcat 是 Servlet 容器,但它也提供了一个 JNDI InitialContext 实现,
因此用户可以像在 J2EE 应用程序服务器中一样在 Tomcat 中使用 JNDI 查找
JDBC 数据源。不过在事务处理方面,Tomcat 本身并不支持 JTA(Java
Transaction API),所以需要借助其他的方案。JOTM(Java Open Transaction
Manager)是 ObjectWeb 的一个开源 JTA 实现,它本身也是开源应用程序服务
器 JOnAS(Java Open Application Server)的一部分,为其提供 JTA 支持和
分布式事务管理。JOTM 同样可以为 Tomcat 提供 JTA 支持,以下将对相关的
配置进行简单说明,使用的相应版本为:
Tomcat 5.5.x
JOTM 2.0.x
Oracle 9i
1. 配置 Tomcat 环境
在$TOMCAT_HOME/conf/context.xml 文件中添加以下内容:
2. 添加所需的 JAR 文件
下载 JOTM,将以下文件添加到$TOMCAT_HOME/common/lib/:
jotm.jar
jotm_jrmp_stubs.jar
jotm_iiop_stubs.jar
ow_carol.jar
jta-spec1_0_1.jar
jts1_0.jar
objectweb-datasource.jar
xapool.jar
howl.jar
connector-1_5.jar
同时,还需要添加相应数据库的 JDBC 包,例如 Oracle 的 classes12.jar
3. 配置 JOTM
新建一个 carol.properties 文件,置于
$TOMCAT_HOME/common/classes/,文件内容如下:
# JNDI (Protocol Invocation)
carol.protocols=jrmp
# Local RMI Invocation
carol.jvm.rmi.local.call=true
# do not use CAROL JNDI wrapper
carol.start.jndi=false
# do not start a name server
carol.start.ns=false
# Naming Factory
carol.jndi.java.naming.factory.url.pkgs=org.apache.naming
这样 JOTM 将不会使用 CAROL JNDI wrapper,从而可以避免类装载错误的发
生
4. 说明
4.1 JOTM 目前的版本在 JDK1.5 或以上可能无法正常运行,解决的方法有
两个:使用 JDK1.5 重新编译 carol 库,或者将 Tomcat 运行在 JDK1.4 中
4.2 是 Tomcat 5 中的新标记,对于不支持此标记的老版本,
需要使用以下语句代替事务资源的声明:
4.3 需要注意的是,使用节点声明的资源默认上下文前缀是
"java:comp/env",而使用< Transaction>节点时则是"java:comp"。因此,
当使用 4.2 的方式声明用户事务时,相应的 JNDI 查找代码也应该改为
UserTransaction ut =
(UserTransaction)initCtx.lookup("java:comp/env/UserTransaction");
5. 测试
假设数据库中已经做了相应配置,可以使用如下 jsp 页面进行测试:
<%@page contentType="text/html;charset=GB2312"%>
<%@page import="java.sql.*"%>
<%@page import="javax.sql.*"%>
<%@page import="javax.naming.*"%>
<%@page import="javax.transaction.UserTransaction"%>
<%
ResultSet rs = null;
Statement stmt = null;
UserTransaction ut = null;