数据库笔记

it2024-08-20  50

并发一致性

脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。

不可重复读是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

幻想读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

两段式锁

在对任何数据进行读、写操作之前,事务首先要申请并获得对该数据的封锁; 在释放一个封锁之后,事务不再申请和获得任何其他封锁。 这样的方式尽管无法避免死锁。可是两段锁协议能够保证事务的并发调度是串行化 (串行化非常重要,尤其是在数据恢复和备份的时候)的。

RC 和 RR

提交读(READ COMMITTED)

一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。

可重复读(REPEATABLE READ)

保证在同一个事务中多次读取同样数据的结果是一样的。

表锁

意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。

共享锁(S):

SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE

排他锁(X):

SELECT * FROM table_name WHERE ... FOR UPDATE

行锁

InnoDB有三种行锁的算法:

Record Lock:单个行记录上的锁。

Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。

Next-Key Lock:1+2,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。

MVCC

mvcc 的意义就是用乐观锁的方式,而不是通过加锁的方式实现了可重复读,但是不能解决幻读,解决幻读只能用串行化或者使用间隙锁。

整体流程 我们在了解了隐式字段,undo log, 以及Read View的概念之后,就可以来看看MVCC实现的整体流程是怎么样了 整体的流程是怎么样的呢?我们可以模拟一下

当事务2对某行数据执行了快照读,数据库为该行数据生成一个Read View读视图,假设当前事务ID为2,此时还有事务1和事务3在活跃中,事务4在事务2快照读前一刻提交更新了,所以Read View记录了系统当前活跃事务1,3的ID,维护在一个列表上,假设我们称为trx_list

