Skip to main content

moregeek program

多版本并发控制 mvcc_飞鱼的博客-多极客编程

介绍多版本并发控制


多版本并发控制技术(Multiversion Concurrency Control,MVCC)


技术是为了解决问题而生的,通过 MVCC 我们可以解决以下几个问题:



  1. 读写之间阻塞的问题:通过 MVCC 可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就可以提升事务并发处理能力。
  2. 降低了死锁的概率:这是因为 MVCC 没有使用锁,读取数据时并不需要加锁,对于写操作,也只锁定必要的行。
  3. 解决一致性读的问题:一致性读也被称为快照读,当我们查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交更新的结果。

MVCC 的思想


MVCC 是通过数据行的历史版本来实现数据库的并发控制。


简单来说 MVCC 的思想就是保存数据的历史版本。这样一个事务进行查询操作时,就可以通过比较版本号来判断哪个较新的版本对当前事务可见。


InnoDB 对 MVCC 的实现


MVCC 没有正式的标准,所以在不同的 DBMS 中,MVCC 的实现方式可能是不同的。


InnoDB 对 MVCC 的实现主要是通过 版本链 + ReadView 结构完成。


版本链存储记录的多个版本



先介绍聚簇索引记录的隐藏列,再介绍 Undo Log 版本链





对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含 3 个隐藏列



  1. db_row_id:隐藏的行 ID。在没有自定义主键也没有 Unique 键的情况下,会使用该隐藏列作为主键。
  2. db_trx_id:操作这个数据的事务 ID,也就是最后一个对该数据进行插入或更新的事务 ID。
  3. db_roll_ptr:回滚指针,也就是指向这个记录的 Undo Log 信息。Undo Log 中存储了回滚需要的数据。


事务ID


事务执行过程中,只有在第一次真正修改记录时(比如进行 insert、delete、update 操作),才会被分配一个唯一的、单调递增的事务 ID,如果没有修改记录操作,按照一定的策略分配一个比较大的事务 ID,减少分配事务 ID 的锁竞争。每当事务向数据库写入新内容时, 所写的数据都会被标记操作所属的事务的事务ID。





在 InnoDB 存储引擎中,版本链由数据行的 Undo Log 组成。


每次对数据行进行修改,都会将旧值记录到 Undo Log,算是该数据行的一个旧版本。


Undo Log 有两个重要的属性:db_roll_ptr、db_trx_id




  • Undo Log 也有一个 db_roll_ptr 属性(insert 操作对应的 Undo Log 没有 db_roll_ptr 属性,因为 insert 操作对应的数据行没有更早的版本),Undo Log 的 db_roll_ptr 属性指向上一次操作的 Undo Log,所有的版本被 db_roll_ptr 属性连接形成一个链表。该链表即版本链,版本链的头节点就是数据行的最新值。




  • Undo Log 还包含生成该版本时,对应的事务 ID,用于判断当前版本的数据对事务的可见性。




版本链如下图所示。这样如果我们想要查找历史快照,就可以通过遍历回滚指针的方式进行查找。


1644822603680-293f8716-0b36-4053-ba87-00230e3e6ed5.png


ReadView 判断版本链中的哪个较新的版本对当前事务是可见的


ReadView 用来判断版本链中的哪个较新的版本对当前事务是可见的。


ReadView 中主要包含 4 个比较重要的属性:



  • m_ids:表示在生成 ReadView 时,当前系统中所有活跃的读写事务的 ID 集合(列表)
  • min_transaction_id:表示在生成 ReadView 时,m_ids 中的最小值
  • max_transaction_id:表示在生成 ReadView 时,系统应该分配给下一个事务的 ID 值
  • creator_transaction_id:表示生成该 ReadView 的事务的 ID



有了这个 ReadView,这样在访问某条记录时,就可以用 ReadView 来判断版本链中的哪个较新的版本对当前事务是可见的。




  • 如果被访问版本的 transaction_id 属性值与 ReadView 中的 creator_trx_id 值相同,表明当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。




  • 如果被访问版本的 transaction_id 属性值 小于 ReadView 中的 min_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交了,所以该版本可以被当前事务访问。




  • 如果被访问版本的 transaction_id 属性值 大于 ReadView 中的 max_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。




  • 如果被访问版本的 transaction_id 属性值在 ReadView 的 min_trx_id 和 max_trx_id 之间,那就需要判断一下 transaction_id 属性值是不是在 m_ids 列表中:





    • 如果在,表明生成 ReadView 时,被访问版本的事务还是活跃的,所以该版本不可以被当前事务访问
    • 如果不在,表明生成 ReadView 时,被访问版本的事务已经被提交了,所以该版本可以被当前事务访问



如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断


可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对当前事务完全不可见,查询结果就不包含该记录。


ReadView 的生成时机


MVCC 可以防止脏读,也可以防止不可重复读。


防止脏读 和 防止不可重复读 实现的不同之处就在:ReadView 的生成时机不同



  • 防止脏读:每次读取数据前,都生成一个 ReadView
  • 防止不可重复读:在当前事务第一次读取数据时,生成一个 ReadView,之后的查询操作都重复使用这个 ReadView



对于隔离级别为 读未提交 的事务来说,直接读取记录的最新版本即可。


对于隔离级别为 串行化 的事务来说,InnoDB 存储引擎使用加锁的方式来访问记录。


对于隔离级别为 读已提交 和 可重复读 的事务来说,都必须保证只能读到已经提交的事务修改的数据,不能读到未提交的事务修改的数据。


©著作权归作者所有:来自51CTO博客作者真正的飞鱼的原创作品,请联系作者获取转载授权,否则将追究法律责任

mysql回收表空间_mysql dba攻坚之路的博客-多极客编程

