《mysql》之undolog


还不懂mysql的undo log和mvcc?算我输! 

 

undo log有两个作用:提供回滚和MVCC。

undo log是逻辑日志。

undo log存在于一个特殊的段中,存在于表空间中,和主键id组织的数据存在一个文件中,毕竟每行数据都有个指向undo log的指针。

 

 

当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。

有时候应用到行版本控制的时候,也是通过undo log来实现的:当读取的某一行被其他事务锁定时

它可以从undo log中分析出该行记录以前的数据是什么,从而提供该行版本信息,让用户实现非锁定一致性读取。

 

undo log的存储方式

innodb存储引擎对undo的管理采用段的方式。rollback segment称为回滚段,每个回滚段中有1024个undo log segment。

在以前老版本,只支持1个rollback segmentMySQL5.5可以支持128个rollback segment,即支持128*1024个undo操作,还可以通过变量 innodb_undo_logs自定义多少个rollback segment,默认值为128。

undo log默认存放在共享表空间中。

默认rollback segment全部写在一个文件中,但可以通过设置变量 innodb_undo_tablespaces 平均分配到多少个文件中。

 

 delete/update操作的内部机制

当事务提交的时候,innodb不会立即删除undo log,因为后续还可能会用到undo log,如隔离级别为repeatable read时,事务读取的都是开启事务时的最新提交行版本,只要该事务不结束,该行版本就不能删除,即undo log不能删除。

但是在事务提交的时候,会将该事务对应的undo log放入到删除列表中,未来通过purge来删除。

并且提交事务时,还会判断undo log分配的页是否可以重用,如果可以重用,则会分配给后面来的事务,避免为每个独立的事务分配独立的undo log页而浪费存储空间和性能。

 

通过undo log记录delete和update操作的结果发现:

delete操作实际上不会直接删除,而是将delete对象打上delete flag,标记为删除,最终的删除操作是purge线程完成的。

update分为两种情况:update的列是否是主键列。

  • 如果不是主键列,在undo log中直接反向记录是如何update的。即update是直接进行的。
  • 如果是主键列,update分两部执行:先删除该行,再插入一行目标行。

 

insert undo log和update undo log为啥要分开,为啥提交之后insert undo log可以直接删除了,update undo log需要等待purge?

如果某个事务ID=100新增了一条记录,那么在这个事务版本之前这个记录是不存在的

  • 这条数据要么是事务100提交的,然后就存在这条数据了
  • 事务100没有提交,这条数据是nul

这条数据本身就是一个版本,要么是不存在,读取不到,要么就是存在,可以读取

数据是否存在,在RC和RR级别看事务有没有提交。

 

undo log 分成两种格式

一种给insert操作。记录中不含回滚指针,不含旧值。insert之前是没有的。

一种给update/delete操作。有回滚指针,有旧值。

 

对于INSERT_UNDO,调用函数trx_undo_page_report_insert进行插入,记录格式大致如下图所示: 

pastedGraphic_3.png

 

对于UPDATE_UNDO,调用函数trx_undo_page_report_modify进行插入,UPDATE UNDO的记录格式大概如下图所示: pastedGraphic_4.png

 

 

 

 

【MySQL笔记】正确的理解MySQL的MVCC及实现原理

 

什么是MVCC

MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突

做到即使有读写冲突时,也能做到不加锁,非阻塞并发读

准确的说,MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突” 这么一个概念。仅仅是一个理想概念


当前读和快照读

当前读

像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读

它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

快照读
像不加锁的select操作就是快照读,即不加锁的非阻塞读

快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;

提高并发性能,快照读是基于多版本并发控制,即MVCC,在很多情况下,避免了加锁操作,降低了开销;

既然是基于多版本,即快照读读到的不一定是数据的最新版本,而有可能是之前的历史版本

 

MVCC就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读

当前读实际上是一种加锁的操作,是悲观锁的实现


当前读,快照读和MVCC的关系

(1)MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突” 这么一个概念。仅仅是一个理想概念,

