写在开头:本文是学习尚硅谷JavaWeb的个人笔记,便于自己后期复习,也供各位参考评论,指出错误共同进步。
1 什么叫数据库事务?
事务:一组逻辑单元操作单元,使数据从一种状态转换到另一种状态。(将AA的账户余额转100到B的账户上。这样要经过两个update操作,使得balances由原来的1000、1000转变为900、1100,这就是数据库事务。)
一组逻辑操作单元:一个或多个DML(数据的增删改查)
事务处理原则 :
1. 保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时 ,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(**rollback)**到最初状态。
和上面例子一样,要么两人(AA、BB)数字都改变,如果出现异常就回滚,两者都不改变。
2.数据一旦提交,就不可回滚。(我们要做的操作就是不能让数据提交,后面再提交)
3.哪些操作会导致数据的自动提交?
(1)DDL操作一旦执行,都会自动提交。(DLL操作是对数据表的创建create、修改alter、删除drop等操作),set autocommit = false 对DDL操作失效(反正他就是要自动提交)。
(2)DML默认情况下,一旦执行,就会自动提交。(数据的增删改查),(这里解决办法是我们可以通过set autocommit = false的方式取消DML操作的自动提交。)
(3)默认在关闭连接时,会自动的提交数据。(这里解决办法就是:让两个事务用一个连接conn,整个转账操作做完之后在关闭连接)
为了让多个 SQL 语句作为一个事务执行:
- 1、调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
- 2、在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
- 3、在出现异常时,调用 rollback(); 方法回滚事务
- 4、若此时 Connection 没有被关闭, 则需要恢复其自动提交状态
实例一:未考虑数据库事务的转账操作
(问题所在:如果程序现故障可能得到的结果就是AA转账成功但是BB没有收到)
public class TransactionTest {
/**********************未考虑数据库事务的转账操作***************
* 针对数据表user_table来说,
* AA用户给BB用户转账100
*/
@Test
public void testUpdate() {
String sql1 = "update user_table set balance=balance-100 where user=?";
update(sql1,"AA");
//网络异常
System.out.println(10/0);//问题所在:如果程序在这里出现故障那得到的结果就是AA转账成功但是BB没有收到
String sql2 = "update user_table set balance=balance+100 where user=?";
update(sql2,"BB");
System.out.println("转账成功");
}
public int update(String sql,Object ...args){//sql中占位符的个数与可变形参的长度相同!
Connection conn = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
conn = JDBCUtils.getConnection();
//2.预编译sql语句,返回PreparedStatement的实例
ps = conn.prepareStatement(sql);
//3.填充占位符
for(int i = 0;i < args.length;i++){
ps.setObject(i + 1, args[i]);//小心参数声明错误!!
}
//4.执行
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally{
//5.资源的关闭
JDBCUtils.closeResource(conn, ps);
}
return 0;
}
实例二:考虑数据库事务的转账操作
//***************考虑事务的转账操作***********************
@Test
public void testUpdatewidthTx() {
Connection conn=null;//两步操作用一个连接串联起来了
try {
conn = JDBCUtils.getConnection();
System.out.println(conn.getAutoCommit());
//1.取消数据的自动提交
conn.setAutoCommit(false);
String sql1 = "update user_table set balance=balance-100 where user=?";
update(conn,sql1,"AA");//两者公用一个conn
//网络异常
//System.out.println(10/0);
String sql2 = "update user_table set balance=balance+100 where user=?";
update(sql2,"BB");
System.out.println("转账成功");
conn.commit();//2.提交数据
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
try {
conn.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}//3.产生异常我们回滚数据
}finally {
JDBCUtils.closeResource(conn, null);//最后我们采取关闭连接
}
}
//通用增删改操作version2.0(从外部传一个连接进来)
public int update(Connection conn,String sql,Object ...args){//sql中占位符的个数与可变形参的长度相同!
PreparedStatement ps = null;
try {
//2.预编译sql语句,返回PreparedStatement的实例
ps = conn.prepareStatement(sql);
//3.填充占位符
for(int i = 0;i < args.length;i++){
ps.setObject(i + 1, args[i]);//小心参数声明错误!!
}
//4.执行
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally{
// 5.恢复每次DML操作的自动提交功能
try {
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
//5.资源的关闭
JDBCUtils.closeResource(null, ps);//连接不关,后面还要用
}
return 0;
}
2 事务的ACID属性
2.1 事物的四个属性
- 原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
- 隔离性(Isolation) 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。(最重要)
- 持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
2.2 数据的并发问题
对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
- 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。(一般解决脏读就好了,因为人家都没提交,你读了也没有用)
T1读取了T2 更新但还没有被提交数据,这种没有提交被我们读到的数据就叫脏读,一旦T1回滚,T2再去读取,两次读取的数据就不一样。
- 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
比如在火车站买票,点击进去有200张票,过了一会儿我们并没有关闭这个事务,再刷新,票数就达到了210张。两次读到的数据不一样叫不可重复读。(一般来说这个可以不解决)
- 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如 果 T1 再次读取同一个表, 就会多出几行。
事务T再去查的时候发现表中多了几行,跟幻觉一样。
数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题
一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。
2.3 四种隔离级别
数据库提供的4种事务隔离级别:
表格复制
隔离级别 | 描述 |
---|---|
READ UNCOMMITTED(读未提交数据) | 允许事务读取未被其他事务提交的变更;脏读,不可重复度和幻读的问题都会出现(三个问题都不能解决) |
READ COMMITED(读已提交数据) | 只允许事务读取已经被其他事务提交的变更;可以避免脏读,但不可重复读和幻读的问题仍然可能出现(解决脏读) |
REPEATABLE READ(可重复读) | 确保事务可以多次从一个字段中读取相同的值;在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读的问题仍然存在(解决脏读、幻读) |
SERIALIZABLE(串行化) | 确保事务可以从一个表中读取相同的行;在这个事务持续期间,禁止其他事务对该表进行插入,更新和删除操作;所有并发问题都可以避免,但性能十分低下(三个问题都能解决) |
越往下并发性越差,一致越好。
可重复读解决的方法是:如果事务T1去查表结果是1,,现在事务T2改了表的值变成了2,并且提交了,但现在事务T1并没有断掉,T1再去查表仍然是1,只有当事务T1断了,查到的结果才是2.
串行化解决的方法是:同上,只要在同一个事务内,查到的东西都一样。只有关掉这个事务,重新打开新的事务(比如在一个网页上我们关掉网页重新打开)才能看到更新的行,列。
Oracle 支持的 2 种事务隔离级别:READ COMMITED,SERIALIZABLE; Oracle 默认的事务隔离级别为:READ COMMITED(读已经提交了的)
Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ(可以反复读的)