四个特征ACID
一个业务通常需要多条DML(增删改)语句共同联合起来(同时成功或失败)才能完成,那么事务其实就是这个完整的业务逻辑
,是一个最小的工作单元不可再分
MySQL默认情况下是支持自动提交事务
的,即每执行一条DML语句会自动提交一次,自动提交事务并不符合我们的开发需求
start transaction
事务在执行过程中每一条DML(增删改)的操作都会被记录到InnoDB存储引擎
提供的事务性活动的日志文件
中,最后根据需求决定是提交还是回滚事务
commit,提交事务(标志事务成功结束)
:,执行之前批量的DML操作然后将数据全部持久化到数据库表中,然后清空事务性活动的日志文件rollback,回滚事务(标志事务失败结束)
: 将之前所有的DML操作全部撤销(回滚永远都是只能回滚到上一次的提交点),然后清空事务性活动的日志文件insert,delete和update
语句,不能回滚select,create,drop,alter
这些操作因为没有任何意义事务的四个处理过程
第一步: 开启事务start transaction
第二步: 执行核心业务代码包含批量的DML语句
第三步: 如果核心业务代码处理过程中没有出现异常则提交事务commit
第四步: 如果核心业务代码处理过程中出现异常回滚事务rollback
事务的四个特性原子性,一致性,隔离性,持久性
特性 | 描述 |
---|---|
Atomicity(原子性) | 说明事务是最小的工作单元 ,整个事务中的所有操作必须作为一个单元即全部完成或全部取消 |
Consistency(一致性) | 在同一个事务当中所有操作必须同时成功或失败,保证数据库从一个一致性状态转换到另一个一致性状态 |
Isolation(隔离性) | 当A事务和B事务操作同一张表 的时候,事务之间需要具有一定的隔离性事务并发 就是一个线程一个事务 |
Durability(持久性) | 事务一旦提交后该事务对数据库所作的更改将持久地保存在数据库之中,即使数据库发生故障也无法回滚 |
事务的提交与回滚
-- 使用bjpowernode数据库
mysql> use bjpowernode;
-- 查询dept_bak表中的数据
mysql> select * from dept_bak;
+--------+-------+------+
| DEPTNO | DNAME | LOC |
+--------+-------+------+
| 10 | abc | bj |
+--------+-------+------+
1 row in set (0.00 sec)
-- 开启事务,关闭事务的自动提交机制
mysql> start transaction;
-- 向dept_bak表中插入数据
mysql> insert into dept_bak values(20,'abc','bj');
-- 提交事务
mysql> commit;
-- 再次查看表中的数据
mysql> select * from dept_bak;
+--------+-------+------+
| DEPTNO | DNAME | LOC |
+--------+-------+------+
| 10 | abc | bj |
| 20 | abc | tj |
+--------+-------+------+
-- 回滚事务只能回滚到上一次的提交点
mysql> rollback;
-- 再次查看表中的数据
mysql> select * from dept_bak;
+--------+-------+------+
| DEPTNO | DNAME | LOC |
+--------+-------+------+
| 10 | abc | bj |
| 20 | abc | tj |
+--------+-------+------+
事务的隔离级别(三大读)
InnoDB存储引擎
实现了四个隔离级别用以控制每个事务所做的修改,并将修改通告至其它并发的事务
不可重复读和幻读的区别
: 不可重复读重点是事务A读取到了事务B修改后的数据,幻读重点在于事务A按照固定的查询语句查询记录,读取到的记录可能新增/减少名称 | 描述 |
---|---|
脏读 | 在事务A中读取到了事务B未提交到数据库的数据,由于事务B可能回滚,所以事务A可能会读取到数据库中不存在的数据 |
不可重复读(读-读) | 事务B在事务A多次读取同一数据的过程中对事务A读取的数据做了更新操作 并提交,导致事务A每次读取的数据都不一致 |
幻读(读-写) | 某一次的读操作得到的结果无法支撑后续的业务操作,多事务并发的情况下一定会存在幻读现象 在可重复读的情况下即事务读取到的永远是自己刚开启事务时的数据 ,此时事务B插入一条id=5 的记录并提交了事务,但是事务A由于查询不到事务B插入的记录,结果就是事务A插入该记录时报错 |
隔离级别 | 描述 | 脏读 | 不可重复读 | 幻读 | 加锁读 |
---|---|---|---|---|---|
读未提交(理论级别)(READ_UNCOMMITTED) | 在一个事务中可以看到其他事务未提交的修改数据 ,大多数的数据库隔离级别都是二档起步 | 有 | 有 | 有 | 不加锁 |
读已提交 (Oracle默认级别) (READ_COMMITTED) | 在一个事务中只能看到其他事务已经提交的修改数据 ,这种隔离级别每次读到的都是真实数据 | 无 | 有 | 有 | 不加锁 |
可重复读(MySQL默认级别) (REPEATABLE_READ) | 一个事务中多次执行相同的SELECT语句得到的是相同的结果,永远读取的都是自己刚开启事务时的数据 ,不管其他事务是否提交了修改数据 | 无 | 无 | 有 | 不加锁 |
序列化(最高隔离级别) SERIALIZABLE | 一个事务与其他事务完全地隔离,每一次读取到的数据都是最真实的,但是所有事务只能排队执行,不支持并发所以效率最低 | 无 | 无 | 无 | 加锁 |
设置服务的隔离级别
设置MySQL服务默认的事务隔离级别: 在my.ini
配置文件中配置[mysqld]选项的transaction-isolation
属性
#该选项值可以是READ-UNCOMMITTED , READ-COMMITTED , REPEATABLE-READ , SERIALIZABLE
[mysqld]
#设置隔离级别,如果没有设置默认是REPEATABLE-READ
transaction-isolation00 = READ-COMMITTED
动态设置MySQL服务的隔离级别:SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL <isolation-level>
,设置完后需退出MySQL服务重新进入
isolation-level(隔离级别)
: 隔离级别可以是READ UNCOMMITTED
,READ COMMITTED
,REPEATABLE READ
,SERIALIZABLE
全局级(GLOBAL)
: 设置的隔离级别对所有的会话有效
会话级(SESSION)
: 默认设置的隔离级别只对当前的会话有效
-- 设置隔离级别为READ COMMITTED ,默认只对当前的会话有效
mysql> SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 设置会话级隔离级别为READ COMMITTED
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 设置全局级隔离级别为READ COMMITTED :
mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
查看MySQL服务当前的隔离级别: 服务器变量tx_isolation
默认保存着当前的会话隔离级别
-- 查看当前隔离级别,默认是会话级别
mysql> SELECT @@tx_isolation;
-- 查看当前会话的隔离级别
mysql> SELECT @@session.tx_isolation;
-- 查看当前全局的隔离级别
mysql> SELECT @@global.tx_isolation;
验证四种隔离界别
验证read uncommited
读未提交
事务A | 事务B |
---|---|
s1>use bjpowernode; | s2>set global transaction isolation level read uncommitted; |
s1>create table tx ( id int(11),num int (10)); | s2>use bjpowernode; |
s1>start transaction; | s2>start transaction; |
s2>select * from tx;(空表) | |
s1>insert into tx values (1,10);事务A还没有提交 | |
s2>select * from tx;事务B读取到了事务A未提交的数据 | |
s1>rollback; | |
s2>select * from tx; |
验证read commited
读已提交
事务A | 事务B |
---|---|
s1>use bjpowernode | s2> set global transaction isolation level read committed; |
s1>start transaction; | s2>use bjpowernode; |
s2>start transaction; | |
s2>select * from tx;(空表) | |
s1>insert into tx values (1,10); | |
s2>select * from tx;由于事务A没有提交数据,事务B的读取的还是空表 | |
s1>commit;事务A提交了数据 | |
s2>select * from tx;事务B读取到了事务A已提交的数据 |
验证repeatable read
可重复读
事务A | 事务B |
---|---|
s1>use bjpowernode | s2>set global transaction isolation level repeatable read; |
s1>start transaction; | s2>use bjpowernode; |
s2>start transaction; | |
s2>select * from tx;事务B开启时的数据 | |
s1>insert into tx values (1,10); | |
s1>commit;事务A提交了数据 | |
s2>select * from tx;即使事务A提交了数据,事务B读取的还是自己开启事务时的数据 |
验证serializable
序列化
事务A | 事务B |
---|---|
s1>use bjpowernode; | s2>set global transaction isolation level serializable; |
s1>start transaction; | s2>use bjpowernode; |
select * from tx; | s2>start transaction; |
s1>insert into tx values (1,10);事务A在操作tx表时,只要当前事务不结束,其他事务就不能访问tx表 | |
s2>select * from tx;事务B不能访问tx表 |
乐观锁和悲观锁
乐观锁: 支持事务并发,只不过需要设置给数据库中的表设置一个版本号字段
悲观锁(行级锁): 事务必须排队执行
for update
,表示将查询到的所有记录整行锁住,此时只要事务A不提交事务,其他事务就无法对这些记录进行修改操作第一步: 开启一个事务A专门进行查询
,并且使用行级锁
锁住查询到的记录
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
// 使用工具类获取连接对象
conn = DBUtil.getConnection();
// 将数据库的自动提交功能改为手动提交,即开启数据库事务的功能
conn.setAutoCommit(false);
// 获取预编译的数据库操作对象
String sql = "select ename from emp where job = ? for update";
ps = conn.prepareStatement(sql);
ps.setString(1, "MANAGER");
rs = ps.executeQuery();
while(rs.next()){
System.out.println(rs.getString("ename"));
}
// 提交事务
conn.commit();
// 关闭资源
DBUtil.close(conn,ps,rs)
第二步: 开启一个事务B修改锁定的记录
,此时发现只要事务A不结束,事务B的修改操作就无法执行
Connection conn = null;
PreparedStatement ps = null;
// 使用工具类获取连接
conn = DBUtil.getConnection();
// 关闭数据库的自动提交功能改为手动提交, 即开启数据库事务的功能
conn.setAutoCommit(false);
// 获取预编译的数据库操作对象
String sql = "update emp set sal = sal * 1.1 where job = ? ";
ps = conn.prepareStatement(sql);
ps.setString(1, "MANAGER");
int count = ps.executeUpdate();
System.out.println(rs.getString(count));
// 提交事务
conn.commit();
// 关闭资源
DBUtil.close(conn,ps,null)