多版本并发控制 (MVCC)
- MVCC (multi-version concurrency control) 多版本并发控制。
- MVCC 是一种并发控制的方法。
- 在数据库管理系统中,实现对数据库的并发访问,主要是为了提高数据库的并发性能。InnoDB 引擎支持 MVCC,因为 myIsam 不支持事务所以也不支持 MVCC。
- 在编程语言中实现事务内存。
- 为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务的时间戳关联,读操作只读该事务开始前的数据库的快照。
InnoDB 的 MVCC 机制的实现主要是为了提高数据库的并发性能,用更好的不加锁的方式去处理读-写冲突,做到即使有读写冲突时,也能不加锁非阻塞的并发读。
MVCC 机制在读已提交 RC 和可重复读 RR 的隔离级别下起作用。
不加锁的读是快照读,加锁的读是当前读,所以 MVCC 的并发读是快照读。
- 快照读
- 是基于多版本并发控制即 MVCC机制,既然是多版本,那么快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本的数据。
- 如下的操作是快照读:不加锁的 select 操作(事务级别不是串行化,串行化的是当前读)
- 当前读
- 它读取的记录都是数据库中当前的最新版本,会对当前读取的数据进行加锁,防止其他事务修改数据,这种锁是一种悲观锁。
- 如下操作都是当前读:
- select lock in share mode (共享锁)
- select for update (排他锁)
- update (排他锁)
- insert (排他锁)
- delete (排他锁)
- 串行化事务隔离级别
MVCC 的两种实现方式
- 将数据记录的多个版本保存在数据库中,当这些不同版本的数据不再需要时,垃圾收集器回收这些记录。如:postgresql(VACUUM)
- 数据库保存最新版本数据,旧版本的数据存储在单独的位置。比如: SQL Server(tempdb),Oracle/InnoDB(undo log)
MVCC 的好处
- 提高了数据库的并发性能
- 避免了加锁操作
- 降低了开销
- 通过多版本解决了脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题(更新的问题可以配合锁机制比如乐观锁悲观锁使用)
- MVCC + 悲观锁/悲观锁 ,这种组合的方式最大程度的提高数据库并发性能,并解决读写冲突(用 MVCC 解决),和写写冲突导致的问题(用乐观锁/悲观锁来解决)。
InnoDB 实现 MVCC
它的实现依赖于以下 3 点:
- 记录中的 3 个隐式字段
- undo log
- Read View
3 个隐式字段:
数据库的每行记录除了我们自定义的字段外,还有数据库隐式定义的 DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID 等字段。
- DB_TRX_ID: 6byte,最近一次修改或插入操作的事务 ID,记录创建这条记录的 ID 或者最后一次修改这条记录的事务 ID。
- DB_ROLL_PTR: 7byte,回滚指针,指向这条记录的上一个版本(存储于 rollback segment 里),用于配合 undo log 指向上一个旧版本。
- DB_ROW_ID: 6byte,隐含的自增 ID (隐藏主键),如果数据表没有主键,InnoDB 会自动以 DB_ROW_ID 产生一个聚簇索引。有主键就是主键 ID。
undo log
undo log 保存了记录修改前的镜像。有以下两种 undo log
- update undo log
- 事务在进行 update 或 delete 时产生的 undo log;
- 不仅在事务回滚时需要,在快照读时也需要;
- 只有在快速读或事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一清除。purge线程的作用,是清理回滚段(undo segment)的同时,把被删除的行也从数据块中清除,实现真正的物理上的删除。
- insert undo log
- 代表事务在 insert 即插入新记录时产生的 undo log;
- 只在事务回滚时需要;
- 在事务提交或回滚后可以被立即丢弃。
Read View
- 可以理解为,对数据在每个时刻的状态,拍成照片记录下来,如果之后想要获取某时刻的数据,获取的还是原来照片上的数据,是不会变的。
- 是事务开启时当前时刻所有活跃事务的一个集合,这个数据结构中记录并维护了当前活跃事务的 ID, 包括 Read View 中事务最大的 ID 和最小的 ID。
- 还可以理解为一个版本链的集合,是 InnoDB 为实现 MVCC 所使用的内部快照。
RR (可重复读)隔离级别下会在第一次查询时创建 Read View。
RC (读已提交)隔离级别下会在每次查询时创建 Read View。
Read View 的好处
Read View 主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个 Read View 读视图,把它当做条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的 undo log 里面的某个版本的数据。