1.数据库事务的概念:
•事务是指一组相互依赖的操作行为,如银行交易、股票交易或网上购物。事务的成功取决于这些相互依赖的操作行为是否都能执行成功,只要有一个操作行为失败,就意味着整个事务失败。例如,Tom到银行办理转账事务,把100元钱转到Jack的账号上,这个事务包含以下操作行为:
–(1)从Tom的账户上减去100元。
–(2)往Jack的账户上增加100元。
•显然,以上两个操作必须作为一个不可分割的工作单元。假如仅仅第一步操作执行成功,使得Tom的账户上扣除了100元,但是第二步操作执行失败,Jack的账户上没有增加100元,那么整个事务失败。
•数据库事务是对现实生活中事务的模拟,它由一组在业务逻辑上相互依赖的SQL语句组成。
2.数据库事务的生命周期:
3.声明事务的边界:
•事务的开始边界。
•事务的正常结束边界(COMMIT):提交事务,永久保存被事务更新后的数据库状态。
•事务的异常结束边界(ROLLBACK):撤销事务,使数据库退回到执行事务前的初始状态。
(1).在mysql.exe中声明事务:
•每启动一个mysql.exe程序,就会得到一个单独的数据库连接。每个数据库连接都有个全局变量@@autocommit,表示当前的事务模式,它有两个可选值:
–0:表示手工提交模式。
–1:默认值,表示自动提交模式。
•如果要察看当前的事务模式,可使用如下SQL命令:
–mysql> select @@autocommit
•如果要把当前的事务模式改为手工提交模式,可使用如下SQL命令:
–mysql> set autocommit=0;
——在自动提交模式下提交事务:
•在自动提交模式下,每个SQL语句都是一个独立的事务。如果在一个mysql.exe程序中执行SQL语句:
–mysql>insert into ACCOUNTS values(1,'Tom',1000);
•MySQL会自动提交这个事务,这意味着向ACCOUNTS表中新插入的记录会永久保存在数据库中。此时在另一个mysql.exe程序中执行SQL语句:
–mysql>select * from ACCOUNTS;
•这条select语句会查询到ID为1的ACCOUNTS记录。这表明在第一个mysql.exe程序中插入的ACCOUNTS记录被永久保存,这体现了事务的ACID特性中的持久性。
——在手工模式下提交事务:
•在手工提交模式下,必须显式指定事务开始边界和结束边界:
–事务的开始边界:begin
–提交事务:commit
–撤销事务:rollback
例:
–mysql>begin;
–mysql>select * from ACCOUNTS;
–mysql>commit;
(2).通过JDBC API声明事务边界:
• Connection提供了以下用于控制事务的方法:
–setAutoCommit(boolean autoCommit):设置是否自动提交事务
–commit():提交事务
–rollback():撤销事务
例:
try {
con = java.sql.DriverManager.getConnection(dbUrl,dbUser,dbPwd);
//设置手工提交事务模式
con.setAutoCommit(false);
stmt = con.createStatement();
//数据库更新操作1
stmt.executeUpdate("update ACCOUNTS set BALANCE=900 where ID=1 ");
//数据库更新操作2
stmt.executeUpdate("update ACCOUNTS set BALANCE=1000 where ID=2 ");
con.commit(); //提交事务
}catch(Exception e) {
try{
con.rollback(); //操作不成功则撤销事务
}catch(Exception ex){
//处理异常
……
}
//处理异常
……
}finally{…}
(3).通过Hibernate API声明事务边界:
•声明事务的开始边界:Transaction tx=session.beginTransaction();
•提交事务: tx.commit();
•撤销事务: tx.rollback();
4.多个事务并发时的并发问题:
•第一类丢失更新:撤销一个事务时,把其他事务已提交的更新数据覆盖。
•脏读:一个事务读到另一事务未提交的更新数据。
•虚读:一个事务读到另一事务已提交的新插入的数据。
•不可重复读:一个事务读到另一事务已提交的更新数据。
•第二类丢失更新:这是不可重复读中的特例,一个事务覆盖另一事务已提交的更新数据。
以取款事务和支票转账事务例:
•取款事务包含以下步骤:
–(1)某银行客户在银行前台请求取款100元,出纳员先查询账户信息,得知存款余额为1000元。
–(2)出纳员判断出存款额超过了取款额,就支付给客户100元,并将账户上的存款余额改为900元。
•支票转账事务包含以下步骤:
–(1)某出纳员处理一转帐支票,该支票向一帐户汇入100元。出纳员先查询账户信息,得知存款余额为900元。
–(2)出纳员将存款余额改为1000元。
并发运行的两个事务导致脏读:
取款事务在T5时刻把存款余额改为900元,支票转账事务在T6时刻查询账户的存款余额为900元,取款事务在T7时刻被撤销,支票转账事务在T8时刻把存款余额改为1000元。
由于支票转账事务查询到了取款事务未提交的更新数据,并且在这个查询结果的基础上进行更新操作,如果取款事务最后被撤销,会导致银行客户损失100元。
并发运行的两个事务导致第二类更新丢失:
取款事务在T5时刻根据在T3时刻的查询结果,把存款余额改为1000-100元,在T6时刻提交事务。支票转账事务在T7时刻根据在T4时刻的查询结果,把存款余额改为1000+100
元。由于支票转账事务覆盖了取款事务对存款余额所做的更新,导致银行最后损失100元。
5.数据库的隔离级别:
(1).隔离级别与并发性能的关系:
(2).设置隔离级别的原则:
•隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
•对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读,而且具有较好的并发性能。尽管它会导致不可重复读、虚读
和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
(3)在mysql.exe程序中中设置隔离级别:
•每启动一个mysql.exe程序,就会获得一个单独的数据库连接。每个数据库连接都有个全局变量@@tx_isolation,表示当前的事务隔离级别。MySQL默认的隔离
级别为Repeatable Read。如果要察看当前的隔离级别,可使用如下SQL命令:
–mysql> select @@tx_isolation;
•如果要把当前mysql.exe程序的隔离级别改为Read Committed,可使用如下SQL命令:
–mysql> set transaction isolation level read committed;
(4)在Hibernate中设置隔离级别:
•在Hibernate的配置文件中可以显式的设置隔离级别。每一种隔离级别都对应一个整数:
–1:Read Uncommitted
–2:Read Committed
–4:Repeatable Read
–8:Serializable
•例如,以下代码把hibernate.cfg.xml文件中的隔离级别设为Read Committed:
hibernate.connection.isolation=2
对于从数据库连接池中获得的每个连接,Hibernate都会把它改为使用Read Committed隔离级别。