简介
- Multi Version Concurrency Control (MVCC), 即多版本并发控制, 是基于锁的并发控制。(Lock-Based Concurrency Control)
- MVCC在Oracle, MySQL, PostgreSQL等主流RDBMS中都有应用, 在此仅解释MySQL中的MVCC。
- 特征优势: 读不加锁, 读写不冲突, 有利于提升读多写少的OLTP应用中系统的并发性能。
- 在理解MVCC前需要复习一下MySQL架构, 事务隔离级别 以及锁机制。
MySQL架构
-
架构图
-
Connector
- 连接器, 不同语言的客户端通过mysql的协议与mysql服务器进行连接通信。
-
Connection Pool
- 连接池, 进行权限验证, 连接池管理, 线程管理等。
-
SQL Interface
- SQL接口, 生成 Data Manipulation Language(DML), Data Defination Language(DDL), 存储过程, 视图, 触发器等。
-
Parser
- 解析器
- 内部将文本格式转换为二进制结构
- 目的是为了让优化器更好的处理指令,以便以最优的路径,最少的耗时返回我们想要的结果。
- 解析器
-
Optimizer
-
查询优化器
-
对语法分析树的形态进行修改,把语法分析树变为查询树。
-
逻辑优化
- 逻辑优化阶段主要解决的问题是: 如何找出SQL语句等价的变换形式,使得SQL执行更高效。
-
物理优化
- 查询优化器在物理优化阶段,主要解决的问题:
- 从可选的单表扫描方式中,挑选什么样的单表扫描方式是最优的?
- 对于两个表连接时,如何选择是最优的?
- 对多个表连接,连接顺序有多种组合,是否要对每种组合都探索?如果不全部探索,怎么找到最优的一种组合?
- 目前数据库的查询优化器通常融合这两种方式。
- 查询优化器在物理优化阶段,主要解决的问题:
-
-
-
Cache Buffer
- 缓存区
- 将sql 文本及缓存结果,用KV形式保存再服务器内存中,如果运行相同的sql,服务器直接从缓存中去获取结果,不需要在再去解析、优化、执行sql。
- 具体的命中条件, 工作流程, 缓存失败, 缓存内存管理, 缓存的使用时机, 缓存参数, 减少内存碎片策略等暂不展开。
- 缓存区
-
Pluggable Storage Engines
- 可植入的存储引擎
- 是MySql中具体的与文件打交道的子系统。
- 随着MyISAM在mysql5.5后被废弃, 日常开发常用的也就是InnoDB了。
- 可植入的存储引擎
-
File System
- 文件系统
- NTFS(New Technology File System):
- 是Microsoft公司开发的专用文件系统,从Windows NT 3.1开始成为Windows NT家族的标准文件系统。
- Mac OS X内核能对NTFS进行有限的读操作。Linux和BSD提供自由及开放源代码的软件,可用于读写NTFS文件。
- 记得知乎上之前有过比较坑的时间延时bug, 就是因为一堆NTFS硬盘中夹杂了一块FAT32......
- NFS(Network File System):
- 网络文件系统, 允许网络中的计算机之间通过TCP/IP 网络共享资源。
- SAN(Storage Area Network):
- 存储区域网络, 建立专用于存储的区域网络,以达到存储和主机的物理分离。
- 主要面向企业级存储。
- NAS(Network Attached Storage):
- 网络附加存储, 可以简单理解为便捷的局域网存储装置, 在linux系统中可以通过NFS协议挂载NAS存储。
- NAS相对于SAN拥有自己的操作系统, 可以与各个系统更好的兼容, 且更加灵活。
- NTFS(New Technology File System):
- 文件系统
-
Files & Logs
- 文件及日志
- InnoDB中与数据持久性, 一致性相关的日志:
- Bin Log
- 是mysql服务层产生的日志,常用来进行数据恢复、数据库复制,常见的mysql主从架构,就是采用slave同步master的binlog实现的
- Redo Log
- 记录了数据操作在物理层面的修改
- mysql中使用了大量缓存,修改操作时会直接修改内存,而不是立刻修改磁盘
- 事务进行中时会不断的产生redo log,在事务提交时进行一次flush操作,保存到磁盘中。
- 当数据库或主机失效重启时,会根据redo log进行数据的恢复,如果redo log中有事务提交,则进行事务提交修改数据。
- Undo Log
- 进行数据修改时会记录undo log, 用于数据的撤回操作。
- 比如,插入对应删除,修改对应修改为原来的数据,通过undo log可以实现事务回滚,并且可以根据undo log回溯到某个特定的版本的数据,实现MVCC。
- Bin Log
- InnoDB中与数据持久性, 一致性相关的日志:
- 文件及日志
事务隔离级别
-
ACID就不说了, 老生常谈。
-
Read Uncommitted
- 读未提交, 可以读取尚未提交的数据。
- 缺点: 会产生脏读, 幻读, 不可重复读。
-
Read Committed
- 读已提交(大多数数据库默认事务隔离级别, MySQL不是)
- 优点: 避免了脏读
- 缺点: 可能会造成不可重复读
-
Repeatable Read
- 可重复读(MySQL 默认事务隔离级别)
- 优点: 避免了不可重复读
- 缺点: 可能出现幻读
-
Serializable
-
序列化, 最高的事务隔离级别
-
优点: 避免了脏读, 幻读, 不可重复读
-
缺点: 代价高, 性能很低
-
很少使用
-
锁机制
- 读锁
- 也称共享锁, 若事务I对数据对象A加上读锁, 则事务T可以读A但不能修改A。
- 其他事务只能再对A加读锁, 而不能加写锁, 直到I释放A上的读锁。
- 它保证了其他事务可以读A, 但是在I释放A上的S锁之前不能对A做任何修改。
- 写锁
- 又称排他锁, 若事务I对数据对象加上写锁, 事务I可以读A也可以修改A。
- 其他事务不能再对A加任何锁, 直到I释放A上的锁。
- 这保证了其他事务在I释放A上的锁之前不能再读取和修改A。
- 表锁
- 操作对象是数据表。
- MySQL大多数锁策略都支持表锁,是系统开销最低但并发性最低的一个锁策略。
- 行级锁
- 操作对象是数据表中的一行。
- MVCC较多使用该锁。
- 对系统开销较大, 但处理高并发较好。
重要字段
- data_trx_id
- 用来标识最近一次对本行记录做修改(insert|update)的事务的标识符, 即最后一次修改(insert|update)本行记录的事务id。
- DB_TRX_ID记录了行的创建的时间,删除的时间在每个事件发生的时候,每行存储版本号,而不是存储事件实际发生的时间。
- 每次事务的开始此版本号都会增加。
- 自记录时间开始,每个事物都会保存记录的系统版本号。
- 依照事物的版本来检查每行的版本号。
- data_poll_ptr
- 指写入回滚段(rollback segment)的 undo log record (撤销日志记录记录)。如果一行记录被更新, 则 undo log record 包含 '重建该行记录被更新之前内容' 所必须的信息。
MVCC实现
- 基本原理
- MVCC通过在每行纪录后面保存两个隐藏的列来实现的。
- 一个保存了行的创建时间[实际存储的是版本号]
- 一个报存了行的过期时间(或删除时间)[实际存储的是版本号]
- 每开始一个新的事务, 系统版本号都会自动递增。
- 事务开始时刻的系统版本号会作为事务的版本号, 用来与查询到的每行记录的版本号进行比较。
- MVCC通过在每行纪录后面保存两个隐藏的列来实现的。
- 在RR(Repeatable Read) 隔离级别下, MVCC具体操作如下:
- select
- InnoDB会根据以下两个条件检查每行记录:
- InnoDB只查找版本早于当前事务版本的数据行。这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
- 行的删除版本,要么未定义,要么大于当前事务版本号。这样可以确保事务读取到的行,在事务开始之前未被删除。
- InnoDB会根据以下两个条件检查每行记录:
- insert
- InnoDB为插入的每一行保存当前系统版本号作为行版本号。
- delete
- InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
- update
- InnoDB为插入一行新纪录,保存当前系统版本号作为行版本号。
- 同时,保存当前系统版本号到原来的行作为行删除标识。
- select
- 优点
- 保存这两个额外系统版本号,使大多数读操作都可以不用加锁。
- 这样设计使得读数据操作很简单,性能很好。
- 缺点
- 每行纪录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。