本文纯理论学习,无知之处请给与包涵! 写作不易,觉得好,麻烦请点广告支持下 分享一下!
作者不支持读者的任何抽象行为,阅读本文产生的任何后果,作者概不负责
MVCC 英文全称叫多版本并发控制协议. 以前做ORACLE DBA时候没有听说过.后来转到MYSQL DBA就听说了. 另外还有个WAL,这个是POSTGRE SQL的叫法. 这些概念不就是早就实现过得呀! ORACLE REDO,先写日志,后写数据.凡是都要记录日志.ORACLE的日志量可大了, REDO要保护系统表空间,还有辅助表空间,用户表空间,以及UNDO表空间的数据改变前的向量.
ORACLE UNDO 没有听人说 UNDO实现了MVCC.老盖出了那么多书,好像也没有提过.也许我人老了记忆性差.
MVCC 包含两方面:
-
Multi-Versioning,MV:生成多版本的数据内容,使得不同请求(读写)可以获取响应版本数据。
-
Concurrency Control,CC:并发控制,使得并行执行的内容能保持串行化结果。
并发控制,我怎么感觉不出来呢? 是在说读的并发控制吗? 读也需要并发控制吗? 我以为是用MUTE,LATCH等内存锁.
难道是写的并发控制? 这有可能
行的元组
数据库中每个数据的数据头(Header)会包含数据的 metadata 信息,这些信息被称为行的元组
txn-id: 数据的TID,用以实现写锁。begin-ts & end-ts:标识该元组版本的生命起止时间。pointer:指向同一行数据相邻(新/旧)版的指针,依靠指针,版本数据可以形成一个单向链表
上面4个字段,只是意思意思下, ORACLE只有两个 一个锁标志,另外一个是回滚指针. MYSQL会多了些字段.完成是否可见性(ReadView) 查询活跃事务列表,判断本事务ID..
并发控制协议
并发控制协议解决的是不同事务间执行顺序和结果的问题(而不是多版本数据),也就是通过锁 或者时间顺序 保持事务读写数据的串行性 说白了就是修改事务,ORACLE的 DML语句 写的并发控制.论文介绍了四种协议:Timestamp Ordering 、Two-phase Locking 、Concurrency Control 和 Serialization Certifier我在这里不介绍论文,太理论,太烧脑. 我们知道ORACLE直接在行头用锁控制
ORACLE 通过行锁标志+块的事务槽完成DML事务的并发控制.
多版本存储 读写分离核心
多版本存储技术决定了各种不同DB 读写分离实现细节
1 表内存储和表外存储
2 整行存储,修改列增量存储
ORACLE和MYSQL 采用 表外+列增量存储
SQL SERVER 采用 表外+整行存储
POSTGRE SQL 采用 表内+整行存储
表内+整行存储:
即使数据行中只有少量字段发生了变更。另外,如果表中带有非内联数据,如BLOB、TEXT字段,即使事务没有修改它,也会导致引入大量的重复,导致表数据膨胀。Postgres表膨胀这样也是原因之一
SQL SERVER 采用 表外+整行存储
既然行版本控制,是通过在读写并发时,让读会话去读取历史行版本数据,从而避免阻塞等待,那么这些历史行数据,存储在什么地方?
答案是: tempdb 数据库!
ORACLE和MYSQL 采用 表外+列增量存储
这种方案对于更新操作很理想,因为减少了内存的分配。但是对于偏向与读的场景负担大。当进行获取一个元组的多个属性的读操作时,数据库需要遍历版本链,必须从各个字段的版本数据链中获取到对应版本的字段值,再进行拼凑,这就带来了一定的额外开销。我们拿ORACLE 来说, 看图回滚指针存在块的头部,也就是说更新某行的某个列,就要修改块头的UNDO地址, 那么数据块有很多行,很多列.那么这个UNDO地址就修改很频繁. 这数据块的UNDO历史版本就很长. 当某个SELECT想要读取它需要的数据时候,需要遍历这个数据块的历史链条.越过很多不是该行的修改版本.
为此这表外+列增量方式 还需要从当前块或者行来逆历史版本共同构造出CR块或者CR行
其实最佳的应该是SQLSERVER 表外+整行存储. 如果对TEXT,BLOB做下优化就完美了.如果没有修改TEXT,BLOB字段,那么就复制基本行去历史区域.
如果修改了就一起复制过去历史区域. 第一次做一下FULL PAGE.或者其它什么方式进行优化 或者在TEXT,BLOB页增加版本控制的字段.
最后就是 回滚覆盖版本太长了是个问题, 不需要的旧版本应该清理掉. ORACLE 经典的错误
**Oracle ORA-01555
这在ORACLE 11G 以前是个头痛的事情,后来ORACLE根据查询最长时间的SELECT,指定回滚段保留时间,这又导致UNDO的膨胀.
它有三种状态来管理生命周期,分别是ACTIVE UNEXPIRED EXPIRED,
活跃事务的段总是ACTIVE状态;
已完成事务的,但是在UNDO_RETENTION周期内的是UNEXPIRED状态;
已完成事务的UNDO,但已经过了UNDO_RETENTION保留周期的是EXPIRED状态。**
ORACLE 不用专门的程序去清理历史版本,而是采用覆盖重用的方式.
MYSQL 需要专门的线程去清理,它采用事务级别盯着活跃事务列表
事务记录它读写数据行的集合,DBMS需要监控集合中的版本数据是否都过时了。当某个事务的版本数据集合对所有的活跃事务都不可见时,就可以将这个集合中的版本数据都进行清理。优点是数据库可以立即回收对应事务的所有存储空间,缺点是数据库要跟踪每个时期的事务读/写集
MySQL的 MVCC 主要依赖于:数据行中的隐式字段、undo log,consistent read view
undo log 中主要保存了数据的基本信息,比如说日志开始的位置、结束的位置,主键的长度、表id,日志编号、日志类型
InnoDB存储引擎中的每行记录都包含一些隐藏字段,如DB_TRX_ID最后修改该行的事务ID、DB_ROLL_POINT 回滚指针
当事务执行读写分离时,会产生一个ReadView,ReadView中包含了系统中活跃的事务ID列表。
通过比较ReadView与历史版本链中的DB_TRX_ID,可以确定哪个历史版本的数据对当前事务可见。数据库每次数据修改时,都会在undo log中留下快照,用于读取旧版本信息。
ReadView 其实就是一个保存事务ID的list列表。记录的是本事务执行时,MySQL还有哪些事务在执行,且还没有提交。
它主要包含这样几部分:
-
m_ids,当前有哪些事务正在执行,且还没有提交,这些事务的 id 就会存在这里;
-
min_trx_id,是指 m_ids 里最小的值;
-
max_trx_id,是指下一个要生成的事务 id。下一个要生成的事务 id 肯定比现在所有事务的 id 都大;
-
creator_trx_id,每开启一个事务都会生成一个 ReadView,而 creator_trx_id 就是这个开启的事务的 id。
这样在访问某条记录时,只需要按照下边的步骤判断该记录在版本链中的某个版本(trx_id)是否可见:
1、trx_id < m_ids列表中最小的事务id
表明生成该版本的事务在生成ReadView前已经提交,所以该版本可以被当前事务访问。
2、trx_id > m_ids列表中最大的事务id
表明生成该版本的事务在生成ReadView 后才生成,所以该版本不可以被当前事务访问。
3、m_ids列表中最小的事务id ≤ trx_id ≤ m_ids列表中最大的事务id(可以取等于,感谢网友指出)
此处比如m_ids为[5,6,7,9,10]
①、若trx_id在m_ids中,比如是6,可以分成2种情况
a. creator_trx_id也是6,说明这条trx_id记录是当前事务产生的,可以被访问
a. creator_trx_id不是6,说明这条trx_id记录不是当前事务产生的,并且创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问。
②、若trx_id不在m_ids中,比如是8:说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
一句话说:当trx_id在m_ids中且creator_trx_id != trx_id,或者大于m_ids列表中最大的事务id的时候,这个版本就不能被访问。
如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本,如果最后一个版本也不可见的话,那么就意味着该条记录对该事务不可见,查询结果就不包含该记录。
MYSQL 依靠锁链完成事务的并发控制
因此 MVCC 应该说是多版本读写分离
当然也可以这样说 MVCC 是多版本读写分离提升读并发的协议
基于MYSQL的JAVA初级优化措施
MYSQL RR隔离下加锁初步理解
MYSQL RC下加锁的初步理解
不会C/C++的不是好DBA,一个解析MYBAITS的脚本
架构DBA
终于搞明白64位LINUX页表问题
一段直接路径读取文件LINUX C代码