提示:本文章的内容来源于自己所学的一些知识以及网络
博主理解:其实在Java后端面试中,所谓的“八股文”并不是让大家完整地背下来,只是将自己学会的知识,用自己的语言讲给面试官听,面试官希望听到的是你对这个知识点的理解,深度。“八股文”并不是一门技术,希望各位道友不要过多地痴迷,能够及时解决Bug以及根据需求选择合适的架构方案才是一个程序员基本素养。这个面经系列我会一直更新下去,如果对你有帮助,麻烦点个关注,谢谢!
关键字:支持事务、行级锁、外键
可以通过 show engines;
的方式查看对应的数据库支持的存储引擎。
关于InnoDB的页结构如下:
可以看出在页中对应着四条记录,页目录记录的是相应的主键索引最大值,这也就解释了在叶子节点中实际上存的是页(page),而每个页节点中存储的才是表中一行行数据,这里还牵涉到B+树在三层的时候能够存在多少行数据的问题,如下:
假设每个页的主键索引为int型,占用4字节,每页的地址占用6字节,加起来就是10字节,我们知道在B+树中,非叶子节点是不存数据的,只存索引,对应着是图中的绿色框,每页的容量固定为16kb,则绿色的框(第一层)可以管理1638页,这样就不难计算出两层的B+树能够存储多少行数据了。假设一页能够存8条数据(每个数据占用2kb)那么两层的B+树能够存数据1638*(16kb/2kb)=13104条数据,那么三层B+树存储的数据量为1638*13104=21464352,也就是两千万条数据,一般来讲希望MySQL的数据量不要超过两千万行,并且B+树不要超过三层,否则会导致查询效率变慢,此时就要采取分库分表的措施(后面我会介绍分库分表的知识点)。
如何选择?
1、是否需要支持事务,如果需要选择innodb,如果不需要选择myisam
2、如果表的大部分请求都是读请求,可以考虑myisam,如果既有读也有写,使用innodb
现在mysql的默认存储引擎已经变成了Innodb,推荐使用innodb
关键字:索引、哈希索引、B+树索引
为什么不用二叉树或者红黑树作为索引?单链表、层级深
B+树是普通二叉树的扩展,如果采用二叉树作为MySQL的索引结构,会存在如下的问题:1)顺序插入时,会形成一个链表,查询的性能大大降低。2)大数据量的情况下,层级较深,检索速度慢。可能大家会想到,普通的二叉树存在不平衡问题,那么采用自平衡二叉树,比如红黑树?红黑树是可以解决数据的平衡问题,但是红黑树也是二叉树,会存在一个缺点:1)大数据量的情况下,层级较深,检索速度变慢。所以,在MySQL的索引结构中,并没有选择二叉树或者红黑树,而选择的是B+Tree,那么什么是B+Tree呢?在详解B+Tree之前,先来介绍一个B-Tree。
B-Tree的数据结构:·多叉树、中间元素分裂、所有节点存储数据
B树是一种多叉路衡查找树(多叉树),相对于二叉树,B树每个节点可以有多个分支,即多叉。以一颗最大度数(max-degree)为5(5阶)的b-tree为例,那这个B树每个节点最多存储4个key,5个指针:
注意:树的度数指的是一个节点的子节点个数。
B-Tree演示网址 https://www.cs.usfca.edu/~galles/visualization/BTree.html
特点:1)5阶的B树,每一个节点最多存储4个key,对应5个指针。2)一旦节点存储的key数量到达5,就会裂变,中间元素向上分裂。3)在B树中,非叶子节点和叶子节点都会存放数据。
B+Tree的数据结构:叶子结点存数据、非叶子结点存索引、单向链表
B+Tree是B-Tree的变种,只要是在B树上做了改进的都可以称之为B+树。以一颗最大度数(max-degree)为4(4阶)的b+tree为例,来看一下其结构示意图:
我们可以看到,两部分:
绿色框框起来的部分,是索引部分,非叶子结点,仅仅起到索引数据的作用,不存储数据。
红色框框起来的部分,是数据存储部分,在其叶子节点中要存储具体的数据。
B+Tree演示网址 https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html
特点:1)所有的数据都会出现在叶子节点。2)叶子节点形成一个单向链表。3)非叶子节点仅仅起到索引数据作用,具体的数据都是在叶子节点存放的。
MySQL中优化后的B+Tree数据结构:叶子结点存数据、非叶子结点存索引、双向链表
MySQL索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能,利于排序。
聚簇索引与非聚簇索引:Innodb存储引擎在进行数据插入的时候必须要绑定到一个索引列上,默认是主键,如果没有主键,会选择唯一键,如果没有唯一键,那么会选择生成6字节的row_id,跟数据绑定在一起的索引我们称之为聚簇索引,没有跟数据绑定在一起的索引我们称之为非聚簇索引(与引擎的存储文件有关,.idb文件是数据和索引放在一起;.MYD文件和.MYI文件是分别存储数据和索引的文件=>非聚簇索引)。
Innodb存储引擎中既有聚簇索引也有非聚簇索引,而MyISAM存储引擎中只有非聚簇索引。
回表查询:
optimize table xxx;
来强制更新统计信息,或者强制使用某个索引 force index
,人工干预优化器的选择) ACID:
原子性:undo log(MVCC)
一致性: 最核心和最本质的要求
隔离性:锁,mvcc(多版本并发控制)
持久性:redo log
数据库的事务隔离级别有四种,分别是读未提交、读已提交、可重复读、序列化,不同的隔离级别下会产生脏读、幻读、不可重复读等相关问题,因此在选择隔离级别的时候要根据应用场景来决定,使用合适的隔离级别。
各种隔离级别和数据库异常情况对应情况如下:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ- UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE- READ | × | × | √ |
SERIALIZABLE | × | × | × |
SQL 标准定义了四个隔离级别:
事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重复读)并不会有任何性能损失。
事务A按照一定条件进行数据读取,期间事务B插入了相同搜索条件的新数据,事务A再次按照原先条件进行读取时,发现了事务B新插入的数据称之为幻读。(在数据库隔离级别为RR的情况下,执行同一条SQL语句出现数据不一致的情况,根本原因是因为快照读和当前读发生了混用,一般出现了幻读,才取加临键锁,next-key 来解决)
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB ;
INSERT into user VALUES (1,'1',20),(5,'5',20),(15,'15',30),(20,'20',30);
假设有如下业务场景:
时间 | 事务1 | 事务2 |
---|---|---|
begin; | ||
T1 | select * from user where age = 20;2个结果 | |
T2 | insert into user values(25,‘25’,20);commit; | |
T3 | select * from user where age =20;2个结果 | |
T4 | update user set name=‘00’ where age =20;此时看到影响的行数为3 | |
T5 | select * from user where age =20;三个结果 |
执行流程如下:
1、T1时刻读取年龄为20 的数据,事务1拿到了2条记录
2、T2时刻另一个事务插入一条新的记录,年龄也是20
3、T3时刻,事务1再次读取年龄为20的数据,发现还是2条记录,事务2插入的数据并没有影响到事务1的事务读取
4、T4时刻,事务1修改年龄为20的数据,发现结果变成了三条,修改了三条数据
5、T5时刻,事务1再次读取年龄为20的数据,发现结果有三条,第三条数据就是事务2插入的数据,此时就产生了幻读情况
此时大家需要思考一个问题,在当下场景里,为什么没有解决幻读问题?
其实通过前面的分析,大家应该知道了快照读和当前读,一般情况下select * from …where …是快照读,不会加锁,而 for update,lock in share mode,update,delete都属于当前读,会使得read-view发生改变,改变之后做select查询,读取的是最新的read-view,如果事务中都是用快照读,那么不会产生幻读的问题,但是快照读和当前读一起使用的时候就会产生幻读。
如下图所示,为演示案例:
从图中可以看出,出现了幻读情况,最后我们执行相同的SQL语句:
select * from user where age = 20;
如果都是当前读的话,如何解决幻读问题呢(锁+MVCC)?
truncate table user; // 清空表操作
INSERT into user VALUES (1,'1',20),(5,'5',20),(15,'15',30),(20,'20',30);
时间 | 事务1 | 事务2 |
---|---|---|
begin; | ||
T1 | select * from user where age =20 for update; | |
T2 | insert into user values(25,‘25’,20);此时会阻塞等待锁 | |
T3 | select * from user where age =20 for update; |
此时,可以看到事务2被阻塞了,需要等待事务1提交事务之后才能完成,其实本质上来说采用的是间隙锁的机制解决幻读问题。比如说执行如下SQL时候,
select * from user where age =20 for update;
再执行查看InnoDB锁机制的命令,
show engine innodb status\G;
MySQL是只支持一种Join算法Nested-Loop Join(嵌套循环连接),并不支持哈希连接和合并连接,不过在mysql中包含了多种变种,能够帮助MySQL提高join执行的效率。
1、Simple Nested-Loop Join
这个算法相对来说就是很简单了,从驱动表中取出R1匹配S表所有列,然后R2,R3,直到将R表中的所有数据匹配完,然后合并数据,可以看到这种算法要对S表进行RN次访问,虽然简单,但是相对来说开销还是太大了。
2、Index Nested-Loop Join
索引嵌套联系由于非驱动表上有索引,所以比较的时候不再需要一条条记录进行比较,而可以通过索引来减少比较,从而加速查询。这也就是平时我们在做关联查询的时候必须要求关联字段有索引的一个主要原因。
这种算法在链接查询的时候,驱动表会根据关联字段的索引进行查找,当在索引上找到了符合的值,再回表进行查询,也就是只有当匹配到索引以后才会进行回表。至于驱动表的选择,MySQL优化器一般情况下是会选择记录数少的作为驱动表,但是当SQL特别复杂的时候不排除会出现错误选择。
在索引嵌套链接的方式下,如果非驱动表的关联键是主键的话,这样来说性能就会非常的高,如果不是主键的话,关联起来如果返回的行数很多的话,效率就会特别的低,因为要多次的回表操作。先关联索引,然后根据二级索引的主键ID进行回表的操作。这样来说的话性能相对就会很差。
3、Block Nested-Loop Join
在有索引的情况下,MySQL会尝试去使用Index Nested-Loop Join算法,在有些情况下,可能Join的列就是没有索引,那么这时MySQL的选择绝对不会是最先介绍的Simple Nested-Loop Join算法,而是会优先使用Block Nested-Loop Join的算法。
Block Nested-Loop Join对比Simple Nested-Loop Join多了一个中间处理的过程,也就是join buffer,使用join buffer将驱动表的查询JOIN相关列都给缓冲到了JOIN BUFFER当中,然后批量与非驱动表进行比较,这也来实现的话,可以将多次比较合并到一次,降低了非驱动表的访问频率。也就是只需要访问一次S表。这样来说的话,就不会出现多次访问非驱动表的情况了,也只有这种情况下才会访问join buffer。
在MySQL当中,我们可以通过参数join_buffer_size来设置join buffer的值,然后再进行操作。默认情况下join_buffer_size=256K,在查找的时候MySQL会将所有的需要的列缓存到join buffer当中,包括select的列,而不是仅仅只缓存关联列。在一个有N个JOIN关联的SQL当中会在执行时候分配N-1个join buffer。
使用mycat或者shardingsphere中间件做分库分表,选择合适的中间件,水平分库,水平分表(按行切分),垂直分库,垂直分表(按字段切分,热点字段和非热点字段)
在进行分库分表的时候要尽量遵循以下原则:
1、能不切分尽量不要切分;
2、如果要切分一定要选择合适的切分规则,提前规划好;
3、数据切分尽量通过数据冗余或表分组来降低跨库 Join 的可能(利用空间换时间);
4、由于数据库中间件对数据 Join 实现的优劣难以把握,而且实现高性能难度极大,业务读取尽量少使用多表 Join(使用的话,在本库Join操作,不要跨库Join)。
企业的应用场景中,为了知道优化SQL语句的执行,需要查看SQL语句的具体执行过程,以加快SQL语句的执行效率。可以使用explain+SQL语句来模拟优化器执行SQL查询语句,从而知道mysql是如何处理sql语句的,详细操作见MySQL的执行计划。
Column | Meaning |
---|---|
id | The SELECT identifier |
select_type | The SELECT type |
table | The table for the output row |
partitions | The matching partitions |
type | The join type |
possible_keys | The possible indexes to choose |
key | The index actually chosen |
key_len | The length of the chosen key |
ref | The columns compared to the index |
rows | Estimate of rows to be examined |
filtered | Percentage of rows filtered by table condition |
extra | Additional information |
乐观锁并不是数据库自带的,如果需要使用乐观锁,那么需要自己去实现,一般情况下,我们会在表中新增一个version字段,每次更新数据version+1,在进行提交之前会判断version是否一致。我们所说的加行锁,重点在于理解是给索引加锁。
mysql中的绝大部分锁都是悲观锁,按照粒度可以分为行锁和表锁:
行锁:
共享锁:当读取一行记录的时候,为了防止别人修改,则需要添加S锁
排它锁:当修改一行记录的时候,为了防止别人同时进行修改,则需要添加X锁
X | S | |
---|---|---|
X | 不兼容 | 不兼容 |
S | 不兼容 | 兼容 |
排他锁时,加锁的范围:
记录锁:添加在行索引上的锁
间隙锁:锁定范围是索引记录之间的间隙,针对可重复读以上隔离级别
临键锁:记录锁+间隙锁
表锁:
意向锁:在获取某行的锁之前,必须要获取表的锁,分为意向共享锁,意向排它锁
自增锁:对自增字段所采用的特殊表级锁
锁模式的含义:
IX:意向排它锁
X:锁定记录本身和记录之前的间隙、临键锁
S:锁定记录本身和记录之前的间隙、临键锁
X, REC_NOT_GAP:只锁定记录本身、记录锁
S,REC_NOT_GAP:只锁定记录本身、记录锁
X,GAP:间隙锁,不锁定记录本身
S,GAP:间隙锁,不锁定记录本身
X,GAP,INSERT_INTENTION:插入意向锁
以下均为扩展内容:(下面对应的问题是MySQL在什么情况下加什么样的锁)
查看MySQL锁的时候需要先开启一个参数:innodb_status_output_locks=on
并使用命令 show engine innodb status\G;
来查看锁的具体情况。难点在于在不同的隔离级别加什么锁,本质在于RR下是加锁最小粒度确保不幻读
RR级别下增加了间隙锁与临键锁,为了解决的问题是幻读问题。
RC级别下加的都是行锁,为了解决的问题是脏读问题。
MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。
当前读
像select lock in share mode(共享锁), select … for update(加锁读) ; update, insert , delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
快照读(提高数据库的并发查询能力)
像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
当前读、快照读、MVCC关系
MVCC多版本并发控制指的是维持一个数据的多个版本,使得读写操作没有冲突,快照读是MySQL为实现MVCC的一个非阻塞读功能。MVCC模块在MySQL中的具体实现是由三个隐式字段,undo日志、read view,三个组件来实现的。
MVCC解决的问题
数据库并发场景有三种,分别为:
1、读读:不存在任何问题,也不需要并发控制
2、读写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读、幻读、不可重复读
3、写写:有线程安全问题,可能存在更新丢失问题
MVCC是一种用来解决读写冲突的无锁并发控制,也就是为事务分配单项增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照,所以MVCC可以为数据库解决一下问题:
1、在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
2、解决脏读、幻读、不可重复读等事务隔离问题,但是不能解决更新丢失问题
MVCC实现原理
mvcc的实现原理主要依赖于记录中的三个隐藏字段,undolog,read view来实现的。
隐藏字段
每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID, DB_ROLL_PTR, DB_ROW_ID等字段
1)DB_TRX_ID
6字节,最近修改事务id,记录创建这条记录或者最后一次修改该记录的事务id
2)DB_ROLL_PTR
7字节,回滚指针,指向这条记录的上一个版本,用于配合undolog,指向上一个旧版本
3)DB_ROW_ID
6字节,隐藏的主键,如果数据表没有主键,那么innodb会自动生成一个6字节的row_id
记录如图所示:
在上图中,DB_ROW_ID是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本
undo log
undolog被称之为回滚日志,表示在进行insert,delete,update操作的时候产生的方便回滚的日志。
当进行insert操作的时候,产生的undolog只在事务回滚的时候需要,并且在事务提交之后可以被立刻丢弃。
当进行update和delete操作的时候,产生的undolog不仅仅在事务回滚的时候需要,在快照读的时候也需要,所以不能随便删除,只有在快照读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除(当数据发生更新和删除操作的时候都只是设置一下老记录的deleted_bit,并不是真正的将过时的记录删除,因为为了节省磁盘空间,innodb有专门的purge线程来清除deleted_bit为true的记录,如果某个记录的deleted_id为true,并且DB_TRX_ID相对于purge线程的read view 可见,那么这条记录一定时可以被清除的)
下面我们来看一下undolog生成的记录链
1、假设有一个事务编号为1的事务向表中插入一条记录,那么此时行数据的状态为:
2、假设有第二个事务编号为2对该记录的name做出修改,改为lisi:
在事务2修改该行记录数据时,数据库会对该行加排他锁。
然后把该行数据拷贝到undolog中,作为 旧记录,即在undolog中有当前行的拷贝副本。
拷贝完毕后,修改该行name为lisi,并且修改隐藏字段的事务id为当前事务2的id,回滚指针指向拷贝到undolog的副本记录中。
事务提交后,释放锁:
3、假设有第三个事务编号为3对该记录的age做了修改,改为32
在事务3修改该行数据的时,数据库会对该行加排他锁。
然后把该行数据拷贝到undolog中,作为旧纪录,发现该行记录已经有undolog了,那么最新的旧数据作为链表的表头,插在该行记录的undolog最前面。
修改该行age为32岁,并且修改隐藏字段的事务id为当前事务3的id,回滚指针指向刚刚拷贝的undolog的副本记录。
事务提交,释放锁
从上述的一系列图中,大家可以发现,不同事务或者相同事务的对同一记录的修改,会导致该记录的undolog生成一条记录版本线性表,即链表,undolog的链首就是最新的旧记录,链尾就是最早的旧记录。
Read View
上面的流程如果看明白了,那么大家需要再深入理解下read view的概念了。
Read View是事务进行快照读操作的时候生产的读视图,在该事务执行快照读的那一刻,会生成一个数据系统当前的快照,记录并维护系统当前活跃事务的id,事务的id值是递增的。
其实Read View的最大作用是用来做可见性判断的,也就是说当某个事务在执行快照读的时候,对该记录创建一个Read View的视图,把它当作条件去判断当前事务能够看到哪个版本的数据,有可能读取到的是最新的数据,也有可能读取的是当前行记录的undolog中某个版本的数据
Read View遵循的可见性算法主要是将要被修改的数据的最新记录中的DB_TRX_ID(当前事务id)取出来,与系统当前其他活跃事务的id去对比,如果DB_TRX_ID跟Read View的属性做了比较,不符合可见性,那么就通过DB_ROLL_PTR回滚指针去取出undolog中的DB_TRX_ID做比较,即遍历链表中的DB_TRX_ID,直到找到满足条件的DB_TRX_ID,这个DB_TRX_ID所在的旧记录就是当前事务能看到的最新老版本数据。
Read View的可见性规则如下所示:
首先要知道Read View中的三个全局属性:
trx_list:一个数值列表,用来维护Read View生成时刻系统正活跃的事务ID(1,2,3)
up_limit_id:记录trx_list列表中事务ID最小的ID(1)
low_limit_id:Read View生成时刻系统尚未分配的下一个事务ID,(4)
具体的比较规则如下:
1、首先比较DB_TRX_ID < up_limit_id,如果小于,则当前事务能看到DB_TRX_ID所在的记录,如果大于等于进入下一个判断。
2、接下来判断DB_TRX_ID >= low_limit_id,如果大于等于则代表DB_TRX_ID所在的记录在Read View生成后才出现的,那么对于当前事务肯定不可见,如果小于,则进入下一步判断。
3、判断DB_TRX_ID是否在活跃事务中,如果在,则代表在Read View生成时刻,这个事务还是活跃状态,还没有commit,修改的数据,当前事务也是看不到,如果不在,则说明这个事务在Read View生成之前就已经开始commit,那么修改的结果是能够看见的。
MVCC的整体处理流程
假设有四个事务同时在执行,如下图所示:
事务1 | 事务2 | 事务3 | 事务4 |
---|---|---|---|
事务开始 | 事务开始 | 事务开始 | 事务开始 |
… | … | … | 修改且已提交 |
进行中 | 快照读 | 进行中 | |
… | … | … |
从上述表格中,我们可以看到,当事务2对某行数据执行了快照读,数据库为该行数据生成一个Read View视图,可以看到事务1和事务3还在活跃状态,事务4在事务2快照读的前一刻提交了更新,所以,在Read View中记录了系统当前活跃事务1,3,维护在一个列表中。同时可以看到up_limit_id的值为1,而low_limit_id为5,如下图所示:
在上述的例子中,只有事务4修改过该行记录,并在事务2进行快照读前,就提交了事务,所以该行当前数据的undolog如下所示:
当事务2在快照读该行记录的是,会拿着该行记录的DB_TRX_ID去跟up_limit_id,lower_limit_id和活跃事务列表进行比较,判读事务2能看到该行记录的版本是哪个。
具体流程如下:先拿该行记录的事务ID(4)去跟Read View中的up_limit_id相比较,判断是否小于,通过对比发现不小于,所以不符合条件,继续判断4是否大于等于low_limit_id,通过比较发现也不大于,所以不符合条件,判断事务4是否处理trx_list列表中,发现不再次列表中,那么符合可见性条件,所以事务4修改后提交的最新结果对事务2 的快照是可见的,因此,事务2读取到的最新数据记录是事务4所提交的版本,而事务4提交的版本也是全局角度的最新版本。如下图所示:
当上述的内容都看明白了的话,那么大家就应该能够搞清楚这几个核心概念之间的关系了,下面我们讲一个不同的隔离级别下的快照读的不同。
RC、RR级别下的InnoDB快照读有什么不同
因为Read View生成时机的不同,从而造成RC、RR级别下快照读的结果的不同。
1、在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照即Read View,将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见。
2、在RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动和事务的快照,这些事务的修改对于当前事务都是不可见的,而早于Read View创建的事务所做的修改均是可见。
3、在RC级别下,事务中,每次快照读都会新生成一个快照和Read View,这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因。
总结:在RC隔离级别下,是每个快照读都会生成并获取最新的Read View,而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View,之后的快照读获取的都是同一个Read View.
页分裂、页合并
⾃增ID可以保证每次插⼊时B+索引是从右边扩展的,可以避免B+树和频繁合并和分裂(对⽐使⽤UUID)。如果使⽤字符串主键和随机主键,会使得数据随机插⼊,效率⽐较差。