目录
指逻辑上一组操作,要么同时成功,要么同时失败。
mysql中,默认事务是自动事务的。一条sql一个事务
- start transaction -- 开启一个事务。以后的sql都在一个事务中。更改的内容不会自动提交。
- rollback -- 事务的回滚—同时失败的情况。--事务结束,并且全部失败,数据回复到开始之前的状态
- commit -- 事务的提交----同时成功---事务结束。全部成功。
- -- mysql默认类似这样
- start transaction ;
- update account set money = money -100 where name = ‘a’;
- commit;
- start transaction;
- update account set money = money +100 where name = ‘b’;
- commit;
我们也可以手动去控制事务
- -- 自身手动控制事务。
- start transaction ;
- update account set money = money -100 where name = ‘a’;
-
- update account set money = money +100 where name = ‘b’;
- commit;
| 方法 | 说明 |
|---|---|
| setAutoCommit(Boolean boolean) | 相当于start transaction 设置为false开启事务为手动 |
| rollback() | 事务回滚 |
| commit() | 事务提交 |
如下图,创建一个web工程,在WEB-INF下创建lib包,里面放入mysql连接包。
这里提供了mysql5版本和mysql8版本的jar包,对应的就是mysql的版本,如果是mysql5系列版本的就用5.16的jar包,如果是mysql8系列的版本就用8.0.16版本jar包。
链接:https://pan.baidu.com/s/1loxaN41BXlfdePT_8DDBDw
提取码:388i
放入后一定要导入,右键jar包点击ADD Library。

