目录
事物
什么是事物
Transaction 其实是一组操作,里面包含了很多的单一的逻辑。只要有一个逻辑没有执行成功,那么这个事物就是失败的,这个时候所有的数据回滚到最初始的状态(回滚)
事物有什么作用
处理一组逻辑,如果其中一个逻辑不成功就是失败。
例子:银行转账,当A转账给B,突然断电,转账失败,数据回滚最初的状态
事物怎么用
1、事物不是针对的数据库,而是只针对数据库连接的对象(connection),如果再开一个对象,默认是自动提交的
2、事物会自动提交的
使用命令行方式演示事物
- 开启事务
start transaction - 提交或者回滚事物
commit:提交事物
rollback:回滚事物
- 关闭自动提交的功能
命令:show variables like ‘%commit%’
查询含有commit字符串的所有变量名称(variables变量的意思)
命令:set autocommit = off
关闭自动提交事物的功能(auto自动commit提交,off关闭)
- 演示事物
只有当commit才提交数据,数据库表中的数据才会改变,如果不提交,表中数据是不会变的。
原来表中数据zhangsan:1000 lishi:1100,zhangsan减去100,张三的数据在命令行中查的话是改变了,但是数据库中没变,可以使用数据库工具查看。只有commit提交之后才会改变
使用代码的方式演示事物
代码里面的事物,主要针对连接来的(Connection con)
事物,只会针对于 增 删 改 操作,查询不会修改数据,用不到事物
- 通过con.setAutoCommit(false); 来关闭自动提交事物的设置
- 通过con.commit(); 提交事物
- 通过con.rollback(); 回滚事物
@Test
public void textTransaction(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//默认是自动提交事物的
conn = JDBCUtil.getConn();
//关闭自动提交事物的功能
conn.setAutoCommit(false);
String sql = "update account set money = money - ? where id = ?";
ps = conn.prepareStatement(sql);
//给ID为1的用户减少 100 块钱
ps.setInt(1,100);
ps.setInt(2,1);
ps.executeUpdate();
int a = 10 / 0;
//给ID为2的用户增加 100 块钱
ps.setInt(1,-100);
ps.setInt(2,2);
ps.executeUpdate();
conn.commit();
} catch (SQLException e) {
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtil.closeResource(conn,ps,rs);
}
}
事物的特性ACID【面试】
- 原子性
指的是,事物中包含的逻辑,不可分割
- 一致性
指的是,事物执行的前后,数据的一致性
- 隔离性
指的是,事物在执行期间,不受其他事物的影响
- 持久性
指的是,事物执行完毕之后,数据应该持久的保存在磁盘上
事物的安全问题和隔离级别【面试】
安全问题
不考虑事物的隔离级别的设置,那么会出现 读问题,和 写问题
List item
读问题
- 脏读:指的是,一个事物读取到另一个事物还未提交的数据
- 不可重复读:一个事物读到了另一个事物提交的数据,导致两次查询前后数据不一致
- 幻读:一个事物读到了另一个事物已经提交的插入的数据,导致多次查询的结果不一致
写问题(丢失更新)
: 丢失更新
丢失更新解决办法
- 悲观锁
可以在查询的时候,加入 for update
- 乐观锁
要求程序员自己控制。
隔离级别
- 读未提交(Read Uncommitted)
引发“脏读”
指的是,一个事物读到另一个事物还未提交的数据,引发“脏读”,读取到的是数据库内存上的数据,而并非是真正磁盘上的数据
例子:
开启一个命令行窗口A, 开始事务,然后查询表中记录。 设置当前窗口的事务隔离> 级别为 读未提交 命令如下:
//查询当前事物的隔离级别 select @@tx_isolation; //设置当前窗口 事物 隔离级别为 read uncommitted set session transaction isolation level read uncommitted;另外在打开一个窗口B, 也开启事务, 然后执行 sql 语句, 但是不提交
在A窗口重新执行查询, 会看到B窗口没有提交的数据。
- 读已提交(Read committed )
解决脏读,引发不可重复读
与前面的读未提交刚好相反,这个隔离级别是 ,只能读取到其他事务已经提交的数据,那些没有提交的数据是读不出来的。
但是这会造成一个问题是:前后读取到的结果不一样。 发生了不可重复!!!, 所谓的不可重复读,就是不能执行多次读取,否则出现结果不一 。
例子1:
开启一个命令行窗口A, 设置A窗口的隔离级别为 读已提交,开始事务,然后查询表中记录。 设置当前窗口的事务隔离级别为 读已提交 命令如下:
set session transaction isolation level read committed;另外在打开一个窗口B, 也开启事务, 然后执行 sql 语句, 但是不提交
在A窗口重新执行查询, 是不会看到B窗口刚才执行sql 语句的结果,因为它还没有提交。
例子2:
- 设置A窗口的隔离级别为 读已提交
- A B 两个窗口都开启事务, 在B窗口执行更新操作。
- 在A窗口执行的查询结果不一致。 一次是在B窗口提交事务之前,一次是在B窗口提交事务之后。
- 重复读(Repeatable Read)
解决脏读,不可重复读。 不能解决幻读
Repeatable Read 【重复读】 - MySql 默认的隔离级别就是这个。
该隔离级别, 可以让事务在自己的会话中重复读取数据,并且不会出现结果不一样的状况,即使其他事务已经提交了,也依然还是显示以前的数据。
事物与事物之间是分开的,互不影响。
例子:
开启一个命令行窗口A, 开始事务,然后查询表中记录。 设置当前窗口的事务隔离级别为 重复读 命令如下:
set session transaction isolation level repeatable read;另外在打开一个窗口B, 也开启事务, 然后执行 sql 语句, 但是不提交
在A窗口重新执行查询, 是不会看到B窗口刚才执行sql 语句的结果,因为它还没有提交。
在B窗口执行提交。
在A窗口中执行查看, 这时候查询结果,和以前的查询结果一致。不会发生改变。
- 可串行化(Serializable)
解决 脏读、不可重复读、幻读
如果有一个连接的隔离级别设置为了串行化 ,那么谁先打开了事务, 谁就有了先执行的权利, 谁后打开事务,谁就只能等着,等前面的那个事务,提交或者回滚后,才能执行。 但是这种隔离级别一般比较少用。 容易造成性能上的问题。 效率比较低。
Serializable 可以防止上面的所有问题,但是都使用该隔离级别也会有些问题。 比如造成并发的性能问题。 其他的事务必须得等当前正在操作表的事务先提交,才能接着往下,否则只能一直在等着。
| 分类 | Value |
|---|---|
| 按效率划分,从高到低 | 读未提交 > 读已提交 > 可重复读 > 可串行化 |
| 按拦截度划分,从高到低 | 可串行化 > 可重复读 > 读已提交 > 读未提交 |