MySQL事务隔离级别和出现的问题
事务的四大特性
- 原子性 Atomicity:一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
- 一致性 Consistency :在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器)、级联回滚等。
- 隔离性 Isolation:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
- 持久性 Durability :事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
MySQL的四种隔离级别
SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
Read Uncommited(读取未提交)
在该隔离级别,这是最低的隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。可能会导致脏读、幻读或不可重复读。
就好比还没确定的消息,你却先知道了发布出去,最后又变更了,就是说瞎话了。常说的脏读,读到了还未提交的。
Read Committed(读取已提交)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。可以阻止脏读,但是幻读或不可重复读仍有可能发生。
只能读取到已经提交的事务。
Repeatable Read(可重复读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。可以阻止脏读和不可重复读,但幻读仍有可能发生。
幻读,读到已提交的数据。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 。我们可以通过SELECT @@tx_isolation;命令来查看
mysql> SELECT @@tx_isolation;
隔离级别越低,事务请求的锁越少
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。该级别可以防止脏读、不可重复读以及幻读。
事务顺序执行,没有并行,完全杜绝幻读。
注:InnoDB中不可能出现幻读问题。
不同事务级别带来的问题
脏读
脏读发生在一个事务A读取了别另一个事务B修改,但是还没提交的数据。假如B回退,那么A读取的就是无效的数据。
例子
不可重复读
不可重复读 就是一个事务读到另一个事务修改后并提交的数据(update)。在同一个事务中,对于同一组数据读取到的结果不一致。比如,事务B 在 事务A 提交前读到的结果,和在 事务A 提交后读到的结果可能不同。不可重复读出现的原因就是由于事务并发修改记录而导致的。和脏读不同的是,脏读修改后不用提交。
上图中,事务2提交成功,它所做的修改已经可见。然而,事务1已经读取了一个其它的值。在序列化和可重复读的隔离级别中,数据库管理系统会返回旧值,即在被事务2修改之前的值。在提交读和未提交读隔离级别下,可能会返回被更新的值,这就是“不可重复读”。
例子
幻读
幻读发生在当两个完全相同的查询执行时,第二次查询所返回的结果集跟第一个查询不相同
例子
不可重复读和幻读区别:
不可重复读一般针对的是行级别的数据的更改(修改),不可重复读的重点是修改
幻读一般针对表级别的新增数据,在统计事务中,两次读取的数据统计不一致,幻读的重点在于新增或者删除
MVCC机制
InnoDB的一致性的非锁定读 就是通过在MVCC实现的,MySQL的大多数事务型存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制(MVCC)。MVCC的实现,是通过保存数据在某一个时间点的快照来实现的。因此每一个事务无论执行多长时间看到的数据,都是一样的。所以MVCC实现可重复读。
- 快照读:select语句默认,不加锁,MVCC实现可重复读,使用的是MVCC机制读取undo中的已经提交的数据。所以它的读取是非阻塞的
- 当前读:select语句加S锁或X锁;所有的修改操作加X锁,在select for update 的时候,才是当前读。
RR隔离级别下的快照读,不是以begin开始的时间点作为snapshot建立时间点,而是以第一条select语句的时间点作为snapshot建立的时间点。
MVCC依赖数据
行记录隐藏字段
- db_row_id,行ID,用来生成默认聚簇索引(聚簇索引 ,保存的数据在物理磁盘中按顺序保存,这样相关数据保存在一起,提高查询速度)
- db_trx_id,事务ID,新开始一个事务时生成,实例内全局唯一
- db_roll_ptr,undo log指针,指向对应记录当前的undo log
- deleted_bit,删除标记位,删除时设置
undo log
- 用于行记录回滚,同时用于实现MVCC
操作方式
- update
- 行记录数据写入undo log,事务的回滚操作就需要undo log
- 更新行记录数据,当前事务ID写入db_trx_id,undo log指针写入db_roll_ptr
- delete
- 和update一样,只增加deleted_bit设置
- insert
- 生成undo log
- 插入行记录数据,当前事务ID写入db_trx_id, db_roll_ptr为空
这样设计使得读操作很简单,性能很好,并且也能保证只会读到符合标准的行,不足之处是每行记录都需要额外的储存空间,需要做更多的行检查工作,以及额外的维护工作
MVCC如何实现RR
- RR定义:在一个事务内同一快照读执行任意次数,得到的数据一致;且只能读到第一次执行前已经提交的数据或本事务内更改的数据
- 原理:对符合查询条件的记录进行可见性判断 (就是那些数据本事务可以看见,那些数据看不见 )
- read view:记录当前处于活动状态的所有事务ID,RR级别下,第一次快照读时创建,RC级别下,每次快照读均会创建新的
- 缺点: 可能出现幻读
总结
在事务隔离级别为RC和RR级别下, InnnoDB存储引擎使用的才是多版本并发控制。然而,对于快照数据的定义却不相同。在RC事务隔离级别下,对于快照数据(undo端数据),总是读取被锁定行的最新的一份快照数据。而在RR事务隔离级别下,对于快照数据,多版本并发控制总是读取事务开始时的行数据