MVCC
目录
借助wiki上的解释:
Multiversion concurrency control (MCC or MVCC), is a concurrency control method commonly used by database management systems to provide concurrent access to the database and in programming languages to implement transactional memory.
MVCC如何实现
• 简单说,就是更新数据时,保留原先版本的数据, 即一行数据存在多个版本。
• 实现方法: – 回滚段的实现方法(Oracle、MySQL Innodb的实现方 法) – 在原先的数据文件中的旧版本数据不删除,生成新版 本的数据(PostgreSQL的实现方法)
• 通常与事务的功能集成在一起:
• 但MVCC时事物回滚或提交之后,旧版本的数据 仍然需要保留一段时间,是延迟清除的。
旧版本数据延迟清除的原因
• 就是为了提供快照读(一致性读)的功能
• 对于Read Commited隔离级别:需要保证当开始 执行一个SQL之后,这个SQL是读一个不变的快 照的数据,即使在这个SQL执行过程中有其它的事务提交了,这些提交的数据对于这个SQL也是 看不到了,这样保证了数据的一致性。
• 对于Read Repeatable隔离级别:从这个事务开 始之后,看到的数据就是一个不变的数据,执行 相同的SQL总是可以看到相同的数据。所以叫"可重复读"
快照读(一致性读)的用途
如果没有MVCC的解决方案
管理者在事务中持有对A的锁,等待获取B的锁,而用户B在事务中持有对B的锁,等待获取A的锁,于是便发生了死锁。
MVCC总结
- 查询和更新、删除、插入操作互相不阻塞
• 当开始一个查询后,读到的数据总是查询 开始时那个时间点的快照
- 在查询开始后,发生的变更(即使已提交), 这次查询也是看不到的。
- 一个事务无论运行多长时间,看到数据都是相 同的
- 不同开始时间的事务中相同的查询,返回的数 据也可能不同
Mysql MVCC实现
Innodb的事务的相关概念
• redo log – 在变更数据之前,把变更先记录到一个文件中, 称为redo log
• undo log – 与redo log相反,undo log是为回滚而用
• rollback segment – 在Innodb中,undo log被划分为多个段,具体 某行的undo log就保存在某个段中,称为回滚 段。
Innodb多版本的实现
事务1:insert t(id, col1,col2,col3,col4) values(23,'a1','b1','c1','d1');
行上有三个隐含字段:分别对应该行的rowid、事务号和回滚指针, id、Col1~Col4是表各列的名字,23、'a1'~'d1'是其对应的数据。
- 多次更新后,回滚指针会把不同版本的记录 串在一起。
- 在Innodb中存在purge线程,它会查询那些 比现在最老的活动事务还早的undo log,并 删除它们,从而保证undo log文件不至于无 限增长。
Innodb的事务的提交与回滚
提交与回滚
– 当事务正常提交时,Innodb只需要更改事务状 态为COMMIT即可,不需做其他额外的工作
– Rollback需要根据当前回滚指针从undo log中 找出事务修改前的版本,并恢复。
• 如果事务影响的行非常多,回滚则可能会很慢,根 据经验值没提交的事务行数在1000~10000之间, Innodb效率还是非常高的,大量的回滚效能会影响。
• 回滚时,也会产生redo日志 – Innodb的COMMIT效率高,Rollback代价大
Innodb的可见性判断
• InnoDB表会有三个隐藏字段
– 6字节的DB_ROW_ID
– 6字节的DB_TX_ID
• InnoDB内部维护了一个递增的tx id counter,其当 前值可以通过show engine innodb status获得
– 7字节的DB_ROLL_PTR(指向回滚段的地址)
可见性比较的方法:
– 并不是用当前事务ID与表中各个数据行上的事 务ID去比较的
– 在每个事务开始的时候,会将当前系统中的所 有的活跃事务拷贝到一个列表中 (read view), 根据read view最早一个事务ID和最晚的一个事 务ID来做比较的,这样就能确保在当前事务之 前没有提交的所有事务的变更以及后续新启动 的事务的变更,在当前事务中都是看不到的。
– 当然,当前事务自身的变更还是需要看到的。
Oracle的多版本实现
• Oracle也是通过回滚段来实现多版本的 – 但Oracle的实现更复杂,更精细一些。
• Oracle中也有事务ID,但不是递增的
– Undo Segment Number + Transaction Table Slot Number + Wrap
• 与innodb不一样的地方:
– 事务信息并不是记录在每个数据行上的,而是 在块头中的ITL槽上,所以相对来说更省空间
ITL解释
• ITL(Interested Transaction List)在Oracle 数据块的头部
– ITL记录在一个数据块中有多条,每一条itl可 以看作是一个记录,每条记录常被称为槽位 (itl slot)
– 一个itl slot只可以记录一个事务的信息,如果 这个事务已经提交或回滚了,那么这个itl的 位置就可以被反复使用。
• 每个数据块上itl槽的多少可以动态创建, 建表时可以指定: – initrans:每个数据块默认ITL槽数目,默认 为2 maxtrans:每个数据块最多的ITL槽数,最 大255
ITL内容
Oracle数据块的结构
Oracle的多版本实现
• 由于Oracle中的事务ID不是递增的,为了判 断事务之间的先后关系,需要一个递增序 号,这个序号在Oracle中就叫SCN(当然 SCN还有其它用处)
• SCN(System Change Number)
– 是顺序递增的一个数字,在Oracle 中用来标识 数据库的每一次改动,及其先后顺序。
– SCN是由6字节组成,最大值是0xffff.ffffffff
– 单节点的instance中,SCN值存在SGA区,由 system commit number latch保护。任何进程 要得到当前的SCN值,都要先得到这个latch。
– RAC中是通过排队机制(Enqueue)实现SCN在 各并行节点之间的顺序增长,这里不再赘述。
旧版本的数据在回滚段中的结构
PostgreSQL MVCC实现
什么?PostgreSQL没有回滚段!!!
– 是的,没有回滚段。旧数据是放在原有数据文 件中的
– 如果放在原有的数据文件中,旧数据越来越多 怎么办?
• 垃圾回收操作vacuum来做这个事。
• 有自动垃圾回收autovacuum
• 更新操作中新行的物理位置发生了变化, 非更新列的索引是不是也要更新?
– 通常不会,HOT技术。如果原有的数据块之间 有空间,旧行与新行之间会建一个链接,索引 上仍然指向旧的数据行。
• 垃圾回收的代价会不会影响性能?
• 有很多参数控制这个影响:vacuum_cost_delay, vacuum_cost_limit
• 实现方法
– 每行上有xmin和xmax两个系统字段
– 当插入一行数据时,将这行上的xmin设置为当 前的事务id,而xmax设置为0
– 当更新一行时,实际上是插入新行,把旧行上 的xmax设置为当前事务id,新插入行的xmin设 置为当前事务id,新行的xmax设置为0
– 当删除一行时,把当前行的xmax设置为当前事 务id
– 当读到一行时,到commitlog中查询xmin和 xmax对应的事务状态是否是已提交还是回滚了, 就能判断出此行对当前行是否是可见。
– autovacuum进程会把一些不要的旧行清理掉
PostgreSQL事务ID
• 与innodb类似,是一个递增的数字,常常 被称为xid
– 但是一个无符号的32bit的数字表示
• 如何知道事务是提交了还是回滚了?
– 事务的状态记录一个叫commitlog的位图文件 中,即pg_clog目录下的文件中
– 每个事务的状态用两个bit来表示:
– #define TRANSACTION_STATUS_IN_PROGRESS 0x00
– #define TRANSACTION_STATUS_COMMITTED 0x01
– #define TRANSACTION_STATUS_ABORTED 0x02
– #define TRANSACTION_STATUS_SUB_COMMITTED 0x03
PostgreSQL MVCC实现
• 事务ID到达最大的一个数字后,怎么办?
– 到大最大后,又重新开始,
– 此问题被称之为事务ID回卷问题(Transaction ID Wraparound) • 记录事务的commitlog文件会不会不断的增 大?
比较事务新旧的方法
– 普通事务的比较方法:(int32) (id1 - id2) < 0
– 表达式算出来值为真,则id1比id2更旧一些, 为假则id1比id2新 • 例子:id1=4294967294,经过4个事务,id2=5, id1-id2=4294967289,而4294967289因大于2^31, 转成int32后会变成一个负数,表过式为真,所以id1 比id2更旧
– BootstrapXID比所有其它事务都旧,包括 FrozenXID
– FrozenXID比普通事务旧
可见性判断
• 与innodb一样,事务开始时,会把当前活 跃的事务ID记录到一个列表中,这称之为 快照。
• 系统先通过判断t_xmin是否在全局活跃事务 列表中、是否在事务快照活跃事务列表中、 根据事务提交日志判断事务是提交还是回 滚了等来判断t_xmin事务是否在事务开始时 已经提交