数据库事务
什么是事务
数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。—— 维基百科
对事务的理解应该注意以下几点:
- 事务就是要保证一组数据库操作,要么全部成功,要么全部失败
- 在 MySQL 中,事务支持是由存储引擎实现
- 不是所有引擎都支持事务,MySQL 中常用的 InnoDB、BDB 支持事务,MyISAM 和 MEMORY 不支持事务
事务的 4 大特性
原子性(Atomicity)
事务被视为不可分割的最小单元,事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响
一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态,所有的变更都必须完整应用到数据库,以确保数据的完整性
隔离性(Isolation)
一个事务的执行不能被其他事务干扰。
持久性(Durability)
一个事务一旦被提交了,对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作
并发事务引起的问题
当多个事务并发访问同一数据时,可能会存在 5 类问题,包括 3 类数据读取问题(脏读、不可重复读和幻读)和 2 类数据更新问题(第 1 类丢失更新和第 2 类丢失更新)
丢失修改
第一类丢失修改
事务 A 撤销时,覆盖了事务 B 已经提交的数据
| 时间 | 取款事务 A | 转账事务 B |
|---|---|---|
| T1 | start transaction; | |
| T2 | start transaction; | |
| T3 | 查询账户余额为 1000 | |
| T4 | 查询账户余额为 1000 | |
| T5 | 汇入 100 元,修改账户余额为 1100 | |
| T6 | commit; | |
| T7 | 取出 100 元,更新余额为 900 | |
| T8 | rollback | |
| T9 | 余额恢复为 1000 B 事务汇款操作结果丢失 |
第二类丢失修改
A 事务覆盖 B 事务已经提交的数据,造成 B 事务所做的操作丢失
| 时间 | 转账事务 A | 取款事务 B |
|---|---|---|
| T1 | start transaction; | |
| T2 | start transaction; | |
| T3 | 查询账户余额为 1000 | |
| T4 | 查询账户余额为 1000 | |
| T5 | 取出 100 元,更新余额为 900 | |
| T6 | commit | |
| T7 | 汇入 100 元 | |
| T8 | 修改账户余额为 1100 B 事务取款结果丢失 | |
| T9 | commit; |
读脏数据(Dirty Read)
若 A 事务读取 B 事务尚未提交的数据并在此基础上操作,而 B 事务执行回滚,那么 A 读取到的数据就是脏数据
| 时间 | 转账事务 A | 取款事务 B |
|---|---|---|
| T1 | start transaction; | |
| T2 | start transaction; | |
| T3 | 查询账户余额为 1000 | |
| T4 | 取出 500 元,更新余额为 500 | |
| T5 | 查询账户余额为 500 脏读 | |
| T6 | rollback,更新余额为 1000 | |
| T7 | 汇入 100,余额更新为 600 | |
| T8 | commit; |
不可重复读(Unrepeatable Read)
事务 A 重新读取前面读取过的数据,发现该数据已经被另一个已提交的事务 B 修改过了
| 时间 | 转账事务 A | 取款事务 B |
|---|---|---|
| T1 | start transaction; | |
| T2 | start transaction; | |
| T3 | 查询账户余额为 1000 | |
| T4 | 查询账户余额为 1000 | |
| T5 | 取出 500 元,更新余额为 500 | |
| T6 | commit | |
| T7 | 查询账户余额为 500 不可重复读 |
幻读(Phantom Read)
A 事务读取 B 事务提交的新增数据。
| 时间 | 统计金额事务 A | 转账事务 B |
|---|---|---|
| T1 | start transaction; | |
| T2 | start transaction; | |
| T3 | 统计总存款金额为 10000 | |
| T4 | 新开一个账户存入 500 | |
| T5 | commit | |
| T6 | 统计总存款金额为 10500 幻读 |
事务隔离级别
数据库通过锁机制解决并发访问的问题。 根据锁定对象的不同,分为行级锁和表级锁; 从并发事务锁定的关系上看,分为共享锁定和独占锁定,共享锁定会防止独占锁定但允许其他的共享锁定。而独占锁定既防止共享锁定也防止其他独占锁定。为了更改数据,数据库必须在进行更改的行上施加行独占锁定,insert、update、delete 和 select for update 语句都会隐式采用必要的行锁定。 但是直接使用锁机制管理是很复杂的,基于锁机制,数据库给用户提供了不同的事务隔离级别,只要设置了事务隔离级别,数据库就会分析事务中的 sql 语句然后自动选择合适的锁。 隔离级别解决并发问题如下表(✅ 表示该隔离级别可能出现此类问题,❌ 表示不会出现):
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 第一类丢失更新 | 第二类丢失更新 |
|---|---|---|---|---|---|
| READ UNCOMMITTED | ✅ | ✅ | ✅ | ❌ | ✅ |
| READ COMMITTED | ❌ | ✅ | ✅ | ❌ | ✅ |
| REPEATABLE READ | ❌ | ❌ | ✅ | ❌ | ❌ |
| SERIALIZABLE | ❌ | ❌ | ❌ | ❌ | ❌ |
设置事务隔离级别
-
可以在 my.ini 文件中使用 transaction-isolation 选项来设置服务器的缺省事务隔离级别。 该选项值可以是:
– READ-UNCOMMITTED– READ-COMMITTED– REPEATABLE-READ– SERIALIZABLE例如:transaction-isolation = READ-COMMITTED -
通过 sql 命令动态设置隔离级别,隔离级别也可以在运行的服务器中动态设置,应使用 SET TRANSACTION ISOLATION LEVEL 语句。 语法模式为:
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL <isolation-level>其中的<isolation-level>可以是:– READ UNCOMMITTED– READ COMMITTED– REPEATABLE READ– SERIALIZABLE例如: SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
事务隔离级别的作用范围
-
全局级:对所有的会话有效
-
会话级:只对当前的会话有效
例如:设置会话级隔离级别为READ COMMITTED :mysql> SET TRANSACTION ISOLATION LEVEL READ COMMITTED;或:mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;设置全局级隔离级别为READ COMMITTED :mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;