而在MySQL中,实现这么一个MVCC理想概念,我们就需要MySQL提供具体的功能去实现它

(2)快照读就是MySQL实现MVCC的其中一个具体非阻塞读功能。

(3)当前读就是悲观锁的具体功能实现

要说的再细致一些,快照读本身也是一个抽象概念,再深入研究。MVCC模型在MySQL中的具体实现则是由 3个隐式字段undo日志 ,Read View 等去完成的,具体可以看下面的MVCC实现原理


MVCC能解决什么问题

数据库并发场景有三种,分别为:

  • 读-读:不存在任何问题,也不需要并发控制
  • 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
  • 写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

MVCC带来的好处

用来解决读-写冲突的无锁并发控制,为每个修改保存一个版本,读操作只读事务开始前的的快照

(1)在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能

(2)还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题


不满意只让数据库采用悲观锁这样性能不佳的形式去解决读-写冲突问题,而提出了MVCC,所以我们可以形成两个组合:

MVCC + 悲观锁
MVCC解决读写冲突,悲观锁解决写写冲突

MVCC + 乐观锁
MVCC解决读写冲突,乐观锁解决写写冲突

这种组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题

 


 

MVCC的实现原理

MVCC的实现依赖记录中的 3个隐式字段undo日志 ,Read View 

取出DB_TRX_ID(即当前事务ID),与系统当前其他活跃事务的ID去对比(由Read View维护),

不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较,直到找到满足特定条件的DB_TRX_ID,

那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新版本


 

隐式字段

每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段

DB_TRX_ID

6byte,记录创建这条记录 或者 最后一次修改该记录的事务ID

DB_ROLL_PTR

7byte,回滚指针,用于配合undo日志,指向这条记录的上一个版本(存储于rollback segment里)用于配合undo日志

DB_ROW_ID

6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引

实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了

 


undo日志

undo log主要分为两种:

insert undo log
代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃

update undo log
事务在进行updatedelete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

purge

  • 从前面的分析可以看出,为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的deleted_bit,并不真正将过时的记录删除。

  • 为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view);如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。


Read View(读视图)

事务进行快照读的读视图(Read View),用来做可见性判断

(1)当执行快照读的时候,对该记录创建一个Read View读视图,记录当前活跃事务的ID。

(2)用来判断当前事务能够看到哪个版本的数据,可能是当前最新的数据,也可能是该行记录的undo log里面的数据。

实现

当每个事务开启时,都会被分配一个ID, 这个ID是递增的,最新的事务,ID值越大

trx_list:列表,记录Read View生成时刻系统正活跃的事务ID

up_limit_id:记录trx_list列表中事务ID最小的ID

low_limit_id:系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1

(1)首先判断   DB_TRX_ID < up_limit_id,如果小于,则当前事务能看到DB_TRX_ID 所在的记录,如果大于等于进入下一个判断

(2)然后判断 DB_TRX_ID >= low_limit_id ,如果大于等于,则DB_TRX_ID 所在的记录在Read View生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断

(3)判断DB_TRX_ID 是否在活跃事务之中trx_list.contains(DB_TRX_ID)

  • 如果在,则代表Read View生成时刻,这个事务还在活跃,还没有Commit,修改的数据对当前事务不可见;
  • 如果不在,这个事务在Read View生成之前就已经Commit了,修改的结果对当前事务可见

 


 

select count(*) 总行数

在 MyISAM 存储引擎中,把表的总行数存储在磁盘上,直接返回总数据。

在 InnoDB 存储引擎中,没有将总行数存储在磁盘上,会先把数据读出来,一行一行的累加,最后返回总数量。

在默认隔离级别可重复读的情况下,通过多版本并发控制(MVCC)来实现,每一行记录都需要判断自己是否对这个会话可见,因此在统

计总数量时,InnoDB 只好把数据一行一行的读取出来判断,只有当前会话可见的才纳入统计中。

所以同一时刻不同会话查询到的数量就不一样。

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM