设置事务的会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL read uncommitted; --读未提交
SET SESSION TRANSACTION ISOLATION LEVEL read committed; --读已提交
SET SESSION TRANSACTION ISOLATION LEVEL repeatable read; --可重复读
SET SESSION TRANSACTION ISOLATION LEVEL serializable; --序列化读
设置事务的全局隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL read uncommitted; --读未提交
SET GLOBAL TRANSACTION ISOLATION LEVEL read committed; --读已提交
SET GLOBAL TRANSACTION ISOLATION LEVEL repeatable read; --可重复读
SET GLOBAL TRANSACTION ISOLATION LEVEL serializable; --序列化读
查看事务隔离级别
SELECT @@TRANSACTION_ISOLATION; -- 查看事务的隔离级别
隔离级别 | 丢失修改 | 脏读 | 不可重复度 | 幻读 |
---|---|---|---|---|
Read Uncommitted | × | √ | √ | √ |
Read Committed | × | × | √ | √ |
Repeatable Read | × | × | × | √ |
Serializable | × | × | × | × |
丢失修改
:事务T1和T2读入同一数据并修改,T2的提交破坏了T1提交的结果,导致T1的修改丢失。这种情况在事务中不可能出现, 因为一个事务在使用UPDATE修改时会给被修改的记录加锁(排他锁), 其他事务必须等待此事务完成后才可以继续UPDATE脏读
:事务T1读到事务T2尚未提交的修改,这里无法确定T2是提交还是回滚,所以称读到的数据为脏数据。不可重复读
:事务T1多次读取同一个数据集合时,事务T1还没结束,事务T2对数据进行修改并提交,导致事务T1两次读取的数据不一样幻读
:事务T1第一次读到的数据是最近提交的数据(可以理解为“快照”),并且事务T1之后所有读操作都是以第一次读到的数据为准(读到的一直都是“快照”中的内容),即便是事务T2对数据进行修改,T1读到的数据同样是第一次读到的数据。X锁 | S锁 | 一致性保证 | |||||
操作结束释放 | 事务结束释放 | 操作结束释放 | 事务结束释放 | 不丢失修改 | 不读脏数据 | 可重复读 | |
一级封锁协议 | √ | √ | |||||
二级封锁协议 | √ | √ | √ | √ | |||
三级封锁协议 | √ | √ | √ | √ | √ |
两段锁协议 (TwoPhase Loking 简称2PL)
指所有的事务必须分为两个阶段对数据项加锁和解锁。
若并发执行的所有事务都遵循两段锁协议,那么这些事务的任何并发调度策略都是可串行化的,即事务遵循两段锁协议是可串行化的充分不必要条件
START TRANSACTION; --开启事务
SAVE POINT A; --设置回滚点A
ROLLBACK TO A; --回到回滚点A
COMMIT; --提交事务
ROLLBACK; --回滚事务
注意:
- 在Mysql中若未开启事务,则默认每一条DML语句作为事务自动提交
- 开启事务后,不要掺杂DML以外的语句,否则可能会导致事务无法回滚成功
mysql> -- 表的原始数据
mysql> SELECT * FROM emp;
+-------+--------+-----------+------+------------+---------+---------+--------+
| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO |
+-------+--------+-----------+------+------------+---------+---------+--------+
| 1111 | ghs | wu | NULL | NULL | 3000.00 | NULL | NULL |
| 1121 | aaaa | NULL | NULL | NULL | NULL | NULL | NULL |
| 2222 | wy | NULL | NULL | NULL | 1.00 | NULL | NULL |
| 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 |
| 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 |
| 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 |
| 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 |
| 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 |
| 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 |
| 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 |
| 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 |
| 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 |
| 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 |
| 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 |
| 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 |
| 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 |
| 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 |
+-------+--------+-----------+------+------------+---------+---------+--------+
17 rows in set (0.00 sec)
mysql>
mysql> -- 开启事务
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql>
mysql> -- 向表中插入empno=1的数据
mysql> INSERT INTO emp VALUES(1,'1','1',1,'1970-01-01',1.0,1.0,10);
Query OK, 1 row affected (0.00 sec)
mysql>
mysql> -- 向表中插入empno=2的数据
mysql> INSERT INTO emp VALUES(2,'A','2',2,'1970-01-01',2.0,2.0,10);
Query OK, 1 row affected (0.00 sec)
mysql> -- 设置回滚点A
mysql> SAVEPOINT A;
Query OK, 0 rows affected (0.00 sec)
mysql>
mysql>
mysql> -- 向表中插入empno=3的数据
mysql> INSERT INTO emp VALUES(3,'B','3',3,'1970-01-01',3.0,3.0,10);
Query OK, 1 row affected (0.00 sec)
mysql> -- 设置回滚点B
mysql> SAVEPOINT B;
Query OK, 0 rows affected (0.00 sec)
mysql>
mysql> -- 向表中插入empno=4的数据
mysql> INSERT INTO emp VALUES(4,'4','4',4,'1970-01-01',4.0,4.0,10);
Query OK, 1 row affected (0.00 sec)
mysql>
mysql>
mysql> -- 重新查看表数据(可以看到empno为1、2、3、4的数据都被插入到表中了)
mysql> SELECT * FROM emp;
+-------+--------+-----------+------+------------+---------+---------+--------+
| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO |
+-------+--------+-----------+------+------------+---------+---------+--------+
| 1 | 1 | 1 | 1 | 1970-01-01 | 1.00 | 1.00 | 10 |
| 2 | A | 2 | 2 | 1970-01-01 | 2.00 | 2.00 | 10 |
| 3 | B | 3 | 3 | 1970-01-01 | 3.00 | 3.00 | 10 |
| 4 | 4 | 4 | 4 | 1970-01-01 | 4.00 | 4.00 | 10 |
| 1111 | ghs | wu | NULL | NULL | 3000.00 | NULL | NULL |
| 1121 | aaaa | NULL | NULL | NULL | NULL | NULL | NULL |
| 2222 | wy | NULL | NULL | NULL | 1.00 | NULL | NULL |
| 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 |
| 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 |
| 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 |
| 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 |
| 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 |
| 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 |
| 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 |
| 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 |
| 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 |
| 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 |
| 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 |
| 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 |
| 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 |
| 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 |
+-------+--------+-----------+------+------------+---------+---------+--------+
21 rows in set (0.00 sec)
mysql>
mysql> -- 回到B回滚点
mysql> ROLLBACK TO B;
Query OK, 0 rows affected (0.00 sec)
mysql> -- 重新查看表数据(可以看出成功回滚到回滚点B)
mysql> SELECT * FROM emp;
+-------+--------+-----------+------+------------+---------+---------+--------+
| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO |
+-------+--------+-----------+------+------------+---------+---------+--------+
| 1 | 1 | 1 | 1 | 1970-01-01 | 1.00 | 1.00 | 10 |
| 2 | A | 2 | 2 | 1970-01-01 | 2.00 | 2.00 | 10 |
| 3 | B | 3 | 3 | 1970-01-01 | 3.00 | 3.00 | 10 |
| 1111 | ghs | wu | NULL | NULL | 3000.00 | NULL | NULL |
| 1121 | aaaa | NULL | NULL | NULL | NULL | NULL | NULL |
| 2222 | wy | NULL | NULL | NULL | 1.00 | NULL | NULL |
| 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 |
| 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 |
| 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 |
| 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 |
| 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 |
| 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 |
| 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 |
| 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 |
| 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 |
| 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 |
| 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 |
| 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 |
| 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 |
| 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 |
+-------+--------+-----------+------+------------+---------+---------+--------+
20 rows in set (0.00 sec)
mysql>
mysql> -- 回到A回滚点
mysql> ROLLBACK TO A;
Query OK, 0 rows affected (0.00 sec)
mysql> -- 重新查看表数据(可以看出成功回滚到回滚点A)
mysql> SELECT * FROM emp;
+-------+--------+-----------+------+------------+---------+---------+--------+
| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO |
+-------+--------+-----------+------+------------+---------+---------+--------+
| 1 | 1 | 1 | 1 | 1970-01-01 | 1.00 | 1.00 | 10 |
| 2 | A | 2 | 2 | 1970-01-01 | 2.00 | 2.00 | 10 |
| 1111 | ghs | wu | NULL | NULL | 3000.00 | NULL | NULL |
| 1121 | aaaa | NULL | NULL | NULL | NULL | NULL | NULL |
| 2222 | wy | NULL | NULL | NULL | 1.00 | NULL | NULL |
| 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 |
| 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 |
| 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 |
| 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 |
| 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 |
| 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 |
| 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 |
| 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 |
| 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 |
| 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 |
| 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 |
| 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 |
| 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 |
| 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 |
+-------+--------+-----------+------+------------+---------+---------+--------+
19 rows in set (0.00 sec)
mysql>
mysql> -- (回滚事务)结束事务
mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
mysql> -- 重新查看表数据(和原表数据相同)
mysql> SELECT * FROM emp;
+-------+--------+-----------+------+------------+---------+---------+--------+
| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO |
+-------+--------+-----------+------+------------+---------+---------+--------+
| 1111 | ghs | wu | NULL | NULL | 3000.00 | NULL | NULL |
| 1121 | aaaa | NULL | NULL | NULL | NULL | NULL | NULL |
| 2222 | wy | NULL | NULL | NULL | 1.00 | NULL | NULL |
| 7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL | 20 |
| 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 | 1600.00 | 300.00 | 30 |
| 7521 | WARD | SALESMAN | 7698 | 1981-02-22 | 1250.00 | 500.00 | 30 |
| 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 |
| 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 | 1250.00 | 1400.00 | 30 |
| 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 |
| 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 |
| 7788 | SCOTT | ANALYST | 7566 | 1987-04-19 | 3000.00 | NULL | 20 |
| 7839 | KING | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL | 10 |
| 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 | 1500.00 | 0.00 | 30 |
| 7876 | ADAMS | CLERK | 7788 | 1987-05-23 | 1100.00 | NULL | 20 |
| 7900 | JAMES | CLERK | 7698 | 1981-12-03 | 950.00 | NULL | 30 |
| 7902 | FORD | ANALYST | 7566 | 1981-12-03 | 3000.00 | NULL | 20 |
| 7934 | MILLER | CLERK | 7782 | 1982-01-23 | 1300.00 | NULL | 10 |
+-------+--------+-----------+------+------------+---------+---------+--------+
17 rows in set (0.00 sec)
package com.jsoft.transaction;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
public class Demo {
private static final DataSource DATA_SOURCE;
static {
Properties pros = new Properties();
try {
pros.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("druid.properties"));
DATA_SOURCE = DruidDataSourceFactory.createDataSource(pros);
} catch (IOException e) {
throw new RuntimeException();
} catch (Exception e) {
throw new RuntimeException();
}
}
@Test
public void test() {
try(Connection conn = DATA_SOURCE.getConnection();
PreparedStatement ps = conn.prepareStatement("");) {
// 查看事务未开启前表的内容
try (ResultSet rt = ps.executeQuery("SELECT * FROM emp")) {
StringBuilder sb = new StringBuilder();
while (rt.next()) {
sb.delete(0, sb.length());
sb.append(rt.getInt("empno")).append("\t");
sb.append(rt.getString("ename")).append("\t");
System.out.println(sb);
}
}
System.out.println("----------------------------------------");
// 关闭事务自动提交
conn.setAutoCommit(false); // ***注意***:不能等同于start transaction;或begin;
// 向表中插入empno=1,empno=2的数据
ps.addBatch("INSERT INTO emp VALUES(1,'1','1',1,'1970-01-01',1.0,1.0,10);");
ps.addBatch("INSERT INTO emp VALUES(2,'A','2',2,'1970-01-01',2.0,2.0,10);");
ps.executeBatch();
// 设置回滚点A
Savepoint a = conn.setSavepoint("A"); // 等同于 ---> savepoint A;
// 向表中插入empno=3,empno=4的数据
ps.addBatch("INSERT INTO emp VALUES(3,'B','3',3,'1970-01-01',3.0,3.0,10);");
ps.addBatch("INSERT INTO emp VALUES(4,'4','4',4,'1970-01-01',4.0,4.0,10);");
ps.executeBatch();
// 回到回滚点A
conn.rollback(a);
// 提交事务
conn.commit(); // 等同于 ---> commit;
// 查看事务结束后表的内容(如果成功将在表中可以看到只会新增emp=1和emp=2这两条记录)
try (ResultSet rt = ps.executeQuery("SELECT * FROM emp")) {
StringBuilder sb = new StringBuilder();
while (rt.next()) {
sb.delete(0, sb.length());
sb.append(rt.getInt("empno")).append("\t");
sb.append(rt.getString("ename")).append("\t");
System.out.println(sb);
}
}
// 开启事务自动提交
conn.setAutoCommit(true);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}