1.事务的概念
事务就是要保证一组数据库操作,要么全部成功,要么全部失败。
- 事务支持是在引擎层实现的,并不是所有的引擎都支持事务,MySQL 原生的 MyISAM 引擎就不支持事务。前面我们说过MySQL的架构中,引擎是插拔式的。想用事务,就可以选择InnoDB引擎,不想用或者没有事务需要就选择MyISAM。
2.隔离性和隔离级别
提到事务,首先会想到的是ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性),今天我们就来说说其中 I,也就是“隔离性”。
隔离级别解决了什么问题?
当数据库上有多个事务同时执行的时候,可能会出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。
- 总结:事务的隔离级别是为了解决多个事务间同时执行导致的数据不一致的问题
事务的隔离级别有哪些?
读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。下面我逐一为你解释:
- 读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。出现脏读
- 读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。解决脏读,会出现不可重复读(即在一个事务当中两次读同一个数据居然会不一样)
- 可重复读:一个事务在提交之前看到的数据,总是跟这个事务在启动时看到的数据是一致的。即改数据的事务已经提交,我在我的事务中也不去读。有可能出现幻读。可重复读是InnoDB引擎的默认隔离级别。
- 串行:是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
幻读的理解
事务A在执行读取操作,需要两次统计满足某个数值范围【1,100】数据的总量,前一次查询数据总量22条后,此时事务B执行了新增数据的操作并提交后(有10条满足【1,100】这个范畴),这个时候事务A读取的数据总量32条,和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,这种情况称为幻读。如下图:
各个隔离级别会出现的问题?
| 事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交(read-uncommitted) | √ | √ | √ |
| 读已提交(read-committed) | × | √ | √ |
| 可重复读(repeatable-read) | × | × | √ |
| 串行化(serializable) | × | × | × |
举例子说明各种隔离级别
mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);
下面是按照时间顺序执行两个事务的行为:
现在,来看看各种隔离级别下,我们将会读到什么值。即图里面 V1、V2、V3 的返回值分别是什么。
- 若隔离级别是“读未提交”, 结果:V1、V2、V3 都是 2。为什么A1值是2?因为这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此,V1的值是读到未提交的B事务中的更改后的数据了。
- 若隔离级别是“读提交”,则 V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A 看到。所以, V3 的值也是 2。
- 若隔离级别是“可重复读”,则 V1、V2 是 1,V3 是 2。之所以 V2 还是 1,遵循的就是这个要求:事务在执行期间(即未提交之前)看到的数据前后必须是一致的。
- 若隔离级别是“串行化”,事务A查询值1的时候,给这一行数据加上读锁。事务 B 执行“将 1 改成 2”的时候,会被阻塞直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2。为什么不是B事务先加锁?答:这个值1这个数据所在行是A事务先读取的,所以是B要等A执行完成,再执行。
隔离级别的具体实现?
在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。事务的隔离具体实现非常复杂,涉及到MVCC,undo log,read-view
- “可重复读”隔离级别:这个视图是在事务启动时创建的,整个事务存在期间(就是说事务开始到事务提交)都用这个视图。
- “读提交”隔离级别:视图是在每个 SQL 语句开始执行的时候创建的。
- “读未提交”隔离级别:直接返回记录上的最新值,没有视图概念;
- “串行化”隔离级别:直接用加锁的方式来避免并行访问,也没有视图概念。
如何知道自己的数据库是什么隔离级别?
答案:利用show variables
mysql> show variables like 'transaction_isolation';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
可重复读隔离级别的使用场景
我们来看一个数据校对逻辑的案例。假设你在管理一个个人银行账户表。一个表存了账户余额,一个表存了账单明细。到了月底你要做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。你一定希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。这时候使用“可重复读”隔离级别就很方便。事务启动时的视图可以认为是静态的,不受其他事务更新的影响。
3.事务的启动方式有哪些?
MySQL 的事务启动方式有以下几种:
- 显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。
- set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接。
- begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。如果你想要马上启动一个事务。可以使用start transaction with consistent snapshot这个命令。
一致性视图的生成时机
不同的事务启动方式,一致性的视图的生成时机会不同。
- 利用start/begin启动事务:一致性视图是在执行第一个快照读语句时创建的;
- 第二种启动方式,一致性视图是在执行 start transaction with consistent snapshot 时创建的。
4.如何查看自己数据库当中有哪些长事务?
利用下面的命令:
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
总结:
- 今天介绍了MySQL的事务,事务的隔离级别,幻读的概念,事务的启动方式等等。
- 《MySQL实战45讲》学习笔记 Day 3