1 理解事务
- 一组sql语句(insert、update、delete),
全部成功整体才算成功,一个失败整体也算失败。
- 举例: 转账 a给b转账100元,a 和 b的账户中都有1000元, 转账完成后会是什么结果?
- 使用sql语句描述上述过程:
- 准备工作:
- 财务表 t_account , 里面有 姓名列 name 和 余额列 money。 a=1000, b=1000
正常情况下:
update t_account set money = money -100 where name='a';
update t_account set money = money +100 where name='b';
结果: a=900; b=1100
update t_account set money = money -100 where name='a';
发生异常;
update t_account set money = money +100 where name='b';
结果: a=900; b=1000
- 思考: 这种结果合理吗?如果在你的身上发生这样的事 你可以容忍吗?
- 如果出现了异常,怎么才算是合理的结果呢?
- 是不是两个操作 要么全成功,要么全失败?
- 事务的出现 解决上面的问题。 特点:要么全成功,要么全失败。
- 事务是如何处理异常情况的呢?
- 开启事务 (针对一组sql) a=1000 b=1000
update t_account set money = money -100 where name='a';
发生异常; 进行事务管理
update t_account set money = money +100 where name='b';
结果: a=1000 b=1000
开启事务
update t_account set money = money -100 where name='a'; a=900;
update t_account set money = money +100 where name='b'; b=1100
提交事务,使更改的内容生效。
结果:a=900 b=1100
- 简单总结:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201216022923854.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODkzNDk3,size_16,color_FFFFFF,t_70)
开启事务 | |
---|
执行sql语句群 | |
出现异常 回滚事务(撤销) | 一切正常 事务提交(生效) |
2 Mysql中的事务管理
- mysql 的事务
默认 自动打开,自动提交
。 一条sql就是一个事务,所以不需要 事务开启、事务回滚、事务提交。
开启事务: start transaction; | |
---|
执行sql语句群 | |
出现异常 事务回滚(撤销)事务结束 rollback; | 无异常 事务提交(生效) 事务结束 commit; |
start transaction;
rollback;
commit;
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201216023108115.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODkzNDk3,size_16,color_FFFFFF,t_70)
create table account(
name varchar(32),
money int
);
insert into account values('a', 1000);
insert into account values('b', 1000);
需求1:演示事务回滚
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201216023213423.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODkzNDk3,size_16,color_FFFFFF,t_70)
需求2:演示事务提交
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201216023232551.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODkzNDk3,size_16,color_FFFFFF,t_70)
3 java中的事务管理
开启事务: Connection.setAutoCommit(false) | |
---|
执行sql语句群 | |
出现异常 事务回滚(撤销)事务结束 Connection.rollback(); | 无异常 事务提交(生效)事务结束 Connection.commit(); |
3.1 演示未添加事务
@Test
public void demo1() {
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = JDBCUtils.getConnection();
String sql = "update account set money=money-100 where name='a'";
stmt = conn.prepareStatement(sql);
stmt.executeUpdate();
String sql2 = "update account set money=money+100 where name='b'";
stmt = conn.prepareStatement(sql2);
stmt.executeUpdate();
System.out.println("一切ok");
} catch (Exception e) {
e.printStackTrace();
System.out.println("出现异常");
} finally {
JDBCUtils.release(conn, stmt);
}
}
3.2 演示添加事务
- 需求:a 向 b 转账 100元, 模拟异常。(强调事务的重要性:要么全执行,要么全撤销。)
@Test
public void demo2() {
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = JDBCUtils.getConnection();
conn.setAutoCommit(false);
String sql = "update account set money=money-100 where name='a'";
stmt = conn.prepareStatement(sql);
stmt.executeUpdate();
String sql2 = "update account set money=money+100 where name='b'";
stmt = conn.prepareStatement(sql2);
stmt.executeUpdate();
int j = 1 / 0;
System.out.println("一切ok, 提交事务");
conn.commit();
} catch (Exception e) {
e.printStackTrace();
System.out.println("出现异常,回滚事务");
try {
if (conn != null) {
conn.rollback();
}
} catch (Exception e1) {
e1.printStackTrace();
}
} finally {
JDBCUtils.release(conn, stmt);
}
}
- 注意事项
- 1、Connection 和 PrepareStatement 对象引入时,必须是
java.sql 包下
的。 - 2、开启事务 conn.setAutoCommit(false)
必须在 Connection对象获取之后。
- 3、抓异常时,
最好选得大一些
。因为 数/0
报的是 算术异常
,不在sql异常范围内
,所以最好改成Exception。
3.3 DBUtils事务操作
Connection对象的方法名 | 描述 |
---|
conn.setAutoCommit(false) | 开启事务 |
new QueryRunner() | 创建核心类,不设置数据源(手动管理连接) |
query(conn , sql , handler, params ) 或 update(conn, sql , params) | 手动传递连接, 执行SQL语句CRUD |
DbUtils.commitAndCloseQuietly(conn) | 提交并关闭连接,不抛异常 |
DbUtils.rollbackAndCloseQuietly(conn) | 回滚并关闭连接,不抛异常 |
public class TransactionDemo2 {
@Test
public void test1() throws SQLException {
Connection conn = null;
try {
conn = DruidUtils.getConnetion();
conn.setAutoCommit(false);
QueryRunner qr = new QueryRunner();
String sql = "update account set money=money-? where name=?";
qr.update(conn, sql, 1000, "jack");
int n =1/0;
sql = "update account set money=money+? where name=?";
qr.update(conn, sql, 1000, "rose");
DbUtils.commitAndCloseQuietly(conn);
} catch (Exception e) {
e.printStackTrace();
DbUtils.rollbackAndCloseQuietly(conn);
}
}
}
4 事务特性:ACID
原子性
(Atomicity)原子性是指事务是一个不可分割
的工作单位,事务中的操作要么都发生,要么都不发生
。一致性
(Consistency)事务前后数据的完整性必须保持一致,也就是说回滚前后数据一致
。隔离性
(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰
,多个并发事务之间数据要相互隔离。持久性
(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的
,接下来即使数据库发生故障也不应该对其有任何影响。
5 并发访问问题
脏读
:一个事务读到
了另一个事务未提交
的数据.
不可重复读
:一个事务读到了另一个事务已经提交(update
)的数据。引发另一个事务,在事务中的多次查询结果不一致
。主要是数据内容
虚读 /幻读
:一个事务读到了另一个事务已经提交(insert
)的数据。导致另一个事务,在事务中多次查询的结果不一致
。主要是条数
6 隔离级别:解决问题
- 数据库规范规定了
4种
隔离级别,分别用于描述两个事务并发
的所有情况
。
read uncommitted 读未提交
,一个事务读到另一个事务没有提交的数据。
- a)存在:3个问题(脏读、不可重复读、虚读)。
- b)解决:0个问题
read committed 读已提交
,一个事务读到另一个事务已经提交的数据。
- a)存在:2个问题(不可重复读、虚读)。
- b)解决:1个问题(脏读)
repeatable read:可重复读
,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
- a)存在:1个问题(虚读)。
- b)解决:2个问题(脏读、不可重复读)
serializable 串行化
,同时只能执行一个事务,相当于事务中的单线程。
-
a)存在:0个问题。
-
b)解决:3个问题(脏读、不可重复读、虚读)
安全和性能对比
- 安全性:serializable > repeatable read > read committed > read uncommitted
- 性能 : serializable < repeatable read < read committed < read uncommitted
- 也就是说
两者不可兼得
- 常见数据库的默认隔离级别:
- MySql:
repeatable read
–可重复读,解决不可重复读和脏读
- Oracle:
read committed
–读已提交,解决不可重复读
7 小结
- 事务:逻辑上的一组sql操作,要么全执行,要么全不执行。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201216024303780.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2ODkzNDk3,size_16,color_FFFFFF,t_70)