mysql表空洞delete导致的表空洞delete 命令其实只是把记录的位置,或者数据页标记为了“可复用”,但磁盘文件的大小是不会变的。也就是说,通过 delete 命令是不能回收表空间的。这些可以复用,而没有被使用的空间,看起来就像是“空洞”。insert导致的表空洞如果数据是按照索引递增顺序插入的,那么索引是紧凑的。但如果数据是随机插入的,就可能造成索引的数据页分裂。怎么清理空洞重建表试想一

浅谈mysql--sorted index builds_mysql dba攻坚之路的博客-多极客编程

索引创建概述在早期的MySQL版本,创建索引是需要复制整表数据的;在5.5版本中,MySQL利用快速创建索引(fast index creation)技术,优化了索引的创建算法,使其执行时不需要再进行复制表数据的操作,同时允许在创建索引时,其他事务并发读;而在5.6以上的版本,在创建索引时允许其他事务并发读写,并将此索引创建方式称为Sorted Index Builds。以下介绍一下5.5前后版本

mysql内存管理机制浅析_wx630f055ce23fc的博客-多极客编程

一、placement new的定义 通常情况下,C++中通过用new方式申请内存空间时,是在系统的堆内存空间中进行分配,底层使用C标准库的 malloc() 完成内存分配工作。因此本次申请的内存空间大小,是根据程序运行时对象的大小及使用情况来决定的。但是某些场景中,可能需要预先分配完成内存空间,然后再把对象"放置"在之前预先分配的内存空间上。即所谓的 placement new 操作。定点放置的

io性能瓶颈优化--组提交_mysql dba攻坚之路的博客-多极客编程

二阶段提交二阶段提交步骤1、更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。 2、然后告知执行器执行完成了,随时可以提交事务。执行器生成这个操作的 binlog,并把 binlog 写入磁盘。 3、执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)组提交我们知道redo的组提交是mysql自己就默认的,在并发更新场

mysql索引优化_mysql dba攻坚之路的博客-多极客编程

判断索引选择性好坏可以使用 show index from table_name方法,看到一个索引的基数。“基数”(cardinality)越大,索引的区分度越好。 show index from *****;+---------------------+------------+-------------+--------------+--------------+-----------+---

mysql常用的几个reset_mysql dba攻坚之路的博客-多极客编程

reset master删除所有index file 中记录的所有binlog 文件,将日志索引文件清空,创建一个新的日志文件,这个命令通常仅仅用于第一次用于搭建主从关系的时的主库。​注意: reset master 不同于purge binary log的两处地方1.reset master 将删除日志索引文件中记录的所有binlog文件,创建一个新的日志文件 起始值从000001 开始,然而p

mysql回收表空间_mysql dba攻坚之路的博客-多极客编程

mysql表空洞delete导致的表空洞delete 命令其实只是把记录的位置,或者数据页标记为了“可复用”,但磁盘文件的大小是不会变的。也就是说,通过 delete 命令是不能回收表空间的。这些可以复用,而没有被使用的空间,看起来就像是“空洞”。insert导致的表空洞如果数据是按照索引递增顺序插入的,那么索引是紧凑的。但如果数据是随机插入的,就可能造成索引的数据页分裂。怎么清理空洞重建表试想一

浅谈mysql--sorted index builds_mysql dba攻坚之路的博客-多极客编程

索引创建概述在早期的MySQL版本,创建索引是需要复制整表数据的;在5.5版本中,MySQL利用快速创建索引(fast index creation)技术,优化了索引的创建算法,使其执行时不需要再进行复制表数据的操作,同时允许在创建索引时,其他事务并发读;而在5.6以上的版本,在创建索引时允许其他事务并发读写,并将此索引创建方式称为Sorted Index Builds。以下介绍一下5.5前后版本

mysql内存管理机制浅析_wx630f055ce23fc的博客-多极客编程

一、placement new的定义 通常情况下,C++中通过用new方式申请内存空间时,是在系统的堆内存空间中进行分配,底层使用C标准库的 malloc() 完成内存分配工作。因此本次申请的内存空间大小,是根据程序运行时对象的大小及使用情况来决定的。但是某些场景中,可能需要预先分配完成内存空间,然后再把对象"放置"在之前预先分配的内存空间上。即所谓的 placement new 操作。定点放置的

elasticsearch聚合学习之二:区间聚合_github.com/zq2599的博客-多极客编程

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是《Elasticsearch聚合学习》系列的第二篇,上一篇是我们熟悉了聚合的基本操作,本篇的内容是按照区间聚合的实战操作; 环境信息 以下是本次实战的环境信息,请确保您的Elasticsearch可以正常运行: 操作系统:U

redis变慢?深入浅出redis性能诊断系列文章(二)_数据库架构师的博客-多极客编程

(本文首发于“数据库架构师”公号,订阅“数据库架构师”公号,一起学习数据库技术)本篇为Redis性能问题诊断系列的第二篇,本文主要从应用发起的典型命令使用上进行讲解,由于Redis为单线程服务架构,对于一些命令如果使用不当会极大的影响Redis的性能表现,这里也会对不合理的使用方式给出优化解决方案。一、Redis慢日志功能分析Redis访问变慢,其中有个最基础的方法就是先去看Redis是否有慢日志

轻量级的linux监控工具njmon安装与使用_雍州无名的博客-多极客编程

njmon与nmon类似,但是其可以保存成JSON格式或直接保存到InfluxDB,可以用于服务器性能统计。1.环境准备(1).CentOS7(2).Python32.下载软件官方网址:​​http://nmon.sourceforge.net/pmwiki.php?n=Site.Njmon​​(1).下载njmon目前njmon的最新版本是njmon_linux_binaries_v78但是由于