搜索引擎 网站模板,房屋设计软件免费版,网站产品要如何做详情,11个免费网站空间文章目录锁概述全局锁表级锁表锁元数据锁意向锁行级锁行锁间隙锁临键锁InnoDB引擎逻辑存储结构架构内存结构磁盘结构后台线程事务原理redo logundo logMVCC锁
概述
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中#xff0c;除传统的计算资源#x…
文章目录锁概述全局锁表级锁表锁元数据锁意向锁行级锁行锁间隙锁临键锁InnoDB引擎逻辑存储结构架构内存结构磁盘结构后台线程事务原理redo logundo logMVCC锁
概述
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中除传统的计算资源CPU、RAM、I/O的争用以外数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说锁对数据库而言显得尤其重要也更加复杂。
MySQL中的锁按照锁的粒度分分为以下三类
全局锁锁定数据库中的所有表。表级锁每次操作锁住整张表。行级锁每次操作锁住对应的行数据。
全局锁
全局锁就是对整个数据库实例(也就是表)加锁加锁后整个实例就处于只读状态后续的DML的写语句DDL语句已经更新操作的事务提交语句都将被阻塞。 其典型的使用场景是做全库的逻辑备份对所有的表进行锁定从而获取一致性视图保证数据的完整性。
为什么全库逻辑备份就需要加全局锁呢
我们一起先来分析一下不加全局锁可能存在的问题。
假设在数据库中存在这样三张表: tb_stock 库存表tb_order 订单表tb_orderlog 订单日志表。 在进行数据备份时先备份了tb_stock库存表。然后接下来在业务系统中执行了下单操作扣减库存生成订单更新tb_stock表插入tb_order表。然后再执行备份 tb_order表的逻辑。业务中执行插入订单日志操作。最后又备份了tb_orderlog表。
此时备份出来的数据是存在问题的。因为备份出来的数据tb_stock表与tb_order表的数据不一致(有最新操作的订单信息,但是库存数没减)。
那如何来规避这种问题呢? 此时就可以借助于MySQL的全局锁来解决。
对数据库进行进行逻辑备份之前先对整个数据库加上全局锁一旦加了全局锁之后其他的DDL、DML全部都处于阻塞状态但是可以执行DQL语句也就是处于只读状态而数据备份就是查询操作。那么数据在进行逻辑备份的过程中数据库中的数据就是不会发生变化的这样就保证了数据的一致性和完整性。
语法
加全局锁
flush tables with read lock ;数据备份
mysqldump -uroot –p1234 itcast itcast.sql释放锁
unlock tables ;特点
数据库中加全局锁是一个比较重的操作存在以下问题
如果在主库上备份那么在备份期间都不能执行更新业务基本上就得停摆。如果在从库上备份那么在备份期间从库不能执行主库同步过来的二进制日志binlog会导致主从延迟。
在InnoDB引擎中我们可以在备份时加上 --single-transaction 参数来完成不加锁的一致性数据备份。(其底层使用快照读来完成)
mysqldump --single-transaction -uroot –p123456 itcast itcast.sql表级锁
表级锁每次操作锁住整张表。锁定粒度大发生锁冲突的概率最高并发度最低。应用在MyISAM、InnoDB、BDB等存储引擎中。
对于表级锁主要分为以下三类
表锁元数据锁meta data lockMDL意向锁
表锁
对于表锁分为两类
表共享读锁read lock表独占写锁write lock
语法
加锁
lock tables 表名... read/write释放锁
unlock tables / 客户端断开连接 特点 读锁 两个客户端都只能读 写锁 对当前加锁的客户端可读可写对于其他的客户端不可读也不可写
元数据锁
meta data lock , 元数据锁简写MDL。 这里的元数据大家可以简单理解为就是一张表的表结构。 其主要的作用就是维护表结构的一致性。 MDL加锁过程是系统自动控制无需显式使用在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性在表上有活动事务的时候不可以对元数据进行写入操作(也就是不能修改表的结构避免DML与DDL冲突保证读写的正确性)。
在MySQL5.5中引入了MDL
当对一张表进行增删改查的时候加MDL读锁(共享)当对表结构进行变更操作的时候加MDL写锁(排他)
常见的SQL操作时所添加的元数据锁
我们可以通过下面的SQL来查看数据库中的元数据锁的情况
select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks ;意向锁
为了避免DML在执行时加的行锁与表锁的冲突在InnoDB中引入了意向锁使得表锁不用检查每行数据是否加锁使用意向锁来减少表锁的检查。 锁冲突就是锁互斥造成的阻塞等待情况 假如没有意向锁客户端一对表加了行锁后客户端二如何给表加表锁呢来通过示意图简单分析一下
首先客户端一开启一个事务然后执行DML操作在执行DML语句时会对涉及到的行加行锁。 当客户端二想对这张表加表锁时会检查当前表是否有对应的行锁如果没有则添加表锁此时就会从第一行数据检查到最后一行数据效率较低。
有了意向锁之后 :
客户端一在执行DML操作时会对涉及的行加行锁同时也会对该表加上意向锁。 而其他客户端在对这张表加表锁的时候会根据该表上所加的意向锁来判定是否可以成功加表锁而不用逐行判断行锁情况了。
分类
意向共享锁(IS): 由语句select ... lock in share mode添加 。 与 表锁共享锁(read)兼容与表锁排他锁(write)互斥 意向排他锁(IX): 由insert、update、delete、select…for update添加 。 与表锁共享锁(read)及排他锁(write)都互斥意向锁之间不会互斥 一旦事务提交了意向共享锁、意向排他锁都会自动释放。 可以通过以下SQL查看意向锁及行锁的加锁情况
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data fromperformance_schema.data_locks;行级锁
行级锁每次操作锁住对应的行数据。锁定粒度最小发生锁冲突的概率最低并发度最高。应用在InnoDB存储引擎中。
InnoDB的数据是基于索引组织的行锁是通过对索引上的索引项加锁来实现的而不是对记录加的锁(不是直接对数据加锁)。对于行级锁主要分为以下三类 行锁Record Lock锁定单个行记录的锁防止其他事务对此行进行update和delete。在RC、RR隔离级别下都支持。 间隙锁Gap Lock锁定索引记录间隙不含该记录确保索引记录间隙不变防止其他事务在这个间隙进行insert产生幻读。在RR隔离级别下都支持。 临键锁Next-Key Lock行锁和间隙锁组合同时锁住数据并锁住数据前面的间隙Gap。在RR隔离级别下支持。
InnoDB默认加锁方式是next-keynext-key lock 在一些场景下会退化成记录锁或间隙锁。如果某个加锁操作未使用到索引该锁则会退化为表锁。 退化成表锁其实这句话并不是非常的准确因为这个表锁是通过Next-Key Locks来实现的可以理解为是用了行锁间隙锁来实现锁表的操作! 那么何时产生行锁何时产生间隙锁
只使用唯一索引查询并且只锁定一条记录时innoDB会使用行锁。只使用唯一索引查询但是检索条件是范围检索或者是唯一检索但检索结果不存在试图锁住不存在的数据时会产生 Next-Key Lock临键锁。使用普通索引检索时不管是何种查询只要加锁都会产生间隙锁Gap Lock。同时使用唯一索引和普通索引时由于数据行是优先根据普通索引排序再根据唯一索引排序所以也会产生间隙锁。 这里的查询不包括select 行锁
InnoDB实现了以下两种类型的行锁
共享锁S允许一个事务去读一行阻止其他事务获得相同数据集的排它锁。 就是说与共享锁兼容与排他锁互斥 排他锁X允许获取排他锁的事务更新数据阻止其他事务获得相同数据集的共享锁和排他锁。 也就是说共享锁和排他锁都互斥
两种行锁的兼容情况如下
常见的SQL语句在执行时所加的行锁如下 注意这里的SQL语句都要和索引挂钩 注意
默认情况下InnoDB在 REPEATABLE READ事务隔离级别运行InnoDB使用临键锁进行搜索和索引扫描以防止幻读。
针对唯一索引进行检索时对已存在的记录进行等值匹配时将会自动优化为行锁。InnoDB的行锁是针对于索引加的锁不通过索引条件检索数据那么InnoDB将对表中的所有记录加锁此时就会升级为表锁。
可以通过以下SQL查看意向锁及行锁的加锁情况
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;实例演示 普通的select语句执行时不会加锁。 select…lock in share mode加共享锁共享锁与共享锁之间兼容 共享锁与排他锁之间互斥。 客户端一获取的是id为1这行的共享锁客户端二是可以获取id为3这行的排它锁的因为不是同一行数据。 而如果客户端二想获取id为1这行的排他锁会处于阻塞状态以为共享锁与排他锁之间互斥。 排它锁与排他锁之间互斥 当客户端一执行update语句会为id为1的记录加排他锁 客户端二如果也执行update语句更新id为1的数据也要为id为1的数据加排他锁但是客户端二会处于阻塞状态因为排他锁之间是互斥的。 直到客户端一把事务提交了才会把这一行的行锁释放此时客户端二解除阻塞。 无索引行锁升级为表锁 在客户端一中开启事务并执行update语句更新name为Lily的数据也就是id为19的记录 。然后在客户端二中更新id为3的记录却不能直接执行会处于阻塞状态为什么呢原因就是因为此时客户端一根据name字段进行更新时name字段是没有索引的如果没有索引此时行锁会升级为表锁(因为行锁是对索引项加的锁而name没有索引)。 接下来我们再针对name字段建立索引索引建立之后再次做一个测试 此时我们可以看到客户端一开启事务然后依然是根据name进行更新。而客户端二在更新id为3的数据时更新成功并未进入阻塞状态。 这样就说明我们根据索引字段进行更新操作就可以避免行锁升级为表锁的情况。
间隙锁临键锁
默认情况下InnoDB在 REPEATABLE READ事务隔离级别运行InnoDB使用临键锁进行搜索和索引扫描以防止幻读。
索引上的等值查询(唯一索引)给不存在的记录加锁时, 优化为间隙锁 。索引上的等值查询(非唯一普通索引)向右遍历时最后一个值不满足查询需求时临键锁退化为间隙锁。索引上的范围查询(唯一索引)–会访问到不满足条件的第一个值为止。 注意 间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。 演示 索引上的等值查询(唯一索引)给不存在的记录加锁时, 优化为间隙锁 索引上的等值查询(非唯一普通索引)向右遍历时最后一个值不满足查询需求时next-keylock 退化为间隙锁。 注意我们知道InnoDB的B树索引叶子节点是有序的双向链表。 假如我们要根据这个二级索引查询值为18的数据并加上共享锁我们是只锁定18这一行就可以了吗 并不是因为是非唯一索引这个结构中可能有多个18的存在所以在加锁时会继续往后找找到一个不满足条件的值当前案例中也就是29。此时会对18加临键锁并对29之前的间隙加锁。 索引上的范围查询(唯一索引)–会访问到不满足条件的第一个值为止 查询的条件为id19并添加共享锁。 此时我们可以根据数据库表中现有的数据将数据分为三个部分[19]、(19,25]、(25,∞]。所以数据库数据在加锁是就是将19加了行锁25的临键锁包含25及25之前的间隙正无穷的临键锁(正无穷及之前的间隙)。
InnoDB引擎
逻辑存储结构
InnoDB的逻辑存储结构如下图所示:
表空间是InnoDB存储引擎逻辑结构的最高层 如果用户启用了参数 innodb_file_per_table(在8.0版本中默认开启) 则每张表都会有一个表空间xxx.ibd一个mysql实例可以对应多个表空间用于存储记录、索引等数据。段分为数据段Leaf node segment、索引段Non-leaf node segment、回滚段Rollback segmentInnoDB是索引组织表数据段就是B树的叶子节点 索引段即为B树的非叶子节点。段用来管理多个Extent区。区表空间的单元结构每个区的大小为1M。 默认情况下 InnoDB存储引擎页大小为16K 即一个区中一共有64个连续的页。页是InnoDB 存储引擎磁盘管理的最小单元每个页的大小默认为 16KB。为了保证页的连续性InnoDB 存储引擎每次从磁盘申请 4-5 个区。行InnoDB 存储引擎数据是按行进行存放的。
在行中默认有两个隐藏字段
Trx_id每次对某条记录进行改动时都会把对应的事务id赋值给trx_id隐藏列。Roll_pointer每次对某条引记录进行改动时都会把旧的版本写入到undo日志中然后这个隐藏列就相当于一个指针可以通过它来找到该记录修改前的信息。
架构
MySQL5.5 版本开始默认使用InnoDB存储引擎它擅长事务处理具有崩溃恢复特性在日常开发中使用非常广泛。下面是InnoDB架构图左侧为内存结构右侧为磁盘结构。
内存结构 在左侧的内存结构中主要分为这么四大块儿
Buffer PoolChange BufferAdaptiveHash IndexLog Buffer
Buffer Pool
InnoDB存储引擎基于磁盘文件存储访问物理硬盘和在内存中进行访问速度相差很大为了尽可能弥补这两者之间的I/O效率的差值就需要把经常使用的数据加载到缓冲池中避免每次访问都进行磁盘I/O。
在InnoDB的缓冲池中不仅缓存了索引页和数据页还包含了undo页、插入缓存、自适应哈希索引以及InnoDB的锁信息等等。
缓冲池 Buffer Pool是主内存中的一个区域里面可以缓存磁盘上经常操作的真实数据在执行增删改查操作时先操作缓冲池中的数据若缓冲池没有数据则从磁盘加载并缓存然后再以一定频率刷新到磁盘从而减少磁盘IO加快处理速度。
缓冲池以Page页为单位底层采用链表数据结构管理Page。根据状态将Page分为三种类型 free page空闲page未被使用。clean page被使用page数据没有被修改过。dirty page脏页被使用page数据被修改过其中数据与磁盘的数据产生了不一致。 在专用服务器上通常将多达80的物理内存分配给缓冲池 。 参数设置 show variables like ‘innodb_buffer_pool_size’; Change Buffer
Change Buffer更改缓冲区针对于非唯一二级索引页在执行DML语句时如果这些数据Page没有在Buffer Pool中不会直接操作磁盘而会将数据变更存在更改缓冲区 Change Buffer中在未来数据被读取时再将数据合并恢复到Buffer Pool中再将合并后的数据刷新到磁盘中。
Change Buffer的意义是什么呢?
先来看一幅图这个是二级索引的结构图
与聚集索引不同二级索引通常是非唯一的并且以相对随机的顺序插入二级索引。同样删除和更新可能会影响索引树中不相邻的二级索引页如果每一次都操作磁盘会造成大量的磁盘IO。有了ChangeBuffer之后我们可以在缓冲池中进行合并处理减少磁盘IO。
Adaptive Hash Index
Adaptive Hash Index自适应hash索引用于优化对Buffer Pool数据的查询。MySQL的innoDB引擎中虽然没有直接支持hash索引但是给我们提供了一个功能就是这个自适应hash索引。 因为前面我们提到过hash索引在进行等值匹配时一般性能是要高于B树的因为hash索引一般只需要一次IO即可而B树可能需要几次匹配所以hash索引的效率要高但是hash索引又不适合做范围查询、模糊匹配等。 InnoDB存储引擎会监控对表上各索引页的查询如果观察到在特定的条件下hash索引可以提升速度则建立hash索引称之为自适应hash索引。 自适应哈希索引无需人工干预是系统根据情况自动完成。 参数 adaptive_hash_index Log Buffer
Log Buffer日志缓冲区用来保存要写入到磁盘中的log日志数据redo log 、undo log默认大小为 16MB日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或删除许多行的事务增加日志缓冲区的大小可以节省磁盘 I/O。
参数: innodb_log_buffer_size缓冲区大小 innodb_flush_log_at_trx_commit日志刷新到磁盘时机取值主要包含以下三个 1: 日志在每次事务提交时写入并刷新到磁盘默认值。 0: 每秒将日志写入并刷新到磁盘一次。 2: 日志在每次事务提交后写入并每秒刷新到磁盘一次。
磁盘结构
接下来再来看看InnoDB体系结构的右边部分也就是磁盘结构 System Tablespace 系统表空间是更改缓冲区的存储区域。如果表是在系统表空间而不是每个表文件或通用表空间中创建的它也可能包含表和索引数据。(在MySQL5.x版本中还包含InnoDB数据字典、undolog等)参数innodb_data_file_path 系统表空间默认的文件名叫 ibdata1 File-Per-Table Tablespaces 如果开启了innodb_file_per_table开关 则每个表的文件表空间包含单个InnoDB表的数据和索引 并存储在文件系统上的单个数据文件中。开关参数innodb_file_per_table 该参数默认开启。 那也就是说我们没创建一个表都会产生一个表空间文件如图 General Tablespaces 通用表空间需要通过 CREATE TABLESPACE 语法创建通用表空间在创建表时可以指定该表空间。创建表空间CREATE TABLESPACE ts_name ADD DATAFILE file_name ENGINE engine_name; 创建表时指定表空间CREATE TABLE xxx ... TABLESPACE ts_name; Undo Tablespaces撤销表空间MySQL实例在初始化时会自动创建两个默认的undo表空间初始大小16M用于存储undo log日志。 Temporary TablespacesInnoDB 使用会话临时表空间和全局临时表空间。存储用户创建的临时表等数据。 Doublewrite Buffer Files双写缓冲区innoDB引擎将数据页从Buffer Pool刷新到磁盘前先将数据页写入双写缓冲区文件中便于系统异常时恢复数据。 Redo Log重做日志是用来实现事务的持久性。该日志文件由两部分组成重做日志缓冲redo logbuffer以及重做日志文件redo log,前者是在内存中后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中, 用于在刷新脏页到磁盘时,发生错误时, 进行数据恢复使用。 以循环方式写入重做日志文件涉及两个文件
后台线程 在InnoDB的后台线程中分为4类分别是 Master Thread 核心后台线程负责调度其他线程还负责将缓冲池中的数据异步刷新到磁盘中, 保持数据的一致性还包括脏页的刷新、合并插入缓存、undo页的回收 。 IO Thread在InnoDB存储引擎中大量使用了AIO来处理IO请求, 这样可以极大地提高数据库的性能而IOThread主要负责这些IO请求的回调。 我们可以通过show engine innodb status \G; 的这条指令查看到InnoDB的状态信息其中就包含IO Thread信息: Purge Thread主要用于回收事务已经提交了的undo log在事务提交之后undo log可能不用了就用它来回收。 Page Cleaner Thread协助 Master Thread 刷新脏页到磁盘的线程它可以减轻 Master Thread 的工作压力减少阻塞。
事务原理
我们研究事务的原理就是研究MySQL的InnoDB引擎是如何保证事务的这四大特性的
原子性Atomicity事务是不可分割的最小操作单元要么全部成功要么全部失败。一致性Consistency事务完成时必须使所有的数据都保持一致状态。隔离性Isolation数据库系统提供的隔离机制保证事务在不受外部并发操作影响的独立环境下运行。持久性Durability事务一旦提交或回滚它对数据库中的数据的改变就是永久的。
而对于这四大特性实际上分为两个部分。 其中的原子性、一致性、持久化实际上是由InnoDB中的两份日志来保证的一份是redo log日志一份是undo log日志。 而隔离性是通过数据库的锁加上MVCC来保证的。 那么redo log、undo log和MVCC是什么呢
redo log
重做日志记录的是事务提交时数据页的物理修改是用来实现事务的持久性。 该日志文件由两部分组成
重做日志缓冲redo log buffer重做日志文件redo logfile
前者是在内存中后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中, 用于在刷新脏页到磁盘,发生错误时, 进行数据恢复使用。
如果没有redolog可能会存在什么问题的 我们知道在InnoDB引擎中的内存结构中主要的内存区域就是缓冲池在缓冲池中缓存了很多的数据页。 当我们在一个事务中执行多个增删改的操作时InnoDB引擎会先操作缓冲池中的数据如果缓冲区没有对应的数据会通过后台线程将磁盘中的数据加载出来存放在缓冲区中然后将缓冲池中的数据修改修改后的数据页我们称为脏页。 而脏页则会在一定的时机通过后台线程刷新到磁盘中从而保证缓冲区与磁盘的数据一致。 而缓冲区的脏页数据并不是实时刷新的而是一段时间之后将缓冲区的数据刷新到磁盘中假如刷新到磁盘的过程出错了而提示给用户事务提交成功而数据却没有持久化下来这就出现问题了没有保证事务的一致性。 那么如何解决上述的问题呢 在InnoDB中提供了一份日志 redo log通过redolog就可以解决这个问题。
有了redolog之后当对缓冲区的数据进行增删改之后会首先将操作的数据页的变化记录在redolog buffer中。在事务提交时会将redo log buffer中的数据刷新到redo log磁盘文件中。过一段时间之后如果刷新缓冲区的脏页到磁盘时发生错误此时就可以借助于redo log进行数据恢复这样就保证了事务的一致性。 而如果脏页成功刷新到磁盘或者涉及到的数据已经落盘此时redolog就没有作用了就可以删除了所以存在的两个redolog文件是循环写的。
那为什么每一次提交事务要刷新redo log 到磁盘中呢而不是直接将buffer pool中的脏页刷新到磁盘呢 ?
因为在业务操作中我们操作数据一般都是随机读写磁盘的而不是顺序读写磁盘。 而redo log在往磁盘文件中写入数据由于是日志文件所以都是顺序写的。顺序写的效率要远大于随机写。 这种先写日志的方式称之为 WALWrite-Ahead Logging。
undo log
回滚日志用于记录数据被修改前的信息 , 作用包含两个 : 提供回滚(保证事务的原子性) 和MVCC(多版本并发控制) 。
undo log和redo log记录物理日志不一样它是逻辑日志。可以认为当delete一条记录时undo log中会记录一条对应的insert记录反之亦然当update一条记录时它记录一条对应相反的update记录。当执行rollback时就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。
Undo log销毁undo log在事务执行时产生事务提交时并不会立即删除undo log因为这些日志可能还用于MVCC。Undo log存储undo log采用段的方式进行管理和记录存放在前面介绍的 rollback segment回滚段中内部包含1024个undo log segment。
MVCC
在说MVCC之前先了解两个概念 当前读读取的是记录的最新版本读取时还要保证其他并发事务不能修改当前记录会对读取的记录进行加锁。对于我们日常的操作如select … lock in share mode(共享锁)select … for update、update、insert、delete(排他锁)都是一种当前读。 例如 在测试中我们可以看到即使是在默认的RR隔离级别下事务A中依然可以读取到事务B最新提交的内容因为在查询语句后面加上了 lock in share mode 共享锁此时是当前读操作。当然当我们加排他锁的时候也是当前读操作。 快照读简单的select不加锁就是快照读快照读读取的是记录数据的可见版本有可能是历史数据不加锁是非阻塞读。在不同的隔离级别下 Read Committed每次select都生成一个快照读。Repeatable Read开启事务后第一个select语句才是快照读的地方后面的查询直接返回的就是前面的快照数据Serializable快照读会退化为当前读。测试 在测试中,我们看到即使事务B提交了数据,事务A中也查询不到。 原因就是因为普通的select是快照读而在当前默认的RR隔离级别下开启事务后第一个select语句才是快照读的地方后面执行相同的select语句都是从快照中获取数据可能不是当前的最新数据这样也就保证了可重复读。
然后我们来说说MVCC
全称 Multi-Version Concurrency Control多版本并发控制。指维护一个数据的多个版本使得读写操作没有冲突快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。
三个隐式字段
当我们创建了上面的这张表我们在查看表结构的时候就可以显式的看到这三个字段。 实际上除了这三个字段以外InnoDB还会自动的给我们添加三个隐藏字段及其含义分别是
undo log日志
前面我们已经提到过这里我们说说它的具体工作流程
有一张表原始数据为 DB_TRX_ID : 代表最近修改事务的ID。因为同一时间可能有很多个事务在操作这里记录的就是当前事务的编号。而事务的id是自增的。 DB_ROLL_PTR 由于这条数据是才插入的没有被更新过所以该字段值为null。 然后有四个并发事务同时在访问这张表。 当事务2执行第一条修改语句时会记录undo log日志记录数据变更之前的样子; 然后更新记录并且记录本次操作的事务ID回滚指针回滚指针用来指定如果发生回滚回滚到哪一个版本。 当事务3执行第一条修改语句时也会记录undo log日志记录数据变更之前的样子; 然后更新记录并且记录本次操作的事务ID回滚指针回滚指针用来指定如果发生回滚回滚到哪一个版本。 当事务4执行第一条修改语句时也会记录undo log日志记录数据变更之前的样子; 然后更新记录并且记录本次操作的事务ID回滚指针回滚指针用来指定如果发生回滚回滚到哪一个版本。
最终我们发现不同事务或相同事务对同一条记录进行修改会导致该记录的undolog生成一条记录版本链表链表的头部是最新的旧记录链表尾部是最早的旧记录。
readview ReadView读视图是 快照读SQL执行时MVCC提取数据的依据记录并维护系统当前活跃的事务未提交的id。
ReadView中包含了四个核心字段 而在readview中就规定了版本链数据的访问规则 trx_id 代表当前undolog版本链对应事务ID。 不同的隔离级别生成ReadView的时机不同
READ COMMITTED 在事务中每一次执行快照读时生成ReadView。REPEATABLE READ仅在事务中第一次执行快照读时生成ReadView后续复用该ReadView。
然后我们来看看MVCC的过程原理
在RC隔离级别下
RC隔离级别下在事务中每一次执行快照读时生成ReadView。我们就来分析事务5中两次快照读读取数据是如何获取数据的? 在事务5中查询了两次id为30的记录由于隔离级别为Read Committed所以每一次进行快照读都会生成一个ReadView那么两次生成的ReadView如下。 那么这两次快照读在获取数据时就需要根据所生成的ReadView以及ReadView的版本链访问规则到undolog版本链中匹配数据最终决定此次快照读返回的数据。
先来看第一次快照读具体的读取过程 图片纠正 左下角的undolog应该没有事务4的那一条版本数据因为从图中我们可以得知在事务5执行第一次快照的时候事务4还没有来得及对表中的数据进行修改。 在进行匹配时会从undo log的版本链从上到下进行挨个匹配在匹配到 发现满足条件返回的数据就是版本链中记录的这条数据。
第二次快照读同理
在RR隔离级别下
RR隔离级别下仅在事务中第一次执行快照读时生成ReadView后续复用该ReadView。 而RR 是可重复读在一个事务中执行两次相同的select语句查询到的结果是一样的。
那MySQL是如何做到可重复读的呢? 我们简单分析一下就知道了 我们看到在RR隔离级别下只是在事务中第一次快照读时生成ReadView后续都是复用该ReadView那么既然ReadView都一样 ReadView的版本链匹配规则也一样 那么最终快照读返回的结果也是一样的。
所以呢MVCC的实现原理就是通过 InnoDB表的隐藏字段、UndoLog 版本链、ReadView来实现的。而MVCC 锁则实现了事务的隔离性。 而一致性则是由redolog 与 undolog保证。