Read View不仅仅会通过一个列表trx_list来维护事务2执行快照读那刻系统正活跃的事务ID,还会有两个属性up_limit_id(记录trx_list列表中事务ID最小的ID),low_limit_id(记录trx_list列表中事务ID最大的ID,也有人说快照读那刻系统尚未分配的下一个事务ID也就是目前已出现过的事务ID的最大值+1,我更倾向于后者;所以在这里例子中up_limit_id就是1,low_limit_id就是4 + 1 = 5,trx_list集合的值是1,3,Read View如下图

我们的例子中,只有事务4修改过该行记录,并在事务2执行快照读前,就提交了事务,所以当前该行当前数据的undo log如下图所示;我们的事务2在快照读该行记录的时候,就会拿该行记录的DB_TRX_ID去跟up_limit_id,low_limit_id和活跃事务ID列表(trx_list)进行比较,判断当前事务2能看到该记录的版本是哪个。

所以先拿该记录DB_TRX_ID字段记录的事务ID 4去跟Read View的的up_limit_id比较,看4是否小于up_limit_id(1),所以不符合条件,继续判断 4 是否大于等于 low_limit_id(5),也不符合条件,最后判断4是否处于trx_list中的活跃事务, 最后发现事务ID为4的事务不在当前活跃事务列表中, 符合可见性条件,所以事务4修改后提交的最新结果对事务2快照读时是可见的,所以事务2能读到的最新数据记录是事务4所提交的版本,而事务4提交的版本也是全局角度上最新的版本

也正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同

MVCC相关问题

RR是如何在RC级的基础上解决不可重复读的? 当前读和快照读在RR级别下的区别: 表1: 表2: 而在表2这里的顺序中,事务B在事务A提交后的快照读和当前读都是实时的新数据400,这是为什么呢? 这里与上表的唯一区别仅仅是表1的事务B在事务A修改金额前快照读过一次金额数据,而表2的事务B在事务A修改金额前没有进行过快照读。 所以我们知道事务中快照读的结果是非常依赖该事务首次出现快照读的地方,即某个事务中首次出现快照读的地方非常关键,它有决定该事务后续快照读结果的能力 我们这里测试的是更新,同时删除和更新也是一样的,如果事务B的快照读是在事务A操作之后进行的,事务B的快照读也是能读取到最新的数据的

RC,RR级别下的InnoDB快照读有什么不同? 正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同 在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见; 即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见 而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因 总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。

redo undo

Redo记录以顺序附加的形式记录新值,如某条记录<T,X,V>,表示事物T将新值V存储到数据库元素X,新值可以保证重做; 而Undo记录通常以随机操作的形式记录旧值,如某条记录<T1,Y,9>,表示事物T1对Y进行了修改,修改前Y的值是9,旧值能用于撤销,也能供其他事务读取。 Redo用来保证事务的原子性和持久性,Undo能保证事务的一致性,两者也是系统恢复的基础前提

redo

Innodb执行事务时会拆分为很多小事务,每个小事务产生某条Redo记录。 而通过几个数据库原语能更一般性的描述Redo记录: Input(X):将X值从存储介质读入缓冲区 Read(X,t):将X值从缓冲区读入事务内的变量t,如果缓冲中不存在,则触发Input Write(X,t): 将事务内的t写入到缓冲区X块,如果缓冲中X不存在,则触发Input(X),再完成write Output(X):将缓冲区X写入到存储中

chekpoint

一旦事务commit日志记录写入磁盘,逻辑上而言本事务的Undo记录在恢复时已经不需要,在commit时可以删除之前的Undo记录。但由于多事务同时执行的原因,有时候不能这样做,尽管本事务已经commit,但其他事务可能在使用Undo中的旧值。为此需要checkpoint来处理这些当前活跃的事务。

检查点技术可分为简单检查点与更优化的非静止检查点。在一个简单检查点中有如下过程:

停止接受新的事务等待当前所有活跃事务完成或中止,并在日志中写入commit或abort记录。 如果不停止,假设写入ckpt时a事务已经提交,而b事务对X的修改并未提交,那么 b事务时只能从a修改后的X值上进行redo将当前位于内存的日志,将缓冲块刷新到磁盘写入日志记录,并再次刷新到磁盘重新开始接受事务

系统恢复时,可以从日志尾端反向搜索,直到找到第一个标志,而没有必要处理之前的记录。

当使用非静止检查点技术,恢复时的也是从日志尾向前扫描,可能先遇到标志,也可能先遇到标志: 1.先遇到<START CKPT(t1,…tn)>时,说明系统在检查点过程中崩溃,未完成事务包括2部分:(t1,…tn)记录的部分及标志后新进入部分。这部分事务中最早那个事务的开始点就是扫描的截止点,再往前可以不必扫描。

2.先遇到,说明系统完成了上一个周期的检查点,新的检查点还没开始。需要处理2部分事务:标志之后到系统崩溃时这段时间内的事务及上一个,区间内新接受的事务。为此扫描到上一个检查点<START CKPT()>就可以截止。

多说一句,很容易发现,非静止检查点是将一个点扩展为一个处理区间了,类似的设计其他技术也有,如JVM的GC处理,从stop the world到安全区的处理[1]。

undo

Undo是逻辑日志,并不幂等,在撤销时,根据undo记录进行补偿操作。Undo本身也产生redo记录。通过Undo日志数据库可以实现MVCC。 Undo保证了事务失败或主动abort时的机能,除此之外,系统崩溃恢复时,也确保数据库状态能恢复到一致。

系统恢复时,Undo需要Redo的配合来实现,或者说二者是一套机制的两个方面。因为在Redo日志有commit或abort记录的事务是无需undo的。 假设以静止的检查点为日志类型,以<CKPT (t0,…,tn)>做检查点,期间不接受新事务进入,整个Undo过程可以描述如下: 1.以进行检查点时记录的活跃事务(t0,…,tn)为undo-list 2.在Redo阶段,发现一条<T,START>记录,就将T加入到undo-list 3.在redo阶段,发现一条<T,END>或<T,ABORT>记录,就将T从undo-list删除 4.此时undo-list中的事务都是些未提交也没回滚的事务,系统如同普通的事务回滚样进行具体的undo操作 5.当undo-list中发现<T,START>时,说明完成了具体的回滚操作,系统写入一个<T,ABORT>记录,并从undo-list中删除T。 6.直到undo-list为空,撤销阶段完成

最新回复(0)