在src包下创建jdbc.properties文件
- jdbc.className=com.mysql.jdbc.Driver
- jdbc.url=jdbc:mysql://localhost:3306/test6
- jdbc.username=root
- jdbc.password=xxx
在src包下创建util包存放JDBC工具类
- public class JDBCUtil {
- public JDBCUtil() {
- }
-
- private static String className;
- private static String url;
- private static String username;
- private static String password;
- private static Connection conn = null;
-
- static {
- Properties pro = null;
- InputStream is = null;
- try {
- is = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
- pro = new Properties();
- pro.load(is);
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- try {
- is.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- className = pro.getProperty("jdbc.className");
- url = pro.getProperty("jdbc.url");
- username = pro.getProperty("jdbc.username");
- password = pro.getProperty("jdbc.password");
- //加载驱动
- try {
- Class.forName(className);
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- //获取连接
- try {
- conn = DriverManager.getConnection(url, username, password);
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
-
- public static Connection getConnection() {
- return conn;
- }
-
- public static void close(ResultSet rs, Statement stmt, Connection conn) {
- try {
- if (rs != null) {
- rs.close();
- rs = null;
- }
- } catch (SQLException e) {
- e.printStackTrace();
- }
- try {
- if (stmt != null) {
- stmt.close();
- stmt = null;
- }
- } catch (SQLException e) {
- e.printStackTrace();
- }
- try {
- if (conn != null) {
- conn.close();
- conn = null;
- }
-
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
-
- public static void close(Statement stmt, Connection conn) {
- try {
- if (stmt != null) {
- stmt.close();
- stmt = null;
- }
- } catch (SQLException e) {
- e.printStackTrace();
- }
- try {
- if (conn != null) {
- conn.close();
- conn = null;
- }
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- }
创建mysql数据库和表
- CREATE TABLE `account` (
- `name` varchar(20) NOT NULL,
- `money` int(255) DEFAULT NULL,
- PRIMARY KEY (`name`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
添加数据,模拟a向b进行转账,起始两人都是1000
为了演示不使用事务带来的问题,创建一个测试包,在其测试类中进行编写。
注意事项:该测试方法中finally一定要写commit()方法,因为事务一旦开启,如果不提交,则mysql默认会进行回滚,try里一旦报错,finally里面的代码也会被执行,为了使异常时不让其回滚,方便我们看到效果,我们需要手动加入提交。
- public class TransactionTest {
- @Test
- public void test1() {
- Connection conn = JDBCUtil.getConnection();
- Statement stmt = null;
- try {
- stmt = conn.createStatement();
- conn.setAutoCommit(false);
- //模拟a向b转账100元
- stmt.executeUpdate("update account set money= money-100 where name='a'");
- //手动创建异常 模拟转账期间出现网络异常问题
- int a = 1 / 0;
- //b收到a转账的100元
- stmt.executeUpdate("update account set money=money+100 where name='b'");
-
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- try {
- //finally块中加入commit提交,不论try是否报错,一定会进行提交,否则看不到效果
- conn.commit();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- JDBCUtil.close(stmt, conn);
- }
- }
- }
运行结果
可以看到结果报了运算异常的错误,这也是在情理之中的

那我们去数据库看看数据会发生什么

可以发现,这时就出现bug了,a少了100元这是对的,但是b缺没有收到a转出去的100元,这a会损失100元,这种严重的问题是绝不能出现在实际开发过程中的,有这种好事电商不亏死O(∩_∩)O。
那如何解决这种严重的BUG呢,就要使用事务中的回滚进行解决。
在catch里面加入了一个rollback回滚,这样try里面一旦报错,进入到catch块中就会进行回滚操作,把finally中的commit删除,因为finally中写commit只是为了让我们看到效果。
- public class TransactionTest {
- @Test
- public void test1() {
- Connection conn = JDBCUtil.getConnection();
- Statement stmt = null;
- try {
- stmt = conn.createStatement();
- conn.setAutoCommit(false);
- stmt.executeUpdate("update account set money= money-100 where name='a'");
- //手动创建异常
- int a = 1 / 0;
- stmt.executeUpdate("update account set money=money+100 where name='b'");
- conn.commit();
-
- } catch (Exception e) {
- try {
- //调用回滚方法
- conn.rollback();
- conn.commit();
- } catch (SQLException e1) {
- e1.printStackTrace();
- }
- e.printStackTrace();
- }finally{
- //释放资源
- JDBCUtil.close(stmt, conn);
- }
- }
- }
再次测试

可以发现,这次都没有改变,因为遵循了事务的原则,要不同时失败要不同时成功,一旦有一方失败,则整体失败。
| 方法 | 说明 |
|---|---|
| setSavepoint() | 设置回滚点 |
如果一遇到错误就回滚,那么也不一定是好事,比如现在有1w条数据,前面9998条数据都修改成功了,当9999条数据修改操作时出现了异常,那么前面9998也会被回滚掉,等于说修改操作白做了,所以,为了解决这一现象,需要使用回滚点,每10条数据设置一个回滚点,当第9999出现问题后,只会回滚9990-9999这10条数据,前面的9990条数据是不会被回滚的。
首先,先创建一个表为t_user
- CREATE TABLE `t_user` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `name` varchar(255) DEFAULT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
下面我演示一下1000条数据
- @Test
- public void test2() {
- Connection conn = JDBCUtil.getConnection();
- PreparedStatement pstmt = null;
- Savepoint save = null;
- try {
-
- pstmt = conn.prepareStatement("insert into user(name) values(?)");
- //开启事务
- conn.setAutoCommit(false);
- for (int i = 1; i < 1000; i++) {
- //在999条数据时出现异常
- if (i == 999) {
- int a = 10 / 0;
- }
- pstmt.setString(1, "itssl");
- pstmt.executeUpdate();
-
- //每10条数据创建一个回滚点
- if (i % 10 == 0) {
- save = conn.setSavepoint();
- }
- }
- conn.commit();
- } catch (Exception e) {
- try {
- //回滚并提交
- conn.rollback(save);
- conn.commit();
- } catch (SQLException e1) {
- e1.printStackTrace();
- }
- e.printStackTrace();
- }finally {
- JDBCUtil.close(pstmt,conn);
- }
- }

可以看到,前面的990条数据都是添加成功的,而后面10条会被回滚掉。
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位(最小的一个整体),事务中的操作要么都发生,要么都不发生。
一组操作时一个整体。不能分割。一致性(Consistency)
事务前后数据的完整性必须保持一致。
一致性和原子性相关。只有都成功或者,都失败(原子性) ,就可以保证事务的一致性。
事务的前后的内容一直。
转账: a 1000 b 1000 a给b转100;
转账之前 a和b的总额 2000
转账之后 也要2000;隔离性(Isolation)
事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。多个事务是独立存在的。多个事务不能够相互干扰。
a –b---事务A
c-d---事务B
如果A失败了。不能够影响B持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
一个事务读取了另外一个事务没有的提交的数据。非常严重。尽可能去避免掉脏读

比如:事务一开启事务后进行a减100元,b加100元,但是此时没有提交,而与此同时另一个事务开启事务进行读操作,select该表里的内容,发现a:900,b:1100,后来事务一提交事务(这里指回滚),后来事务二又查询了一次,发现数据又变回1000:1000,这会让事务二的用户感到迷惑
一个事务读取另外一个提交过的数据。造成另外一个事务,多次读取的【内容】不一致,数据的内容的改变。 update

一个事务读取另外一个事务已经提交的数据。但是这里面强调的数据数目的改变。insert,delete。比如该表原始数据为9个,事务一新增数据后提交,事务二查询该表,数量为10,而事务一又把数据给删除了,事务二又查询该表,发现数量又变为了9。

读未提交
Read uncommitted (读未提交) 最低级别,以上情况均无法保证。 两个人的操作都是一样
事务一
- set session transaction isolation level Read uncommitted;
- select @@tx_isolation;
- start transaction;
- update account set money = money-100 where name ='jack';
- update account set money = money+100 where name ='rose';
- rollback;
事务二
- set session transaction isolation level Read uncommitted;
- select @@tx_isolation;
- start transaction;
- select * from account;
读已提交
Read committed;(读已提交) 可避免脏读情况发生(避免不了虚读以及不可重复读)
- set session transaction isolation level Read committed;
- select @@tx_isolation;
- start transaction;
- update account set money = money-100 where name ='jack';
- update account set money = money+100 where name ='rose';
- commit;
- set session transaction isolation level Read committed;
- select @@tx_isolation;
- start transaction;
- select * from account;

可重复读
Repeatable read :可以避免脏读,不可重复度。
- set session transaction isolation level Repeatable read;
- select @@tx_isolation;
- start transaction;
- update account set money = money-100 where name ='jack';
- update account set money = money+100 where name ='rose';
- commit;
- set session transaction isolation level Repeatable read;
- select @@tx_isolation;
- start transaction;
- select * from account;
可串行化
串行化的可以避免所有的问题。 数据库让其他的事务进行等待,等待一个事务结束之后,这个事务再去操作。
- set session transaction isolation level Serializable;
- select @@tx_isolation;
- start transaction;
- update account set money = money-100 where name ='jack';
- update account set money = money+100 where name ='rose';
- commit;
- set session transaction isolation level Serializable;
- select @@tx_isolation;
- start transaction;
- select * from account;

性能比较
Serializable 性能最差:事务一个一个执行的。排队。Serializable < Repeatable read < Read committed < Read uncommitted
安全性比较
Serializable 安全性最好:所有问题避免掉。Serializable > Repeatable read > Read committed > Read uncommitted
Read uncommitted 避免不了最重问题,脏读。
Serializable:性能太差。mysql (默认)-- Repeatable read;(可重复读)
oracle(默认) -- Read committed;(读已提交)