由于正在准备之后的实习面试,故总结了一部分MYSQL innoDB基础的问题,回答全为自己组织的语言,若有错各位大佬可及时指出,大家共同进步,谢谢。
innoDB存储引擎主要支持B+Tree索引、哈希索引、全文索引,其中最常用最有效的则是利用B+Tree结构实现的B+Tree索引,而为何要采用B+Tree作为索引结构呢,则是因为在数据库存取性能评估上,磁盘I/O是目前最大瓶颈,而磁盘I/O速率属于硬件设计范畴。系统层面想要提高存取效率只能尽量减少磁盘I/O,所以由于磁盘特性,Tree可以将节点大小初始化为磁盘页大小,再利用磁盘的局部性原理进行预读,所以利用树结构可以很好的减少磁盘I/O。而为了想要进一步提高存取效率,BTree则在众多树中占主要地位,因为BTree层高始终自平衡维持在3-4,对于磁盘结构来说,层高几乎等同磁盘I/O次数。而B+Tree作为BTree的优化结构,将中间节点全用于存储键信息,增加了分支因子来保证层高足够低,自然也导致了磁盘I/O次数的减少。
mysql当前数据库中的B+Tree索引可以分为聚集索引和辅助索引。由于innoDB存储引擎表是索引组织表,表中数据都按照主键顺序存放,所以聚集索引就是依靠每张表的主键来构造B+Tree,叶节点中则存放整张表的行记录数据也就是数据页。因此,索引组织表中的数据也是索引结构的一部分。多数情况下,查询优化器倾向于利用聚集索引,能直接在叶节点上找到数据,且由于数据本身自带逻辑顺序,查询优化器可以执行范围查找。
而对于辅助索引,叶节点不包含行记录的全部数据。对应的叶节点只包含相应的键值对信息和书签(bookmark),书签用以找到与索引对应的行数据。由于表是索引组织表,所以辅助索引中的书签则是相应行数据的聚集索引键。
目前mysql中实现了四种隔离级别分别是读提交、读未提交、可重复读、串行化,默认使用的隔离级别是可重复读。
读提交作为最基本的隔离级别,主要提供两种保证:读时只能读到已提交的数据,写时只能覆盖已提交的数据。这时一个事务要等另一个事务提交后才能进行后续的读写操作以防止脏读脏写,这种隔离级别通常使用行锁和弱串行写实现,但出现的问题是不可重复读——同一事务中不同时刻所查询到的数据库状态不同。
而读未提交则是只实现了防止脏写不防止脏读。
可重复读也是快照级别隔离的实现,主要解决的问题是每个事务都只能看到该特点时间点的旧数据,确保了同一事务的多个实例就算在并发环境下读取数据时会看到相同数据从而避免不可重复读。快照级别隔离的实现是通过采用写锁防止脏写,使用MVCC多版本并发控制防止脏读幻读。基于MVCC机制可以对每一个不同的查询单独创建一个快照,其中赋予了一个唯一递增的事务ID来标识此快照。
最后是最高的事务隔离级别串行化,在该级别下事务按串行化顺序执行,可以避免所有问题包括基本的脏读脏写读倾斜,以及写倾斜幻读等棘手问题,但是造成的问题就是效率低下且性能依靠单个CPU核的吞吐量。目前MySQL中实现的串行化方式是两阶段加锁而非单线程执行,此时数据库中的每个对象都会持有一个读写锁来从根本是隔离读写,读取时以分享模式获得读锁,但是如果某一时刻存在某个事务获取了独占写锁,则所有持有读锁的事务必须等待。写入时以独占模式获取写锁,不允许其他任何事务获取任何锁。事务获取锁后需要持有锁直到事务结束才可以得以释放当前锁,也就是两阶段加锁的命名来源。
MVCC机制字面意思是多版本并发控制,与常规使用锁进行并发控制有所不同,加锁时会造成其他事务的阻塞从而影响数据库读写性能,而MVCC机制则是为了实现快照级别隔离而存在。每个不同的读对象都会单独创建一个状态快照,快照中有唯一标识的事务ID,读取时只会读到当前事务的可见版本无需加锁,事务写入时也会标记写入者的事务ID。
MVCC底层实现原理是使用了三个隐藏字段来实现多版本并发控制,包括RowID:自增ID,保证若没有主键时来以此创建索引、DB_TRX_ID:最近修改此记录的事务ID,也是刚提到可以唯一标识事务的ID、DB_ROLL_PTR:回滚指针,指向上一个版本。Flag:标识记录是否被删除。
Redolog作为重做日志主要用于实现事务的持久性,主要由两部分组成:内存上的重做日志缓冲、磁盘上的重做日志文件。实现原理主要是当事务更新时,将数据更新的日志写入内存的日志缓冲中,按照一定规则刷到磁盘,或者提交时,先将该事务的所有日志缓冲写入重做日志文件从而保证持久化到磁盘。
Binlog主要用于实现主从复制环境的建立,虽然与Redolog相同都是记录数据库操作的日志,但是redolog是在存储引擎层产生的,而binlog是在整个数据库上层产生,不局限于某个特定的存储引擎。binlog主要记录的是逻辑日志包含sql语句,而redolog则是记录磁盘页操作的物理日志。binlog写入时在事务提交时一次性将事务中的sql语句按编码格式编码后写到文件中,而redolog则会在事务执行过程中按要求多次写入到文件。
Undolog主要用于实现MVCC机制和回滚操作,底层并非物理日志而是逻辑日志,虽然回滚可以让数据库状态回到原来的样子,但底层磁盘的数据结构和页结构都不可逆转。另一个作用是实现MVCC,读取记录时,若该记录已被其他事务占用或修改,则提高undolog读取之前的版本信息,以此实现非锁定读取。值得一提的是,undolog的产生也会产生redolog,也会持久化到磁盘。
底层原理是master数据库将修改操作都写到binlog中,当slave连接到master时,slave开一个后台线程先将master的binlog文件拷贝到自己本地中继日志,然后再开一个线程来依次执行binlog中的sql语句,以此保证数据一致性。这跟redis中的主从同步机制也是相同的,只不过redis在主从同步中的持久化机制是RDB,初步同步后再进行命令传播。而mysql这样的同步机制也有一定缺陷,由于slave拿到binlog后需要串行化的执行相应的sql语句,所以在高并发情况下,slave同步时间较长容易造成长时间数据不一致,所以mysql也推出了两个相应的机制:半同步复制、并行复制。
半同步复制是为了解决数据不一致的空窗,只有master收到来自slave同步完成并且发出的消息后,才可认定当前记录的成功写入,否则查询不到相应记录。
并发复制是slave开启多个线程并发读取中继日志中不同库的日志然后并发执行,这样也可缓解延迟问题。
innodb的关键特性主要包括:插入缓冲、两次写、自适应哈希索引、异步IO、刷新邻接页。
插入缓冲的作用主要是提高非聚集索引插入的性能,对于非聚集索引的插入或更新,由于不像聚集索引一样有顺序性,所以不会每一次都直接插入到索引页中,而是先判断插入的非聚集索引所在的数据页是否在当前缓冲池中,若在就直接插入;若不在,先放到插入缓冲中(insert buffer),再以一定的频率合并在一个数据页中的索引操作,这样就能将多个插入操作减少为一个,从而提高非聚集索引的插入性能。目前innodb的版本已经支持包括delete buffer、purge buffer,对应标记缓冲和删除缓冲。
两次写的作用主要是保证数据页的可靠性,当发生写失效时,底层物理数据页已经发生更改,通过重做日志无法恢复。所以在应用重做日志之前需要一个数据页副本来保证损坏数据页的还原,所以两次写机制在内存设置了缓冲区并且同时写到数据页和共享表空间中的两次写区域。如果数据文件发生损坏则将共享表空间中的数据复原到数据文件。
自适应哈希索引主要是会监控表上各索引页的查询,如果对当前索引页建立哈希索引会带来速度提升则建立。底层实现原理是通过缓冲池中的数据页的访问频率和模式来构造哈希,无需构造磁盘中的数据页所以建立速度很快。
异步IO的作用是提高磁盘操作性能,可以通过同时发起多次的IO操作来进行IO合并,请求发送完成后等待所有IO操作的完成。
刷新邻接页的作用是减少IO次数,当刷新脏页时检测所在区的所有页是否存在需要刷新的其他脏页,如果需要,则一起进行刷新,也是可以将多个IO操作合并的一种异步IO操作。但是对于固态硬盘来说则无需开启此特性。
MySQL保持一致持久性的原理是使用WAL机制,写磁盘之前先写日志,应用到MySQL中就是redolog的实现。数据库挂掉后,重启可以通过redolog还原数据,每次事务提交只需同步redolog。
innoDB中的行锁模式主要包括共享锁和排他锁,也就是读锁和写锁。
共享锁允许多个事务在当前行未被获取排他锁的情况下同时获取,也就是在没有写锁的情况下可以同时获取读锁。而排他锁属于独占,获取后不允许其他事务获取共享锁和排他锁,直到当前事务释放该排他锁,这也是innodb中实现可串行化隔离的两阶段加锁原理。