🌈个人主页 :一条泥憨鱼 (欢迎各位大佬莅临)

前言:
"事务(Transaction)"是数据库开发里非常重要的知识。
简单来说:
事务就是"一组操作,要么全部成功,要么全部失败"。
它主要用于:
-
转账
-
下订单
-
库存扣减
-
支付系统
-
多表更新
这些场景都不能只执行一半,否则数据就会出错。
一、为什么需要事务?
先看一个经典案例:
银行转账
假设:
-
张三账户:1000 元
-
李四账户:1000 元
现在张三给李四转 200 元。
正常流程:
1. 张三 -200
2. 李四 +200
如果没有事务会发生什么?
假设:
张三扣钱成功
↓
程序突然崩溃
↓
李四没收到钱
结果:
张三:800
李四:1000
钱凭空消失了,这就是严重的数据错误。
有事务之后
事务会保证:
要么两步都成功
要么两步都失败
这样数据永远正确。
二、事务的四大特性(ACID)
事务最核心,也是老生常谈的知识。
1. Atomicity(原子性)
原子性:
一个事务中的所有操作,要么全部完成,要么全部不完成。
比如:
A扣钱
B加钱
不能只完成一半。
2. Consistency(一致性)
事务执行前后:
数据必须保持正确状态。
例如:
转账前总金额:2000
转账后总金额:仍然2000
3. Isolation(隔离性)
多个事务之间:
彼此互不干扰。
比如:
用户A正在修改数据
用户B不能读到中间状态
4. Durability(持久性)
事务提交后:
数据永久保存。
即使数据库崩溃,数据也不会丢失。
三、MySQL 中事务的基本操作
MySQL 提供了事务控制语句。
1. 开启事务
START TRANSACTION;
或者:
BEGIN;
2. 提交事务
COMMIT;
提交后:
数据永久生效
3. 回滚事务
ROLLBACK;
回滚后:
撤销事务中的所有操作
四、事务代码示例
创建测试表
账户表
java
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
money DOUBLE
);
插入数据
java
INSERT INTO account VALUES(NULL,'张三',1000);
INSERT INTO account VALUES(NULL,'李四',1000);
五、没有事务的问题
java
UPDATE account SET money = money - 200 WHERE name='张三';
-- 程序崩溃
UPDATE account SET money = money + 200 WHERE name='李四';
这样会导致数据错误。
六、使用事务解决问题
java
START TRANSACTION;
UPDATE account SET money = money - 200 WHERE name='张三';
UPDATE account SET money = money + 200 WHERE name='李四';
COMMIT;
如果发生异常
java
START TRANSACTION;
UPDATE account SET money = money - 200 WHERE name='张三';
-- 出现错误
ROLLBACK;
这样:
张三不会扣钱
数据库恢复原状。
七、Java 中操作事务
JavaWeb 开发里最常见。
一般使用:
JDBC
MyBatis
Spring
这里先讲 JDBC 原理。
八、JDBC 默认事务机制
JDBC 默认:
自动提交事务
即:
每执行一条SQL
自动commit
所以:
conn.setAutoCommit(false);
非常重要。
九、JDBC 事务完整代码
转账案例
java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class Demo {
public static void main(String[] args) {
Connection conn = null;
try {
// 获取连接
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test",
"root",
"123456");
// 关闭自动提交
conn.setAutoCommit(false);
// 张三减200
String sql1 =
"update account set money = money - 200 where name=?";
PreparedStatement ps1 =
conn.prepareStatement(sql1);
ps1.setString(1, "张三");
ps1.executeUpdate();
// 模拟异常
int a = 1 / 0;
// 李四加200
String sql2 =
"update account set money = money + 200 where name=?";
PreparedStatement ps2 =
conn.prepareStatement(sql2);
ps2.setString(1, "李四");
ps2.executeUpdate();
// 提交事务
conn.commit();
System.out.println("转账成功");
} catch (Exception e) {
try {
// 回滚事务
if(conn != null){
conn.rollback();
}
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println("转账失败");
} finally {
try {
if(conn != null){
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
十、事务执行流程图
开始事务
↓
执行SQL1
↓
执行SQL2
↓
是否异常?
↓ ↓
否 是
↓ ↓
commit rollback
十一、事务隔离级别
多个用户同时操作数据库时:
会产生并发问题。
MySQL 提供了:
四种隔离级别
1. Read Uncommitted(读未提交)
最低级别。
问题:
脏读
即:
读取到别人未提交的数据。
2. Read Committed(读已提交)
解决:
脏读
Oracle 默认级别。
3. Repeatable Read(可重复读)
MySQL 默认级别。
解决:
脏读
不可重复读
4. Serializable(串行化)
最高级别。
事务串行执行。
最安全:
性能最差
十二、并发问题详解
1. 脏读
事务A:
修改余额但未提交
事务B:
读取到了这个未提交的数据
如果A回滚:
B读到的数据就是假的
2. 不可重复读
同一个事务:
第一次读取:1000
第二次读取:1200
数据不一致。
3. 幻读
事务A读取:
共有5条数据
事务B插入一条数据,事务A再次读取:
变成6条
像"幻觉"一样。
十三、查看事务隔离级别
SELECT @@transaction_isolation;
十四、设置事务隔离级别
设置当前会话
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
设置全局
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
十五、事务失效的常见原因(面试常问)
1. 忘记提交
COMMIT;
没写。
2. 自动提交未关闭
conn.setAutoCommit(false);
没写。
3. 异常未捕获
导致:
rollback未执行
4. 数据库引擎不支持事务
例如:
MyISAM
不支持事务。
十六、InnoDB 与 MyISAM
InnoDB(推荐)
支持:
-
事务
-
行锁
-
外键
MySQL 默认引擎。
MyISAM
不支持事务。
适合:
读多写少
现在较少使用。
十七、Spring 事务(简单了解即可)
实际开发中,通常不会手写 JDBC 事务。
而是使用:
Spring 声明式事务
@Transactional
public void transfer(){
}
Spring 自动:
开启事务
提交事务
回滚事务
开发效率非常高。
十八、事务总结
核心一句话
事务就是保证多条SQL语句"要么全成功,要么全失败"。
事务控制语句
| 操作 | SQL |
|---|---|
| 开启事务 | START TRANSACTION |
| 提交事务 | COMMIT |
| 回滚事务 | ROLLBACK |
JDBC事务核心代码
java
conn.setAutoCommit(false);
conn.commit();
conn.rollback();
MySQL 默认隔离级别
Repeatable Read(可重复读)
开发中的最佳实践
推荐:
使用 InnoDB
使用 Spring 事务
捕获异常后 rollback
尽量缩小事务范围
十九、面试高频问题
1. 什么是事务?
事务是一组操作,要么全部成功,要么全部失败。
2. 事务四大特性?
ACID
原子性
一致性
隔离性
持久性
3. MySQL 默认隔离级别?
Repeatable Read
4. 如何开启事务?
START TRANSACTION;
5. JDBC 如何控制事务?
setAutoCommit(false)
commit()
rollback()
二十、学习建议
建议学习顺序:
SQL事务
↓
JDBC事务
↓
事务隔离级别
↓
Spring事务
↓
分布式事务
这样会非常清晰。