数据库事务
事务概述
一组要么同时执行成功,要么同时失败的SQL语句。是数据库操作的一个不能分割执行单元。
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理
事务开始于
- 连接到数据库上,并执行一条DML语句insert、update或delete
- 前一个事务结束后,又输入了另一条DML语句
事务结束于
- 执行commit或rollback语句。
- 执行一条DDL语句,例如create table语句,在这种情况下,会自动执行commit语句。
- 执行一条DDL语句,例如grant语句,在这种情况下,会自动执行commit。
- 断开与数据库的连接
- 执行了一条DML语句,该语句却失败了,在这种情况中,会为这个无效的DML语句执行rollback语句。
事务的四大特点
- Atomicity(原子性)
表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败
- Consistency(一致性)
表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前状态
- Isolation(隔离性)
事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。
- Durability(持久性)
持久性事务完成之后,它对于系统的影响是永久性的。
案例演示:
CREATE TABLE account(
id INT PRIMARY KEY,
NAME VARCHAR(20) NOT NULL,
money DOUBLE(10,2)
)
public static void main(String[] args) {
Connection connection=null;
PreparedStatement pstat1=null;
PreparedStatement pstat2=null;
//1注册驱动
try {
Class.forName("com.mysql.jdbc.Driver");
//2获取连接
connection=DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "root");
//3创建命令
//3.1开启事务 ,设置事务自动提交为false
connection.setAutoCommit(false);
pstat1=connection.prepareStatement("update account set money=money-1000 where name='张莎强'");
pstat1.executeUpdate();
//int c=10/0;
pstat2=connection.prepareStatement("update account set money=money+1000 where name='小苍'");
pstat2.executeUpdate();
System.out.println("转账成功...");
//3.2提交事务
connection.commit();
} catch (Exception e) {
System.out.println("出现异常");
try {
connection.rollback();//出现问题,要回滚(撤销事务做过的修改)
connection.commit();//可加也不不加
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}finally {
if(pstat1!=null){
try {
pstat1.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(pstat2!=null){
try {
pstat2.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Mysql支持事务语句
#开启事务
START TRANSACTION; # connection.setAutoCommit(false);
UPDATE account SET money=money-1000 WHERE id=1;
UPDATE account SET money=money+1000 WHERE id=2;
#提交事务
COMMIT;#connection.commit();
#回滚
ROLLBACK; #connection.rollback();
事务的隔离级别【重要】
SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别出现不可重复读(Nonrepeatable Read)问题,因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
Repeatable Read可重读
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读(Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻读” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
Serializable 可串行化
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。效率最低的。
【理解】
商品交易:A(卖家)B(买家)
- A:设置事务为Read Uncommitted,B开启事务,当买家完成付款动作后,不需要提交,A就能够看到已经修改过后的数据,然后发货,如果这时,B执行的不是提交,而是回滚,这时钱还是不会到A的帐上
- A:设置事务为Read Committed,A只能看到B已经提交过的数据,不会出现1中出现的问题,但是如果A在查询今天的账单的过程中(查询3遍确认),如果有人在A查询的过程中修改了账单,那么A查询的数据就会不同,解决见下
- A:设置事务为Repeatable Read,在查询的过程中,只要不提交,查询的结果就是一样的,即使有人在查询的过程中修改了
- A:设置事务为Serializable,相当于对数据库加锁,一个账户同时只能有一个人操作
这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。
例如:
脏读(Drity Read):某个事务已更新一份数据未提交前,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
JDBC中事务的应用
JDBC连接默认处于自动提交模式,则每个SQL语句在完成后都会提交到数据库。
事务使您能够控制是否和何时更改应用于数据库。它将单个SQL语句或一组SQL语句视为一个逻辑单元,如果任何语句失败,则整个事务将失败。
要启用手动事务支持,而不是JDBC驱动程序默认使用的自动提交模式,请使用Connection对象的setAutoCommit方法。如果将boolean false传递给setAutoCommit(),则关闭自动提交。我们可以传递一个布尔值true来重新打开它
事务的提交和回滚
完成更改后,我们要提交更改,然后在连接对象上调用commit方法,如下所示:
conn.commit( );
否则,要使用连接名为conn的数据库回滚更新,请使用以下代码 -
conn.rollback( );
try{
//开启事务
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
String SQL = "INSERT INTO Employees VALUES (106, 20, 'Rita', 'Tez')";
stmt.executeUpdate(SQL);
//有可能出现异常
String SQL = "INSERT IN Employees VALUES (107, 22, 'Sita', 'Singh')";
stmt.executeUpdate(SQL);
// 没有错误提交
conn.commit();
}catch(SQLException se){
//出现错误回滚
conn.rollback();
conn.commit();
}
Savepoint
新的JDBC 3.0 Savepoint接口为您提供了额外的事务控制。
设置保存点时,可以在事务中定义逻辑回滚点。如果通过保存点发生错误,则可以使用回滚方法来撤消所有更改或仅保存在保存点之后所做的更改。
Connection对象有两种新的方法来帮助您管理保存点 -
- setSavepoint(String savepointName):定义新的保存点。它还返回一个Savepoint对象。
- releaseSavepoint(Savepoint savepointName):删除保存点。请注意,它需要一个Savepoint对象作为参数。此对象通常是由setSavepoint()方法生成的保存点。
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
Savepoint savepoint1 = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/school", "root", "root");
// Assume a valid connection object conn
conn.setAutoCommit(false);
stmt = conn.createStatement();
// set a Savepoint
String SQL = "INSERT INTO Employees VALUES (106, 20, 'Rita', 'Tez');";
stmt.executeUpdate(SQL);
savepoint1 = conn.setSavepoint("Savepoint1");
// Submit a malformed SQL statement that breaks
SQL = "INSERT IN Employees " + "VALUES (107, 22, 'Sita', 'Tez')";
stmt.executeUpdate(SQL);
// If there is no error, commit the changes.
conn.commit();
System.out.println("执行成功");
} catch (Exception e) {
// If there is any error.
try {
e.printStackTrace();
conn.rollback(savepoint1);
conn.commit();
System.out.println("回滚");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}