分类:
2008-05-15 20:40:06
1,事务是指一组相互依赖的操作行为,事务处理保证所有的事务都作为一个工作单元来执行,即使出现了硬件故障或系统失灵,都不能改变这种执行方式.当在一个事务中执行多个操作时,要么所有的操作都被提交(commit),要么整个事务回滚(rollback)到最初状态.
2,当一个连接被创建时,默认情况下是设置为自动提交事务,这意味着每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,也就不能回滚了.为了将所有SQL语句作为一个事务执行,可以调用Connection对象的setAutoCommit()方法,传入false来取消自动提交事务,然后在所有的SQL语句成功执行后,调用Connection对象的commit()方法提交事务,或者在执行出错时,调用Connection对象的rollback()方法回滚事务
3,数据库事务必须具备ACID特征:
<1>Atomic原子性:
整个数据库事务是不可分隔的工作单元,只有事务中所有的操作执行成功,才算整个事务成功,事务中任何一个SQL语句执行失败,那么已经执行成功的SQL语句也必须撤销,数据库状态应该退回到执行事务前的状态
<2>Consistency一致性:
数据库系统事务不能破坏关系数据库系统的完整性及业务逻辑上的一致性
<3>Isolation隔离性:
并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间
<4>Durability持久性:
只要事务成功结束,它对数据库所做的更新就必须永久保存下来,即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束后的状态
4,事务的ACID特性是由关系数据库管理系统(RDBMS)实现的,日志保证事务的原子性,一致性和持久性,锁机制实现事务的隔离性
5,声明事务包括
<1>事物的开始边界
<2>事务的正常结束边界(commit):提交事务,永久保存被事务更新后的数据库状态
<3>事务的异常结束边界(rollback):撤销事务,或者说回滚事务,使数据库退回到执行事务前的初始状态
6,数据库系统支持两种事务模式:
<1>自动提交模式:每个SQL语句都是一个独立的事务,当数据库系统执行完一个SQL语句后,会自动提交事务
<2>手工提交模式:必须由数据库的客户程序显示指定事务开始边界和结束边界
7,在MySQL中,数据库表分3种类型:INNODB和BDB(支持数据库事务)和MyISAM(不支持数据库事务)类型,用create table语句创建的表默认为MyISAM类型,
如果希望创建INNODB,可以采用以下形式的DDL语句:
create table PersonInfo(
ID bigint not null primary key,
NAME varchar(20),
AGE int(10)
) type=INNODB;
对于一个已经存在的表,可以更改存储类型:
alter table PersonInfo type=INNODB;
8,在Mysql.exe中,每个数据库连接都有一个全局变量@@autocommit,表示当前的事务模式,0表示手工提交模式,1默认值,表示自动提交模式
<1>查看当前事务模式:
mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 1 |
+--------------+
<2>更改事务提交模式:
set autocommit=0;
9,
<1>在自动提交模式下运行事务:
在自动提交模式下,每个SQL语句都是一个独立的事务,当执行下面的SQL语句后
mysql> insert into account values('浩子',880.5);
MySQL会自动提交这个事务,这意味着向ACCOUNT表中插入的记录会永久保存在数据库中,此时在第二个mysql.exe进程中,查看ACCOUNT表内容:
mysql> select * from account;
+--------+---------+
| userid | balance |
+--------+---------+
| 浩子 | 880.50 |
| 彭磊 | 276.00 |
| 小松 | 450.80 |
| 徐璐 | 1000.50 |
+--------+---------+
这行记录已经插入进来,体现了事务的ACID特性中的永久性(持久性)
<2>在手工提交模式下运行事务
在手工提交模式下,必须显示指定事务开始边界和结束边界
*事务的开始边界:begin
*提交事务:commit
*回滚事务:rollback
1,启动两个mysql.exe进程,在两个进程中都设定为手工提交模式
mysql> set autocommit=0;
2,在第一个mysql.exe进程中执行插入
mysql> use bookstore;
mysql> begin;
mysql> insert into account values('小张',560.6);
3,在第二个mysql.exe进程中执行查询
mysql> use bookstore; mysql> begin; mysql> select * from account; +--------+---------+ | userid | balance | +--------+---------+ | 浩子 | 880.50 | | 彭磊 | 276.00 | | 小松 | 450.80 | | 徐璐 | 1000.50 | +--------+---------+ mysql> commit; |
以上select语句的结果并不包含,在第一个mysql.exe进程中插入的数据,因为插入数据后,并没有commit提交事务
4,在第一个mysql.exe进程中提交事务
mysql> commit;
5,在第二个mysql.exe进程中再次查询
mysql> begin; mysql> select * from account; +--------+---------+ | userid | balance | +--------+---------+ | 浩子 | 880.50 | | 彭磊 | 276.00 | | 小松 | 450.80 | | 小张 | 560.60 | | 徐璐 | 1000.50 | +--------+---------+ mysql> commit; |
第一个mysql.exe进程提交事务后,数据被永久保存到了数据库中,所以在第二个mysql.exe进程中可查看到
6, 在第一个mysql.exe进程中执行以下SQL语句
mysql> begin; mysql> insert into account values('小刘',200.5); mysql> insert into account values('小马',400.5); mysql> commit; mysql> begin; mysql> delete from account; mysql> rollback; mysql> begin; mysql> select * from account; +--------+---------+ | userid | balance | +--------+---------+ | 浩子 | 880.50 | | 彭磊 | 276.00 | | 小刘 | 200.50 | | 小马 | 400.50 | | 小松 | 450.80 | | 小张 | 560.60 | | 徐璐 | 1000.50 | +--------+---------+ mysql> commit; |
以上SQL语句共执行3个事务,
第1个事务插入两条记录,提交;
第2个事务删除表中数据,回滚;
第3个事务查询表中数据,提交
10,通过JDBC API声明事务边界
<1>Connection接口提供了以下用于控制事务的方法
*setAutoCommit(boolean autoCommit):设置是否自动提交事务
*commit():提交事务
*rollback(():回滚事务
<2>对于新建的Connection对象,默认采用自动提交事务模式,通过setAutoCommit(false)方法来设置手工提交模式,然后就可以把多条SQL语句作为一个事务,在所有操作完成后调用commit()方法提交事务,如果其中出现任何一个SQL操作失败,程序会抛出SQLException,此时应该在捕获异常的代码块中调用rollback()方法回滚事务.
try { conn = DriverManager.getConnection(url, user, password); //设置手工提交模式 conn.setAutoCommit(false); stmt = conn.createStatement(); //数据库更新操作1 stmt.executeUpdate("update ACCOUNT set BALANCE=900 where id=1"); //数据库更新操作2 stmt.executeUpdate("update ACCOUNT set BALANCE=1100 where id=2"); conn.commit(); //提交事务 }catch(Exception e) { try { conn.rollback(); //操作失败,则回滚事务 }catch(Exception ex) { ... //处理异常 } ... //处理异常 }finally { try { stmt.close(); conn.close(); }catch(Exception ex) { ... //处理异常 } } |
当一个事务提交后,再通过这个连接执行其它SQL语句,实际上开始了一个新的事务,
conn = DriverManager.getConnection(url, user, password); /*******************第一个事务*******************/ //设置手工提交模式 conn.setAutoCommit(false); stmt = conn.createStatement(); //数据库更新操作1 stmt.executeUpdate("update ACCOUNT set BALANCE=900 where id=1"); //数据库更新操作2 stmt.executeUpdate("update ACCOUNT set BALANCE=1100 where id=2"); conn.commit(); //提交事务 /*******************第二个事务*******************/ //数据库更新操作1 stmt.executeUpdate("update ACCOUNT set BALANCE=1200 where id=3"); //数据库更新操作2 stmt.executeUpdate("update ACCOUNT set BALANCE=800 where id=4"); conn.commit(); //提交事务 |
11,保存点
<1>调用Connection的rollback()方法会撤销整个事务,如果只希望撤销事务中的部分操作,可以在事务中设置保存点,Connection接口的setSavepoint()方法用于在事务中设置保存点
设置匿名的保存点:
public SavePoint setSavepoint() throws SQLException
name参数表示保存点的名字:
public SavePoint setSavePoint(String name) throws SQLException
两个方法都会返回表示保存点的SavePoint对象
<2>Connection接口的releaseSavepoint(Savepoint point)方法取消已经设置的保存点,
Connection接口的rollback(Savepoint point)方法是事务回滚到参数指定的保存点
有些JDBC驱动器不支持保存点,DatebaseMetaDate接口的supportsSavepoint()方法可判断是否支持保存点
下面这个类演示了用法:
SavepointTest.java import java.sql.*; public class SavepointTest { public static void main(String args[]) throws Exception { //连接数据库 Connection con = new ConnectionProvider().getConnection(); DatabaseMetaData dbm = con.getMetaData(); //判断此JDBC驱动器是否支持保存点 if(dbm.supportsSavepoints() == false) { System.out.println("此JDBC驱动器不支持保存点"); } System.out.println("如果不支持保存点,则setSavepoint()方法会抛出以下异常:"); try { con.setAutoCommit(false); Statement stmt = con.createStatement(); stmt.executeUpdate("delete from accounts"); stmt.executeUpdate("insert into accounts(ID,NAME,BALANCE) values(1,'Tom',1000)"); Savepoint sp = con.setSavepoint(); //设置保存点 stmt.executeUpdate("update accounts set BALANCE=900 where id=1"); con.rollback(sp); //回滚到保存点 stmt.executeUpdate("insert into accounts values(2,'Jack',1000)"); con.commit(); }catch(SQLException e) { con.rollback(); //回滚整个事务 System.out.println("错误代码: "+e.getErrorCode()); System.out.println("SQL状态: "+e.getSQLState()); System.out.println("消息: "+e.getMessage()); System.out.println("栈跟踪信息: "); e.printStackTrace(); }finally { con.close(); } } } /* 此JDBC驱动器不支持保存点 如果不支持保存点,则setSavepoint()方法会抛出以下异常: 错误代码: 0 SQL状态: S1C00 消息: Feature not implemented 栈跟踪信息: com.mysql.jdbc.NotImplemented: Feature not implemented at com.mysql.jdbc.Connection.setSavepoint(Connection.java:843) at SavePointTest.main(SavePointTest.java:28) */ |
13,批量更新
<1>使用批量更新来执行大量SQL操作,能提高操纵数据库的效率,Statement接口提供了支持批量更新的两个方法:
addBatch(String sql):加入一条SQL语句
int[] executeBatch():执行批量更新,返回值为int数组
注意:
*必须把批量更新中的所有操作放在一单个事务中
*批量更新中可以包括update,delete,insert等数据操纵语句,还可以有create table,drop table等数据定义语句,但不能有select查询语句,否则executeBatch()方法会抛出BatchUpdateException
*有些JDBC驱动器不支持批量更新,DatabaseMetaData接口的supportsBatchUpdates()方法可判断驱动器是否支持批量更新,如果返回false,不支持批量更新的话, executeBatch()方法会抛出BatchUpdateException
BatchUpdateTest类演示了批量更新的用法:
import java.sql.*; public class BatchUpdateTest { public static void main(String args[]) throws Exception { Connection con = new ConnectionProvider().getConnection(); DatabaseMetaData dbm = con.getMetaData(); //判断此JDBC驱动器是否支持批量更新 if(dbm.supportsBatchUpdates() == true) { System.out.println("此JDBC驱动器支持批量更新"); } else { System.out.println("此JDBC驱动器不支持批量更新"); } try { con.setAutoCommit(false); //取消自动更新 Statement stmt = con.createStatement(); stmt.addBatch("delete from accounts"); stmt.addBatch("insert into accounts values(1,'Tom',900)"); //stmt.addBatch("insert into accounts values(2,'Jack',1200)"); stmt.addBatch("insert into accounts values(2,Jack,1200)"); stmt.addBatch("update accounts set balance=1000 where id=1"); stmt.addBatch("update accounts set balance=800 where id=2"); //执行批量更新,并返回一个int型数组 int[] updateCounts = stmt.executeBatch(); for(int i=0; i { System.out.print(updateCounts[i]+" "); } con.commit(); //提交以上操作 }catch(BatchUpdateException be) { System.err.println("--------BatchUpdateException--------"); System.err.println("SQL状态: "+be.getSQLState()); System.err.println("消息: "+be.getMessage()); System.err.println("错误代码: "+be.getErrorCode()); System.err.println("Update counts: "); int[] updateCounts = be.getUpdateCounts(); for(int i=0; i { System.err.print(updateCounts[i]+"--->"); } System.err.println(""); con.rollback(); //回滚事务 }catch(SQLException se) { con.rollback(); //回滚事务 }finally { con.close(); } } } /* 打印如下: accounts表中没有记录,所以执行delete表中所有数据时,返回0, 后面四个1分别指各插入或更新了一条数据 此JDBC驱动器支持批量更新 0 1 1 1 1 如果将第2个插入语句中的'Jack'中的''号去掉,则抛出异常: 此JDBC驱动器支持批量更新 --------BatchUpdateException-------- SQL状态: 42S22 消息: Unknown column 'Jack' in 'field list' 错误代码: 1054 Update counts: 0--->1--->-3--->1--->0---> 查看accounts表内容时: mysql> select * from accounts; Empty set 显示表为空, 说明此事务中的批量更新没有完成,事务进行回滚,一条数据也没有插入,也没有更新 */ |
14,设置事务隔离级别
<1>多用户环境中,如果多个事务同时操纵数据库中的相同数据,都会导致各种并发问题,为了避免这些,数据库系统提供了4中事务隔离级别:
* Serializable: 串行化
* Repeatable Read: 可重复读
* Read Commited: 读已提交数据
* Read UnCommited: 读未提交数据
(隔离级别从由到低,隔离级别越高,越能保证数据库中数据的完整性和一致性,但是对并发性能影响也越大)
<2>Connection接口的setTransactionIsolation(int level)用来设置数据库系统使用的隔离级别,这种设置只对当前连接有效
Connection.TRANSACTION_SERIALIZABLE
Connection.TRANSACTION_REPEATABLE_READ
Connection.TRANSACTION_READ_COMMITTED
Connection.TRANSACTION_READ_UNCOMMITTED