一、事务的介绍
事务是一组操作的集合,事务会把所有操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
二、事务的基本操作
2.1 事务操作方式一
例子: 转账场景(张三向李四转账)
-- 1. 查询张三账户余额
select * from account where name = '张三';
-- 2. 将张三账户余额-1000
update account set money = money - 1000 where name = '张三';
-- 此语句出错后张三钱减少但是李四钱没有增加
模拟sql语句错误
-- 3. 将李四账户余额+1000
update account set money = money + 1000 where name = '李四';
-- 查看事务提交方式
SELECT @@AUTOCOMMIT;
-- 设置事务提交方式,1为自动提交,0为手动提交,该设置只对当前会话有效
SET @@AUTOCOMMIT = 0;
-- 提交事务
COMMIT;
-- 回滚事务
ROLLBACK;
-- 设置手动提交后上面代码改为:
select * from account where name = '张三';
update account set money = money - 1000 where name = '张三';
update account set money = money + 1000 where name = '李四';
commit;
2.2 事务操作方式二
开启事务: START TRANSACTION 或 BEGIN TRANSACTION;
提交事务: COMMIT;
回滚事务: ROLLBACK;
操作实例:
start transaction;
select * from account where name = '张三';
update account set money = money - 1000 where name = '张三';
update account set money = money + 1000 where name = '李四';
commit;
2.3 实际开发的案例
一个实际开发中常见的例子是银行系统中的转账业务。在进行资金转移时,我们需要保证转出和转入两个账户的金额变动是原子性的,要么全部成功,要么全部失败。
以下是一个示例代码片段:
// 假设有两个账户:accountA 和 accountB
String accountA = "A";
String accountB = "B";
double transferAmount = 100.0; // 转账金额// 获取数据库连接
Connection connection = getConnection();
try {connection.setAutoCommit(false); // 设置手动提交事务// 查询账户 A 的余额double balanceA = queryBalance(accountA);// 查询账户 B 的余额double balanceB = queryBalance(accountB);if (balanceA >= transferAmount) { // 检查账户 A 的余额是否足够// 扣除账户 A 的金额updateBalance(connection, accountA, balanceA - transferAmount);// 增加账户 B 的金额updateBalance(connection, accountB, balanceB + transferAmount);connection.commit(); // 提交事务System.out.println("转账成功!");} else {System.out.println("转账失败:账户 A 余额不足!");}
} catch (SQLException e) {connection.rollback(); // 发生异常,回滚事务System.out.println("转账失败:" + e.getMessage());
} finally {connection.close(); // 关闭数据库连接
}
在上述示例中,我们使用connection.setAutoCommit(false)
将自动提交事务的选项关闭,并手动控制事务的提交和回滚。当余额足够时,我们更新账户 A 和账户 B 的余额,并使用connection.commit()
提交事务。如果发生异常或余额不足的情况,我们使用connection.rollback()
回滚事务。
通过使用事务,我们可以确保转账过程中的数据一致性和可靠性。只有当两个账户的金额都成功更新后,才会执行事务的提交操作,否则会回滚到事务开始前的状态。
这是一个简单的示例,实际应用中可能涉及更多复杂的业务逻辑和错误处理。但这个例子展示了如何在实际开发中使用START TRANSACTION
或BEGIN
来管理事务,确保转账操作的一致性和可靠性。
三、事务的四大特性
- 原子性(Atomicity):事务是不可分割的最小操作但愿,要么全部成功,要么全部失败
- 一致性(Consistency):事务完成时,必须使所有数据都保持一致状态
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的
四、并发事务问题
问题 | 描述 |
---|---|
脏读 | 一个事务读到另一个事务还没提交的数据 |
不可重复读 | 一个事务先后读取同一条记录,但两次读取的数据不同 |
幻读 | 一个事务按照条件查询数据时,没有对应的数据行,但是再插入数据时,又发现这行数据已经存在 |
常用的并发控制机制包括:
-
锁(Locking):使用锁来控制并发事务对数据的访问和修改,确保在某个事务读取或修改数据时,其他事务不能同时进行相同的操作。
-
事务隔离级别(Isolation Level):通过设置不同的事务隔离级别,定义了事务之间的可见性、并发控制的粒度等规则。
-
多版本并发控制(MVCC):每个事务在读取数据时,会看到一个特定的版本,而不是直接读取最新的数据。这样可以避免脏读、不可重复读和幻读问题。
-
时间戳排序(Timestamp Ordering):使用时间戳来对事务进行排序,根据时间戳来判断事务的执行顺序,确保事务按照正确的顺序读取和修改数据。
实际开发中,为了解决并发事务问题,需要根据具体情况选择适当的并发控制机制和事务隔离级别,并进行合理的设计和优化。同时,也需要进行充分的测试和验证,确保系统在高并发环境下依然能够保持数据的一致性和可靠性。
五、事务的隔离级别
并发事务隔离级别:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read uncommitted(读未提交) | √ | √ | √ |
Read committed(读已提交) | × | √ | √ |
Repeatable Read(可重复读)(默认的隔离级别) | × | × | √ |
Serializable(串行化) | × | × | × |
- √表示在当前隔离级别下该问题会出现
- Serializable 性能最低;Read uncommitted 性能最高,数据安全性最差
注意:事务隔离级别越高,数据越安全,但是性能越低。
查看事务隔离级别: SELECT @@TRANSACTION_ISOLATION;
设置事务隔离级别: SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE };
SESSION 是会话级别,表示只针对当前会话有效,GLOBAL 表示对所有会话有效