事务的原理、MVCC的原理
事务特性
数据库事务具有以下四个基本特性,通常被称为 ACID 特性:
-
原子性(Atomicity):事务被视为不可分割的最小工作单元,要么全部执行成功,要么全部失败回滚。这意味着如果事务执行过程中发生了错误,所有的修改都将被回滚,数据库状态将回到事务执行前的状态。
-
一致性(Consistency):事务执行前后,数据库从一个一致性状态转换到另一个一致性状态。事务的执行不会破坏数据库的完整性约束,例如主键、外键约束等。数据库会保持数据的完整性和一致性。
-
隔离性(Isolation):每个事务的操作都相互隔离,一个事务的中间结果对其他事务是不可见的。这意味着一个事务的修改在提交之前对其他事务是不可见的,防止了并发事务之间的相互影响,保证了数据的正确性。
-
持久性(Durability):一旦事务提交成功,对数据库的修改就会永久保存,即使系统故障或崩溃也不会丢失。数据库会将事务的结果持久化到非易失性存储器中,以保证即使系统发生故障,数据也不会丢失。
这些特性确保了数据库事务的可靠性和稳定性。它们是数据库管理系统中设计用于支持可靠性和并发控制的重要概念。通过保证事务具有这些特性,数据库可以提供稳定、可靠、高效的数据管理和处理能力。
事务的隔离级别
事务的隔离性
事务完全的串行会严重的降低系统的吞吐量和资源利用率,仔细发现,引发事务一致性问题的根本原因在于多个事务访问了相同的数据,更合理的做法是,在某个事务访问某个数据时,对其他想要访问该数据的事务进行限制,当该事务提交后,其他事务才能继续访问这个数据
事务并发执行引发的一致性问题
脏读
一个事务读取了另一个未提交事务修改过的数据,意味着发生了脏读,脏读引发的事务一致性问题。T1事务读取了T2事务未提交的数据。
幻读和不可重复读是并发事务中可能出现的两种问题,它们在数据库事务隔离级别较低的情况下会更容易发生。它们之间的区别在于以下几点:
幻读(Phantom Read)
幻读指的是在同一个事务内多次执行同样的查询,但在查询中返回了不同的数据行的现象。这种情况通常发生在一个事务中插入数据后,另一个并发事务再执行相同的查询,发现结果集中出现了之前不存在的行。
不可重复读(Non-repeatable Read)
不可重复读指的是在同一个事务内多次执行同样的查询,但在查询中返回了不同的数据行的现象。与幻读不同的是,不可重复读是由于其他并发事务对数据进行了修改导致的。
总的来说,**幻读侧重于事务在查询中发现了之前不存在的数据行,而不可重复读侧重于事务在多次读取相同数据时发现数据被其他事务修改。**两者的根本区别在于幻读关注的是新增或者删除的数据行
,而不可重复读关注的是已经存在的数据行发生了改变
。
SQL标准中的四种隔离级别
设置隔离级别的目的是,舍弃一部分隔离性换取一部分性能,SQL标准中的4个隔离级别:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | 允许 | 允许 | 允许 |
READ COMMITTED | 不允许 | 允许 | 允许 |
REPEATABLE READ | 不允许 | 不允许 | 允许 |
SERIALIZABLE | 不允许 | 不允许 | 不允许 |
MySQL中支持的4中隔离级别
MySQL中的默认隔离级别是:REPEATABLE READ
MySQL8查询事务隔离级别
SELECT @@GLOBAL.transaction_isolation;
修改隔离级别语句
set [global | session] transaction isolation level 隔离级别;
各种隔离级别适用的场景
-
READ UNCOMMITTED (读未提交): 这个隔离级别适用于某些特定的报表查询场景,对数据的实时性要求非常高,而对数据一致性要求较低。例如,在某些实时监控系统中,可以使用读未提交隔离级别来获取最新的数据,即使数据可能尚未被完全提交。
-
READ COMMITTED (读已提交): 这个隔离级别适用于大多数在线交易处理系统(OLTP),如电子商务网站。在这种场景下,用户只需要读取已经提交的数据,以避免看到其他事务未提交的数据。一个典型的例子是在线购物系统中的库存管理,确保每个用户看到的库存数据是准确的。
-
REPEATABLE READ (可重复读): 这个隔离级别适用于某些需要对数据进行长时间读取或计算的场景,比如报表生成或复杂的分析。在这些情况下,确保事务在执行期间不受其他事务的影响是非常重要的,以避免数据不一致性。例如,某些财务报表的生成过程中,需要保证数据的完整性和一致性。
-
SERIALIZABLE (可串行化): 这个隔离级别适用于某些极端要求数据完全一致性的场景,如金融交易系统。在这种系统中,任何数据的不一致性都可能导致严重的后果。因此,确保所有事务按顺序逐个执行是至关重要的,即使在高并发情况下也要保证数据的完整性。
MVCC原理(多版本并发控制)
版本链
在InnoDB存储引擎中的表,聚簇索引中的记录都包含两个隐藏列,
trx_id: 一个事务都某条聚簇索引记录进行修改时,都会把该事务的事务id赋值给trx_id
roll_pointer: 每次对聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中,相当于一个指针,通过它可以找到该记录修改前的信息
ReadView(读视图)
用READ UNCOMMITTED 隔离级别的事务来说,可以读取未提交事务修改的记录,因此直接读取版本链中的最新版本
就可以了;对于SERIALIZABLE隔离级别的事务来说,InnoDB中使用加锁的方式访问记录,对于READ COMMITED和REPEATABLE READ隔离级别的事务来说,都必须保证读到的记录是已经提交的事务修改过的记录,因此核心问题就是:需要判断版本链中的哪个版本对于当前事务是可见的,InnoDB给出的解决方案是,生成ReadView,ReadView包含下列四个重要内容:
m_ids:生成ReadView时,当前系统中活跃的事务id列表
min_trx_id:m_ids中的最小事务id
max_trx_id:在生成ReadView时,系统分配个下一个事务的事务id(max_trx_id+1)
creator_trx_id:生成该ReadView的事务的事务id
生成了ReadView之后,我们就可以借助它来判断当前版本的记录对当前事务是否可见:
- 如果当前记录的trx_id与creator_trx_id相同,意味着当前事务在访问自己修改过的记录,可以访问
- 如果当前记录的trx_id小于min_trx_id,说明生成该版本记录的事务已经提交了,可以访问
- 如果当前记录的trx_id 大于max_trx_id,说明生成该版本的事务在当前事务生成ReadView之后才开启,所以该版本不可以被当前事务访问
- 如果被访问版本的trx_id 在 min_trx_id 和 max_trx_id之间,则需进一步判断trx_id是否在m_ids列表中,如果在说明事务为提交,该版本不可见,如果不在,说明可见
在MySQL中,READ COMMITED和REPEATABLE READ 隔离级别之间一个非常答的区别就是他们生成ReadView的时机不同
READ COMMITED 每次读取数据前都生成一个ReadView
例如,现在系统中有两个事务id为T100,T200的事务正在执行,T80是已经提交的事务。100的事务修改a=1,200的事务修改a=2,下面是表信息,其中第一行索引中的记录。
id | a | trx_id | roll_point |
---|---|---|---|
1 | 1 | 100 | 100 |
1 | 1 | 100 | 80 |
1 | 0 | 80 |
假设现在有一个使用READ COMMITED隔离级别的新事物开始执行:
执行语句为
select a from t where id=1;
select语句执行过程如下:
- 执行select语句时生成一个ReadView,m_ids列表为[100,200],min_trx_id为100,max_trx_id为201,creator_trx_id为0
因为这个新开启的事务只进行了select操作,并没有对记录进行修改,所以系统没有为其分配事务id,默认为0
- 顺序版本链寻找符合要求的版本,返回的是a为"0"的版本
之后,将事务id为100的事务提交事务 200 事务中更新表 hero number 的记录
update t set a = "2" where id=1
然后再到刚才使用 READ COMMITTED隔离级别的事务执行select语句,继续查找number为1 的记录,执行步骤如下:
- 生成ReadView,m_ids列表[200],min_trx_id为200,max_trx_id为200,creator_trx_id为0
- 顺序版本链,查找符合要求的版本,最终返回的是"1"
REPEATABLE 在第一次读取数据时生成一个ReadView
假设现在有一个使用 REPEATABLE READ 隔离级别的新事务开始执行:
第一次select操作时:
- 生成ReadView,执行select语句时生成一个ReadView,m_ids列表为[100,200],min_trx_id为100,max_trx_id为201,creator_trx_id为0
- 找到符合条件的版本"0"
之后将事务id为100的事务提交后,再到事务id为200的事务中更新表中id为1的记录
然后再次执行select:
- 沿用(或者说复制)第一次select时生成的ReadView
- 最终的返回版本还是"0"
总结,在REPEATABLE READ隔离级别下,事务的两次查询结果是一样的,因此可以说在REPEATABLE READ级别下避免了不可重复读的发生,同时也很大程度上避免了幻读的发生
MVCC小结
所谓的MVCC指的就是在使用READ COMMITED 和 REPEATABLE READ这两种隔离级别的事务在执行select操作时,访问记录版本链的过程,这样可以使不同事务的读-写操作并发执行,从而提升系统性能。READ COMMITED 和 REPEATABLE READ的最大区别在于,生成Read View的时机不同,READ COMMITED 每次执行select前都会生成一个新的ReadView,而REPEATABLE READ只在第一次执行select前生成一个ReadView