CPU

为了支持更多的内存支持,CPU必须是64位来支持64位操作系统。

OLTP

  • 用户操作并发量大
  • 事务处理的时间比较短
  • 查询语句简单,走索引
  • 复杂查询少

OLTP对CPU要求不是很高,但是并发量高,是IO密集型操作。

OLAP

  • 用户操作并发量小
  • 一般只用于查询
  • 查询语句复杂,需要优化分析,比较、排序、连接等耗费CPU
  • 复杂查询多,耗费时间长

OLAP对CPU要求较高,是CPU密集型操作。

从InnoDB存储引擎的设计架构上来看,主要后台操作都是在一个单独的master thread中完成的,因此并不能更好的支持多核应用。InnoDB1.2支持多个purge线程,以及将刷新操作从master thread中分离出来。虽然一条SQL查询语句只能在一个CPU总工作,但是可以有效的提升大并发量的请求处理。

可以通过修改以下两个参数来增加IO线程。
innodb_read_io_threads
innodb_write_io_threads

内存

内存的大小直接反映了数据库的性能(InnoDB缓存数据、索引都缓存在内存中时性能最优。)
通常InnoDB存储引擎的缓冲池的命中率不应该小雨99%。

1
show global status like 'innodb%read%';

innodb_buffer_pool_reads: 表示从物理磁盘读取页的次数;
innodb_buffer_pool_read_ahead: 表示预读的次数;
innodb_buffer_pool_read_ahead_evicted: 预读的页,但是没有被读取就从缓冲池中被替换的页的数量,一般用来判断预读的效率;
innodb_buffer_pool_read_requests: 从缓冲池中读取页的次数;
innodb_data_read: 总共读入的字节数;
innodb_data_reads: 总共读取请求的次数,每次读取可能需要读取多个页;

缓冲池命中率 = (innodb_buffer_pool_read_requests) / (innodb_buffer_pool_read_requests + innodb_buffer_pool_read_ahead + innodb_buffer_pool_reads);

平均每次读取字节数 = innodb_data_read / innodb_data_reads;

当缓冲池的大小已经大于数据库文件的大小时,磁盘操作依旧会进行。
数据库的缓冲池只是用来存放热点的区域, 后台线程还负责将脏页异步写入到磁盘。
每次事务提交还要将日志写入重做日志文件。

磁盘

机械硬盘

机械硬盘的访问需要耗费长时间的磁头旋转和定位来查找,因此顺序访问的速度远高于随机访问。
同时用多块磁盘组成RAID来提高数据库的性能,也将数据分布在不同的硬盘上来达到负载均衡。

固态硬盘

基于闪存的固态硬盘,内部由山村组成。企业级应用一般使用固态硬盘,通过并联多块闪存来进一步提高数据传输的吞吐量。
固态硬盘没有传统的机械磁盘的读写磁头,不需要耗费大量的时间用于磁盘旋转和定位来查找数据,所以固态硬盘可以提供一致的随机访问时间。

闪存中的数据时不可以更新的,智能通过扇区的覆盖重写。在覆盖重写之前需要执行非常耗时的擦除工作。擦除操作不能再所含数据的扇区上完成,而需要在删除整个被称为擦出块的基础上完成,这个擦除块的尺寸大于山区大小,通常是128KB或者256KB。每个擦除块有擦写次数的限制。

因为有擦除写入的问题,因此需要考虑如何利用齐读取性能,避免过多次的写入操作。

增加 innodb_io_capacity 变量的值达到充分利用固态硬盘带来的高IOPS特性。
关闭临接页的刷新,可以可以带来一定效果的提升。

RAID

RAID(Redundant Array of Independent Disks,独立磁盘冗余数组)基本思想是吧多个相对便宜的硬盘组合起来,成为一个磁盘数组,使得性能达到甚至超过一个价格昂贵、容量巨大的硬盘。由于将多个硬盘组合成一个逻辑山区,RAID看起来就像一个单独的硬盘或者逻辑存储单元,因此操作系统会把它当作一个硬盘。

RAID的作用:

  • 增强数据集成度
  • 增强容错功能
  • 增加处理量或者容量
RAID0

将多个磁盘并列起来合并成一个大磁盘,存放数据时将数据按照磁盘个数分段,同时将这些数据写入磁盘,不会有冗余,并行IO速度最快,但是没有冗余如果一个磁盘损坏,则所有数据都会丢失。

RAID1

两组以上的N个磁盘相互作为镜像,在一些多线程操作系统中能有很好的读取速度,但是写入速度略有降低。
除非拥有相同数据的主磁盘与镜像同时损坏,否则只要一个磁盘正常即可用维持运作,可靠性最高,磁盘利用率最低。

RAID5

一种存储性能、数据安全和存储成本兼顾的解决方案。是一种硬盘分区技术,RAID5至少需要三个硬盘,RAID5部队存储的数据进行备份,而是把数据和相对应的奇偶校验信息存储到组成?RAID5的各个磁盘上,并且奇偶校验信息和相对应的数据分别存储与不同的磁盘上。

RAID10

RAID1和RAID0的组合体

RAID50

RAID5和RAID0的组合体

操作系统

Linux
FreeBSD

Windows操作系统下,表名不区分大小写;Linux操作系统大小写敏感。

测试工具

sysbench

模块化的,跨平台的多线程基准测试工具,用于测试各种不同系统参数下的数据库负载情况。

  • CPU性能
  • 磁盘IO性能
  • 调度程序性能
  • 内存分配以及传输速度
  • POSIX线程性能
  • 数据库OLTP基准测试

tpcc-mysql

开源的TPC-C测试工具,遵循tpcc标准。

事务

事务会把数据库从一种一致状态转换为另一种一致状态。在数据库提交工作时,可以确保要么所有修改都已经保存,要么所有修改都不保存。

特性

InnoDB存储引擎中的事务完全符合ACID的特性(A:Atomic,C:Consistency,I:Isolation,D:Durability)。

原子性

数据库事务是不可分割的工作单位。只有使事务中所有的数据库操作都执行成功,才算整个事务成功。事务中任何一个SQL执行失败,已经执行成功的SQL也必须撤销,数据库状态应该退回到执行事务前的状态。

一致性

事务将数据库从一种状态变更为另一种一致的状态。在事务开始前和事务结束后,数据库的完整性约束没有被破坏。事务是一致性的单位,如果事务中某个动作失败了,系统可以自动撤销事务,返回初始化的状态。

隔离性

每个读写事务的对象对其他事务的操作对象能相互分离,该事务提交前对其他事务都不可见,通常使用锁来实现。

数据库系统中都提供了一种粒度锁的策略,允许事务仅锁住一个实体对象的子集,来提高事务之间的并发性。

持久性

事务一旦提交,其结果就是永久性的。即使当数据库崩溃需要恢复时,也能保证恢复后提交的数据都不会丢失

分类

扁平事务

事务类型中最简单,生产环境中使用最为频繁的一种事务类型。
在扁平事务中,所有操作都处于同一层次,其由Begin Work开始,有Commit Work或者Rollback work结束,其间操作是原子的,要么都执行,要么都回滚。
扁平事务是应用程序成为原子操作的基本组成模块。

扁平事务的三种情况

缺点:
不能提交或者回滚事务的某一部分,或者分几个步骤提交。

带有保存点的扁平事务

除了支持扁平事务支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态。

这是因为某些事务可能在执行过程中出现的错误并不会导致所有的操作都无效,放弃整个事务不合要求,开销也太大。保存点用来通知系统应该记住事务的当前状态,以便当之后发生错误时,事务能回到保存点当时的状态。

缺点:
当系统发生崩溃时,所有的保存点都将消失。因为其保存点事易失的,而非持久的。
这意味着当进行恢复时,事务需要从开始处重新执行,而不能从最近的一个保存点继续执行。

链事务

保存点模式的一个变种。在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。其中,提交事务操作和开始下一个事务操作将合并为一个原子操作。下一个事务将看到上一个事务的结果,就好像在一个事务中进行的一样。

链式事务与带有保存点的扁平事务不同的是,带有保存点的扁平事务能回滚到任意正确的保存点。
链式事务中的回滚仅限于当前事务,即智能恢复到最近一个的保存点。
对于锁的处理,两者也不相同。
链式事务在执行commit后就释放了当前事务所持有的锁,而带有保存点的扁平事务不影响迄今为止所持有的锁。

链事务

嵌套事务

一个层次结构框架,由一个顶层事务控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务,其控制每一个局部的变换。

嵌套事务

1)嵌套事务是由若干事务组成的一棵树,子树既可以是嵌套事务,也可以是扁平事务
2)处在叶子结点的事务是扁平事务。但是每个子事务从根到叶子结点的距离是可以不同的。
3)位于根结点的事务称为顶层事务,其他事务称为子事务。事务的前驱称为父事务,事务的下一层称为子事务。
4)子事务既可以提交也可以回滚,但是提交操作并不马上生效,除非其父事务应提交。任何子事务都在顶层事务提交后才真正的提交。
5) 树中任意一个事务的回滚回引起它所有子事务一同回滚,子事务仅保留ACI的特性,不具有D的特性。

在Moss理论中,所有的工作都是由叶子结点来完成的,只有叶子结点的事务才能访问数据库、发送消息、获取其他类型的资源。而高层的事务仅负责逻辑控制,解决何时调用相关的子事务。

即使一个系统不支持嵌套事务,用户也可以通过保存点技术来模拟嵌套事务。

分布式事务

在分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同结点。

InnoDB存储引擎支持扁平事务、带有保存结点的扁平事务、链事务、分布式事务。并不原生支持嵌套事务。

实现

事务的隔离性由锁来实现。
原子性、一致性、持久性通过数据库的redo log和undo log来完成。
redolog 用来保证事务的原子性和持久性。
undolog 用来保证事务的一致性。

redo

Redo 用来实现事务的持久性,是InnoDB存储引擎用来记录对于每个页的修改,它在事务的进行中不断的被写入。
由于是物理操作日志,每个事务对应多个日志条目。并且事务的重做日志是并发写入的,因此在文件中记录的顺序并不是事务的顺序。

其由两部分组成:
1)易失的内存中的重做日志缓冲 redo log buffer
2)持久的重做日志文件 redo log file

InnoDB存储引擎通过Force Log At Commit机制实现事务的持久性,事务提交时必须先将该事务的所有日志写入到重做日志文件进行持久化,待事务的commit操作完成才完成。
redo log都是顺序写的,在数据库运行时不需要对redo log的文件进行读取操作。

为了确保每次日志都写入redo log file,在每次重做日志缓冲写入重做日志文件后,InnoDB存储引擎都需要调用一次fsync操作,由于重做日志文件打开并没有使用O_DIRECT选项,因此重做日志缓冲先写入文件系统缓存。为了确保重做日志写入磁盘,必须进行一次fsync操作。由于fsync的效率取决于磁盘的性能,因此磁盘的性能决定了事务提交(数据库)的性能。

InnoDB存储引擎允许用户手工设置非持久的情况发生来提高数据库的性能。当事务提交时,日志不写入重做文件,等待一个时间周期后再执行fsync操作。但是当数据库发生宕机时,由于部分日志未刷新到磁盘,因此回丢失最后一段时间的事务。

innodb_flush_log_at_trx_commit用来控制重做日志刷新到磁盘的策略。
默认值为1,表示事务提交时必须调用一次fsync操作。
值为0:表示事务提交时不进行写入重做日志操作,这个操作仅在master thread中完成,master thread每秒都会进行一次重做日志文件的fsync操作。
值为2:表示事务提交时将重做日志写入重做日志文件,但仅写入文件系统的缓存中,不进行fsync操作。
当Mysql宕机时并不会发生丢失,但是操作系统宕机时,重起数据库后回丢失未从文件系统缓存刷新到重做日志文件的那部分事务。

日志块

在InnoDB存储引擎中,重做日志缓存、重做日志文件都是按照512字节大小的块来进行保存的。
如果一个页中产生的重做日志数量大于512字节,那么需要分割为多个重做日志块进行存储。
此外由于重做日志块的大小和磁盘扇区大小一样都是512个字节,因此重做日志的写入可以保证原子性,不需要doublewrite技术。

重做日志块结构

日志块头

共占用12个字节,

名称 占用字节数 说明
LOG_BLOCK_HDR_NO 4 log buffer 由log block组成,在内部log buffer就像一个数组,log_block_hdr_no来标记这个数据中的位置,它是递增并且循环使用的,占用四个字节。第一位用来判断是否是flush bit,最大的值为2G。
LOG_BLOCK_HDR_DATA_LEN 2 表示log block锁占用的大小。当log block被写满时,该值为0x200,表示使用全部log block空间,也就是占用512字节。
LOG_BLOCK_FIRST_REC_GROUP 2 占用两个字节,表示logblock中第一个日志所在的偏移量,如果该值的大小和log_block_hdr_len相同,表示当前logblock 不包含新的日志。
LOG_BLOCK_CHECKPOINT_NO 4 表示logblock 最后被写入时的检查点的值。
日志内容

日志块内容可以占用512 - 12 - 8 = 492个字节。

日志块尾

共占用8个字节,共有一部分组成,他的值和log_block_hdr_no相同,并在log_block_init中被初始化。

重做日志组

重做日志组中有多个重做日志文件。InnoDB存储引擎只有一个log group。它是一个逻辑上的概念,并没有实际存储的物理文件来表示log group信息。log group是由多个重做日志文件组成的,每个log group中的日志文件大小是相同的。

重做日志文件中存储的是log buffer中保存的log block,因此它也是根据快的方式进行物理存储的管理,每个块的大小与log block一样都是512个字节。在InnoDB存储引擎中,log buffer根据一定的规则将内存中的 log block刷新到磁盘。

  • 事务提交时
  • log buffer中有一般的内存空间已经被使用时
  • log checkpoint时

对于log block的写入追加在redo log file 的最后部分,当一个redo log file 被写满时,回接着写入下一个redo log file, 其使用方式是round-robin.

对于log group中的第一个redo log file, 其前2KB的部分保存4个512字节大小的块,存放的内容是:

名称 大小
log file header 512
checkpoint1 512
512
checkpoint2 512

对于log group的其他 redo log file ,仅保留这些空间,但不保存这些信息。

redo log file的写入并不是完全顺序的,因为除了log block的写入操作,还需要更新前2KB部分的信息,这些信息对于InnoDB存储引擎的恢复非常重要。

log group & redo log file

在log file header 后面的部分为InnoDB存储引擎保存的check point值,其设计是交替写入,这样的设计避免了因介质失败而导致无法找到可用的checkpoint情况。

重做日志格式

不同的数据库操作会有丢赢的重做日志格式,由于InnoDB存储引擎的存储管理是基于页的,因此重做日志格式也是基于页的。

通用的头部格式
redo_log_type space page_no redo log body
重做日志类型 表空间ID 页偏移量 日志不同,存储内容不同
LSN

Log Sequence Number代表日志序列号,在INnoDB存储引擎中,LSN占用8个字节,并且单调递增。它的含义是:

  • 重做日志写入总量,表示事务写入重做日志的总量,单位是字节。
  • checkpoint 的位置
  • 页的版本,在每个页头部的fil_page_lsn记录了该页的LSN值,表示LSN最后刷新时的大小。用来判断该页是否需要进行恢复操作。如,页P1的LSN为10000, 而数据库启动时,InnoDB检测到写入重做日志中的LSN为13000,并且该事务已经提交,那么数据库需要进行恢复操作,将重做日志应用到P1中。对于重做日志中LSN小于P1页的LSN,不需要进行重做,表示已经被刷新到该位置。

恢复

InnoDB存储引擎在启动时不管上次数据哭运行时是否正常关闭,都会尝试进行恢复操作,因为重做日志记录的是物理日志,因此恢复的速度比逻辑日志要快很多。同时InnoDB存储引擎自身对恢复做了优化,如顺序读取和并行应用重做日志,这样可以进一步提高数据库恢复的速度。

由于checkpoint表示应刷新到磁盘页上的LSN,因此恢复过程中仅需要恢复检查点开始的日志部分即可。

1
2
create table t(a int, b int, primary key(a), key(b));
insert into t select 1,2;

对于INSERT操作,其记录的是每个页上的变化。由于需要对聚集索引页和辅助索引页进行操作,其记录的重做日志大致为:
Page(2,3), offset 32, value 1,2#聚集索引
Page(2,4), offset 64, value 2 #辅助索引

如果插入操作涉及到B+树的分裂,则更多的页需要记录日志。此外,重做日志是物理日志,因此其是幂等的。

undo

在数据库进行修改时,为了防止突然宕机导致最近提交的事务丢失,需要redo log进行恢复。
如果需要对当前事务进行回滚,需要undo log来支持。

redo存放在重做日志中, undo 存放在数据库内部的一个特殊段中,这个段称为undo 段(undo segment)。undo 段位于共享表空间内。

undo 是逻辑日志,只是将数据库逻辑地恢复到原来的样子。例如:
执行一个INSERT 10万条记录的事务,这个事务会导致分配一个新的段(导致表空间增大)。
在执行RollBack时,会将插入的事务进行回滚,但是表空间的大小并不会因此收缩。当InnoDB存储引擎回滚时,它实际上做的事与先前相反的工作。对于每个INSERT,InnoDB存储引擎会完成一个DELETE;对于每个DELETE,InnoDB存储引擎会完成一个INSERT;对于每个UPDATE,InnoDB存储引擎会执行一个相反的UPDATE。

除了回滚操作,undo的另一个作用是mvcc(多版本并发控制),在InnoDB存储引擎中mvcc的实现是通过undo来完成的。当用户读取一行记录时,如果该记录已经被其他事务占用,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读。

undo log也需要持久性的保护,所以undo log 也会产生redo log。

undo存储管理

InnoDB存储引擎对undo的管理采用段的方式。
InnoDB存储和引擎有rollback segment,每个回滚段中记录了1024 个undo log segment,在每个undo log segment段中进行undo 页的申请。从1.1开始InnoDB支持最大128个rollback segment,故其支持同时在线的事务限制为128 * 1024个。这些所有的rollback segment都存储在共享表空间中。

  • innodb_undo_directory: 用于设置rollback segment 文件所在的路径,默认为“.”表示当前InnoDB存储引擎的目录。可以设置为共享表空间以外的位置。
  • innodb_undo_logs:设置rollback segment的个数,默认值为128.
  • innodb_undo_tablespaces:设置构成rollback segment文件的数量,这样rollback segment可以较为平均地分布在多个文件中。该参数设置后,可以在innodb_undo_directory看到undo前缀的文件,该文件代表rollback segment文件。
    undo log segment分配页写入undo log 的这个过程同样需要写入redo log日志。
    当事务提交时,InnoDB存储引擎做两件事情:
    1)将undo log放入列表中,供以后的purge使用。
    2)判断undo log所在的页是否可以重用,如果可以则分配给下一个事务使用。

事务提交以后并不能马上删除undo log以及undo log所在的页。这是因为可能还有其他事务需要通过undo log来得到行记录之前的版本。因此事务提交时将undo log放入一个链表中,然后判断undo页的使用空间是否小于3/4,如果小于3/4则表示该undo页可以被重用,之后新的undo log记录在当前undo log后面。由于存放undo log 的里诶包是以记录进行组织的,而undo 页可能存放着不同事务的undo log,是否可以最终删除undo log以及undolog所在的页由purge线程来判断。purge操作需要涉及磁盘的离散读取操作,是一个比较缓慢的过程。

undo log 格式

在InnoDB存储引擎中,undo log分为: insert undo log / update undo log.

Insert undo log 是指在Insert 操作中产生的undo log,因为Insert操作的记录只对事务本身可见,对其他事务部可见,因此该undo log 可以在事务提交后直接删除,不需要等待purge操作。

*:表示对存储的字段进行压缩。

next: 记录下一个undo log 的位置,通过该值的字节可以知道一个undo log所占用的空间字节数。
start:占用2个字节,记录的是undo log 的开始位置。
type_cmp1:占用1个字节,记录的是undo 的类型,对于insert undo log,值总是11。
undo_no:记录事务的ID
table_id:记录undo log所对应的表对象。
接下来记录所有主键的列和值。
在进行rollBack操作室,根据这些纸可以定位到具体的记录,然后进行删除。

update undo log 记录的是对delete和update操作产生的undo log。该undo log 可以能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交时放入undolog 链表中,等待purge线程进行最后的删除操作。

update undo log相对于之前介绍的 insert undo log, 记录的内容更多,所需要占用的空间也更大。

type_cmpl: 12 TRX_UNDO_UPD_EXIST_REC更新non-delete-mark的记录。
13 TRX_UNDO_UPD_DEL_REC将delete的记录标记为not delete
14 TRX_UNDO_DEL_MARK_REC将记录标记为delete

update_vector表示update操作导致发生改变的列。
每个修改的列信息都要记录到undo log中。对于不同的undo log类型,可能还需要记录对索引列所做的修改。

查看undo信息

查看rollback segment

1
DESC INNODB_TRX_ROLLBACK_SEGMENT

查看rollback segment所在的页

1
select segment_id, space, page_no from innodb_trx_rollback_segment;

查看事务所对应的undolog

1
select * from information_schema.INNODB_TRX_UNDO;

通过segment_id 查看当前rollback segment信息

1
select segment_id, insert_undo_list, insert_undo_cached from information_schema.innodb_trx_rollback_segment where segment_id = 2;

delete操作并不直接删除记录,而只是将记录的delete flag标记为1表示已删除,而记录最终的删除是在purge操作中完成的。

update 主键的操作分为两部完成,首先将原主键记录标记为已删除,因此需要产生一个类型为TRX_UNDO_DEL_MARK_REC的undo log, 之后插入一条新的记录,因此需要产生一个类型为TRX_UNDO_INSERT_RED的undo log。

purge

delete和update的操作可能并不直接删除原有的数据。

1
delete from t where a = 1;

表t上列a有聚集索引,列b上有辅助索引。对于上述的delete操作仅仅是将主键列等于1的记录delete flag设置为1, 记录并没有被真正的删除还存在于B+树中。

其次对于辅助索引上a=1,b=1的记录同样没有做任何处理,甚至都没有产生undo log。真正删除这行记录的操作被放到了purge操作中。
是因为InnoDB存储引擎支持MVCC,所以记录不能在事务提交时立即进行处理。这时其他事务可能正在引用这行,因此InnoDB 存储引擎需要保存记录之前的版本。是否可以删除需要通过purge来判断。如果这行已经不被任何其他事务引用,那么就可以进行真正的delete操作。

一个页上有多个undo log存在,虽然不代表事务在全局过程中的提交顺序,但是后面的事务产生的undo log总在最后。此外InnoDB存储引擎还有一个history列表,它根据事务提交的顺序,将undo log进行链接。在InnoDB存储引擎的设计中,先提交的事务总在尾端。undo page存放了undo log,由于可以重用,因此一个undo page中可能存放了多个不同事务的undo log。 trx5的灰色阴影表示该undo log还没有被其他事务引用。

Undo Log History

在执行purge的过程中,InnoDB存储引擎先从history list中找到第一个需要被清理的记录,trx1, 清理之后InnhoDB存储引擎会在trx1的undo log所在的页中继续寻找是否存在可以被清理的记录。会继续找到trx3, trx5但是发现trx5倍其他事务所引用而不能清理,因此再去history list中国呢查找,发现最尾端的记录trx2,找到trx2所在的页,然后依次吧事务trx6,trx4的记录进行清理。 由于undo page2中的所有页都被清理,因此undo page2可以被重用。

InnoDB存储引擎这种先从historylist中照undo log,然后再从undo page中查找undo log的设计避免了大量的随机读取操作,从而提高了purge的效率。

全局动态参数innodb_purge_batch_size用来设置每次purge操作需要清理的undopage数量。值设置的越大,每次回收的undo page就越多,可供重用的undo page就越多,减少了磁盘存储空间与分配的开销。
但是当之设置的太大,每次需要purge处理更多的undo page,从而导致cpu和磁盘IO过于集中与对undo log的处理,使得性能下降。

当InnoDB的存储引擎压力非常大时,并不能高效地进行purge操作。那么history list的长度会变得越来越长。

全局动态参数innodb_max_purge_lag控制history list的长度,
0: 默认值,不对history list 做任何限制。
大于0时,延缓每一行的DML操作。

全局动态参数innodb_max_purge_lag_delay: 用来控制delay的最大号描述。将innodb_max_purge_lag计算出的值大于当前参数值是,将delay设置为innodb_max_purge_lag_delay, 避免由于purge操作缓慢导致其他SQL线程出现无限制的等待。

group commit

如果事务为非只读事务,每次事务提交时需要进行一次fsync操作,以此保证重做日志都已经写入磁盘。当数据库发生宕机时,可以通过重做日志进行恢复。为了提高fsync的效率,数据库都提供了groupcommit功能,一次fsync可以刷新确保多个事务日志被写入文件。对于InnoDB存储引擎来说,事务提交时会进行两个阶段的操作:

1)修改内存中事务对应的信息,并且将日志写入重做日志缓冲。
2)调用fsync将确保日志都从重做日志缓冲写入磁盘。(将多个事务的重做日志通过一次fsync刷新到磁盘,这样大大的减轻了磁盘的压力,从而提高了数据库的整体性能。对于写入或者更新较为频繁的操作,group commit的效果尤为明显)

在InnoDB1.2版本之前,在开启二进制日志后,InnoDB存储引擎的group Commit都会失效,从而导致性能的下降。在线环境多使用replication环境,因此二进制日志的选项基本都为开启状态,因此该问题尤为显著。导致这个问题的原因是在开启二进制日志后, 为了保证存储引擎层中的事务和二进制日志的一致性,二者之间使用了两阶段事务,其步骤如下。
1)当事务提交时InnoDB存储引擎进行prepare操作。
2)MySQL数据库上层写入二进制日志(一旦该步骤完成,即使后续发生宕机,也能保证事务的提交)
3)InnoDB存储引擎层将日志写入重做日志文件
a) 修改内存中事务对应的信息,并且将日志写入重做日志缓冲。
b) 调用fsync将确保日志都从重做日志缓冲写入磁盘。
每个步骤都需要进行一次fsync才能保证上下两层的数据一致性。
步骤2的fsync由参数sync_binlog控制, 步骤3的fsync由陈安叔innodb_flush_log_at_trx_commit控制。

为了保证MYSQL数据库上层二进制日志的写入顺序和InnoDB层的事务提交顺序一致,MySQL数据库内部使用了prepare_commit_mutex串行锁。启用锁后
3.a步骤不可以在其他事务执行步骤3.b时进行,从而导致了group commit失效。

在Mysql5.6之后,采用了Binary Log Group Commit的方式,不但上层写入的二进制日志时group commit的, InnoDB存储引擎也是group commit的,并且移除了prepare_commit_mutex.

BLGC

在Mysql数据库上层进行提交时首先按照顺序将其放入一个队列中,队列中的第一个事务称为leader, 其他事务称为follower, leader控制这follower的行为。 BLGC分为三个阶段:

  • Flush 阶段,将每个事务的二进制日志写入内存中。
  • Sync 阶段,将内存中的二进制日志刷新到磁盘,如果队列中有多个事务,那么仅一次fsync操作就完成了二进制日志的写入。
  • Commit 阶段,leader 根据顺序调用存储引擎层事务的提交,InnoDB存储引擎本身就支持groupcommit, 因此修复了原先由于锁prepare_commit_mutex导致的group commit 失效的问题。

当有一组事务进行Commit操作时,其他新事务可以进行Flush阶段的操作,从而使GroupCommit不断生效。binlog_max_flush_queue_time用来控制Flush阶段中等待的时间,即使之前的额一组事务完成提交,当前一组的事务也不会马上进入Sync阶段,而是进行等待。这样做的好处是groupCommit的数量更多。但是会导致事务的响应时间变慢。

事务控制语句

  • START TRANSACTION | BEGIN : 开启事务;
  • COMMIT:提交事务;
  • ROLLBACK:回滚事务;
  • SAVEPOINT identifier: savepoint 允许在事务中创建一个保存点,一个事务中可以有多个savepoint;
  • RELEASE SAVEPOINT identifier:删除一个事务的保存点,当钱保存点不存在时抛出异常;
  • ROLLBACK TO 【savepoint】identifier:与savepoint一起使用,可以把事务回滚到标记点,而不回滚在标记点之前的任何工作。(后续还需commit或者rollback才会真正的完成事务)
  • SET TRANSACTION:设置事务的隔离级别。

隐式提交的SQL语句

执行完这些操作后,都会产生一个隐式的提交操作。
alter database。。。 upgrade data directory name;
alter event ;
alter procedure;
alter table;
alter view;
create database;
create event;
create index;
create procedure;
create table;
create trigger;
create view;
drop database;
drop event;
drop index;
drop procedure;
drop table;
drop trigger;
drop view;
rename table;
truncate table;

用来隐式地修改MySQL架构的操作:
create user;
drop user;
grant;
rename user;
revoke;
set password;

管理语句:
analyze table;
cache index;
check table;
load index into cache;
optimize table;
repair table;

事务的隔离级别

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • SERIALIZABLE
1
2
3
SET 【GLOBAL|SESSION】 TRASACTION | SOLATION LEVEL {
READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIABLE
}

[mysqld]

1
transaction-isolation=READ-COMMITTED
1
2
select @@tx_isolation;//查看当前会话的事务隔离级别
select @@global.tx_isolation; //查看全局事务隔离级别

InnoDB默认隔离级别为 REPEATABLE READ,使用Next-Key Lock 锁算法, 避免了幻读的产生,因此在改隔离级别下久已经能保证事务隔离性的要求,达到标准SQL的SERIALIZABLE的隔离级别。
隔离级别越低,事务请求的锁越少或者保持锁的时间就越短。

在SERIALIABLE隔离级别, INNODB会对每个SELECT语句后自动加上 lock in share mode的共享锁。 因此在这个隔离级别下,读占用了锁, 对一致性的非锁定读就不再予以支持。
Serialiable隔离级别一般用在分布式事务上。

##分布式事务

InnoDB存储引擎提供了对XA事务的支持,并通过XA事务来支持分布式事务的实现。分布式事务是多个独立的事务资源参与到一个全局的事务中。
全局事务要求在其中的所有参与的事务要么都提交,要么都回滚。在使用分布式事务时,InnoDB存储引擎的事务隔离级别必须设置为SERIALIZABLE。

XA事务由一个或者多个资源管理器、一个事务管理器以及一个应用程序组成。

  • 资源管理器:提供访问事务资源的方法。通常一个数据库就是一个资源管理器。
  • 事务管理器:协调参与全局事务中的各个事务,需要和参与全局事务的所有资源管理器进行通信。
  • 应用程序:定义事务的边界,指定全局事务中的操作。

不好的事务习惯

  • 在循环中提交事务(每次提交都会写一次 redo log,导致性能变慢)
  • 使用自动提交
  • 使用自动回滚

    长事务

InnoDB存储引擎锁的实现提供了一致性的非锁定读、行级锁支持。行级锁没有相关的额外开销,并可以同时得到并发性和一致性。

LOCK与LATCH

Latch

轻量级的锁,因为其要求锁定的时间必须非常短,如果持续的时间长,则应用的性能会非常差。InnoDB存储引擎中,latch又可以分为mutex(互斥量)和rwlock(读写锁),目的就是用来保证并发线程操作临界资源的正确性,通常没有死锁检测机制。

LOCK

Lock对象是事务,用来锁定数据库中的对象,如表、页、行。并且一般lock的对象仅在事务commit或者rollback后进行释放(不同的隔离级别释放时间不同)。lock有死锁机制。

lock & latch

Latch 信息查看

1
show engine innodb mutex;
名称 说明
count mutext被请求的次数
spin_waits spin lock 的次数,InnoDB存储引擎latch在不能获得锁时首先进行自旋,若自旋后还不能获得,则进入等待
spin_rounds 自旋内部循环的总次数,每次自旋的内部循环是一个随机数。spin_rounds/spin_waits 表示平均每次自旋所需要的内部循环次数
os_waits 操作系统等待的次数,当spinlock通过自旋还不能获得latch时,则会进入操作系统等待状态,等待被唤醒
os_yields 进行os_thread_yield唤醒操作的次数
os_wait_times 操作系统等待的时间,单位ms

Lock信息查看

1
2
show engine innodb status;
information_schema 下 innodb_trx、innodb_locks、innodb_lock_waits

InnoDB存储引擎中的锁

锁类型

  • 共享锁 S Lock, 允许事务读一行数据
  • 排他锁 X Lock, 允许事务删除或者更新一行数据。

InnoDB存储引擎支持多力度锁定,允许事务在行级上的锁和表级上的锁同时存在。为了支持在不同粒度上进行加锁操作,Mysql支持意向锁。

一致性非锁定读

一致性非锁定读是InnoDB存储引擎通过行多版本控制的方式来读取当前执行时间数据库中行的数据。
如果读取的行正在执行DELETE或者UPDATE操作,这时读取操作不会u因此去等待行上锁的释放,InnoDB存储引擎会读取行的一个快照数据。快照数据是改行之前的版本数据,通过undo段来完成,undo用来在事务中回滚数据,一次快照数据本身没有额外开销。读取快照数据本身没有额外开销,也不需要上锁。

一个行记录可能有多个快照数据,由此来来的并发控制称为多版本并发控制。
在事务隔离级别为READCOMMITED和REPEATABLEREAD下, InnoDB存储引擎使用非锁定一致性读。但是对于快照的定义不同。

在READ COMMITTED事务隔离级别下,对于快照数据,非一致性读总是读取被锁定行的最新一份快照数据。
在REPEATABLE READ事务隔离级别下,对于快照数据,非一致性读总是读取事务开始时的行数据版本。

一致性锁定读

1
2
select ... for update
select ... lock in share mode

自增长与锁

InnoDB自增长ID的锁机制称为Auto-Inc Locking, 为了提高插入的性能,在完成对自增长值插入的SQL语句后立即释放,而不需要等到事务提交。必须等待前一个插入完成,并且对Inser。。Select的大户巨量的插入会影响插入性能,因为另一个事务中的插入会被阻塞。

轻量级互斥量的自增长实现机制,大大提高了自增长值插入的性能。通过innodb_autoinc_lock_mode来控制自增长的模式。默认值为1.

  • insert-like : 所有的插入语句,insert/replace/insert-select, replace-select, load data.
  • simple-inserts: 插入之前就确定插入行数的语句,如insert/replace。
  • bulk inserts: 插入前不能确定得到插入行数的语句。
  • mixed-mode inserts: 插入中有一部分的数据是自增长的, 一部分是确定的。

innodb_autoinc_lock_mode: 0, auto-inc locking 的方式,
innodb_autoinc_lock_mode: 1, 默认值,
对于imple inserts, 该值会用互斥量去对内存中的计数器进行累加操作,
对于bulk inserts,使用auto-inc locking 方式,不考虑回滚操作时,对于自增值列的增长是连续的,statement-based方式的replication可以很好的工作。
innodb_autoinc_lock_mode: 2, 所有的insert-like自增长的值产生的都是通过互斥量。性能最高。出现的问题是增长的值不连续, 基于statement-based replication 会出现问题。所以在使用当前模式时,任何时候都应该使用 row-based replalication, 这样才能保证最大的并发性能以及replication主从数据的一致。

MyISAM自增主键是表锁设计,自增长不用考虑并发插入的问题, 因此在Master傻姑娘使用InnoDB存储引擎,slave上使用MyISAM存储引擎的replication架构下用户必须考虑这种问题。???

外键和锁

InnoDB引擎中,对于一个外键列,如果没有显式地对这个列加索引,InnoDB存储引擎自动对其家一个索引,这样就可以避免表锁。

对于外键值的插入或者更新,首先要查询父表中的记录,如果以一致性非锁定读的方式读取,会发生数据不一致的问题,此时需要对父表加一个S锁。如果这时父表已经有一个X锁,则子表的操作会被阻塞。

锁的算法

行锁的三种算法

  • Record Lock: 单行记录上锁,总是会去锁定索引记录,如果InnoDB存储引擎在建立的时候没有设置任何一个索引,那么InnoDB存储引擎会使用隐式的主键来进行锁定。
  • Gap Lock: 锁定一个范围,不包含记录本身,为了阻止多个事务将记录插入到同一个范围内。但是又导致Phantom Problem(幻读)问题的产生。
  • Next-Key Lock: Gap Lock + Record Lock, 锁定一个范围,并锁定记录本身, 解决了Phantom Problem。

当查询的索引含有唯一属性时,InnoDB存储引擎会对Next-Key Lock进行优化,将其降级为Record Lock锁住索引本身而不是范围。

对于辅助所用,加上的是Next-keyLock, 锁定的是范围,并且还会对下一个键值加上Gap Lock.

显示关闭gap_lock后,除了外键约束和唯一性检查依然需要GapLock,其余情况仅需要使用RecordLock进行锁定。

  • 将事务的隔离级别设置为READ COMMITTED
  • 设置innodb_locks_unsafe_for_binlog=1

解决Phantom Problem

默认的可重复读的事务隔离级别下,InnoDB存储引擎采用Next-Key Locking机制来避免幻读。
在ReadCommited模式下,仅采用RecordLock来加锁。

锁问题

通过所及之可以实现事务的隔离性要求,使得事务可以并发地工作。
脏数据是指事务对缓冲池中行记录的修改,并且还没有被提交的数据。

脏读

在不同的事务下,当前事务可以读到另外事务未提交的数据(脏数据)。

不可重复读

在一个事务内,多次读取同一数据集合,由于当前事务看到了另外的事务提交的数据,两次返回的数据是不一样的。
InnoDB存储引擎中,使用Next-Key Lock算法来避免不可重复读的问题(不可重复读也叫幻读)。

丢失更新

一个事务的更新操作会被另一个事务的更新成操作覆盖。

阻塞

因为不同锁止键的兼容性关系,在有些时刻一个事务中的锁需要等待另一个事务中的锁匙放它所占用的资源,就是阻塞,阻塞时为了确保事务可以并发且正常地运行。

innodb_lock_wait_timeout: 等待超时时间
innodb_rollback_on_timeout: 等待超时时是否对进行中的事务进行回滚。默认为OFF,默认情况下InnoDB存储引擎不会回滚超时引发的错误异常。。

1
SET @@innodb_lock_wait_timeout=60;

死锁

概念

两个或者两情歌以上的事务在执行过程中,因为争夺锁资源而造成相互等待的现象。

超时解决

解决思索最简单的方式是不要有等待,将任何的等待都化为回滚,并且事务重新开始。
如果超时的事务更新了很多行,占用了较多的undo log会导致回滚性能差。

死锁检测

采用 wait-for graph(等待图)的方式进行死锁检测。要求数据库保存两种信息,通过这两种信息构造一张图,如果图中有回路,就代表存在死锁,从而选择回滚undo量最小的事务。

  • 锁的信息链表
  • 事务等待链表

锁升级

锁升级是指将所得粒度降低。InnoDB存储引擎不存在锁升级的问题。因为不是根据每个记录来产生行锁,而是根据每个事务访问的每个页对锁进行管理,采用的是位图的方式。因此不管是一个事务锁住页中一个记录还是多个记录,开销通常是一样。

InnoDB存储引擎索引

InnoDB支持B+树索引、全文索引和哈希索引。

B+树索引就是传统意义上的索引,是目前关系型数据库中查找最为常用和最为有效的索引。
InnoDB存储引擎会根据表的使用情况自动为表生成哈希索引,不能人为敢于是否在一张表中生成哈希索引。

数据结构与算法

二分查找法

也叫折半查找,必须是一组有序的记录数组。具体详见《二分查找法》。

二叉查找树与平衡二叉树

二叉查找树中,左子树的键值总是小于根的键值,右子树的键值总是大于根的键值。

前序遍历:根 -> 左 -> 右
中序遍历: 左 -> 根 -> 右
后序遍历: 左 -> 右 -> 根

当一个二叉查找树的每个结点只有左子树或者只有右子树时,该数据结构退化成了链表,查找性能会变低,时间复杂度变成O(n).

平衡二叉树:在二叉树的基础上,必须满足任何结点的两个子树高度最大差1. 平衡二叉树的查询速度极快,但是维护一颗平衡二叉树的代价非常大。 需要经过1次或者多次左旋和右旋来得到插入或者更新后的平衡树。

B+树

B+树由B树和索引顺序访问方法演化而来,为磁盘或者其他直接存取辅助设备设计的一种平衡查找树。
在B+树中,所有记录结点都是按照键值的大小顺序存放在同一层的叶子结点上,由各个叶子结点指针进行连接。

B+树的插入

B+树的插入必须保证插入后叶子结点中的记录依然排序,同时要考虑插入到B+树的三种情况,每种情况都可能会导致不同的插入算法。

1)叶子页未满,索引页未满:直接将记录插入到叶子结点。
2)叶子页已满,索引页未满:
2.1)拆分LeafPage
2.2) 将中间的结点放入到IndexPage中
2.3)小于中间结点的记录放在左边
2.4) 大于或者等于中间结点的记录放在右边
3)叶子页已满,索引页已满:
3.1)拆分LeafPage
3.2) 小于中间结点的记录放左边
3.3) 大于或者等于中间结点的记录放右边
3.4) 拆分索引页IndexPage
4.5) 小于中间结点的记录放左边
4.6) 大于中间结点的记录放右边
4.7) 中间结点放入上一层IndexPage

无论如何变化,B+树总会保持平衡。但是为了保持平衡对于新插入的键值可能需要做大量的拆分页操作。
因为B+树结构主要用于磁盘,页的拆分意味着磁盘的额操作,所以在可能的情况下尽量减少页的拆分。
B+树提供了类似于平衡二叉树的旋转功能。
旋转发生在LeafPage已满,但是其左右兄弟结点没有满的情况下。这时B+树并不会急于去做拆分页的操作,而是将记录转移到所在页的兄弟结点上。在通常情况下,左兄弟回被首先检查用来做旋转操作。

B+树的删除

B+树删除时根据填充因子的变化来衡量(最小填充因子的值为50%)。
删除操作同样必须保证删除后叶子结点中的记录已俨然排序。

叶子结点大于填充因子, 中间结点大于填充因子:直接将记录从叶子结点删除,如果该结点还是IndexPage的结点,用该结点的右结点代替。
叶子结点小于填充因子,中间结点大于填充因子:合并叶子结点和它的兄弟结点,同时更新IndexPage
叶子结点小于填充因子,中间结点小于填充因子:合并叶子结点和它的兄弟结点,更新IndexPage,合并IndexPage和它的兄弟结点。

B+树索引

聚集索引

按照每张表的主键构造一颗B+树,同时叶子结点中存放的是张张表的行记录数据,聚集索引的叶子结点称为数据页。
由于实际的树叶也只能按照一颗B+树进行排序,因此每张表只能拥有一个聚集索引。大多数情况下,查询优化器倾向于采用聚集索引。因为聚集索引能够在B+树索引的叶子结点上直接找到数据。此外定义了数据的逻辑顺序,聚集索引能够特别快地访问针对范围的查询。

数据页存放的是完整的每行的数据,索引页存放的是键值和指向数据页的偏移量Offset。
聚集索引是按照主键逻辑顺序在磁盘存储。

辅助索引

对于辅助索引,叶子结点除了包含键值意外,每个结点中的索引行中包含了聚集索引键。
每张表中可以有多个辅助索引,当通过辅助索引来寻找数据时,InnoDB存储引擎会遍历并通过叶子级别的指针获得指向主键索引的主键,然后再通过主键索引来找到一个完整的行记录。

B+树索引的分裂

InnoDB存储引擎的PageHeader中包含了Page_Last_Insert、Page_Direction、Page_N_Direction,通过这几个参数来决定向左分裂或者向右分裂。

如果插入是随机的,则取页的中间记录作为分裂点的记录。
如果向同一方向进行插入的记录数量是5,并且目前已经定位到的记录之后还有3条记录,则分裂点的记录为定位到的记录后的第三条记录,否则分裂点记录就是待插入的记录。

B+树索引的管理

只对表中b字段的前100个字符创建索引。

1
alter table t  add key idx_b(b(100));

查看t表中的所有索引。

1
show index from t;

在非高峰时期执行以下语句,更新索引Cardinality信息,使得优化器和索引可以更好的工作。

1
analyze table t;

生产环境创建索引时遇到的问题:

临时表方式创建索引

先创建临时表,导入数据,创建索引然后修改临时表名称。

Fast Index Creation

FIC在辅助索引创建的过程中对表加了S锁,因此在创建的过程中之能对该表进行读操作,如果有大量的食物需要对目标表进行写操作,那么数据库的服务同样不可用。
FIC方式只限于辅助索引,对于主键的创建和删除需要重建一张表。

Online Schema Change

PHP脚本维护,且在进行OSC过程中,允许SET sql_bin_log=0, 因此所做的操作不会同步到slave服务器,可能导致主从不一致的情况。

Online DDL

允许辅助索引创建的同时,还允许Insert、UPdate、Delete等DML操作,极大地提高了Mysql数据库在生产环境的可用性。
还可以在线的操作以下命令:

1
2
3
4
辅助索引的创建与删除
改变自增长值
添加或者删除外键约束
列的重命名
1
2
alter table table_name add index index_name algorithm={default|INPLACE|COPY}
lock={default|none|shared|exclusive}

algorithm:
copy: 临时表方式创建索引
inplace:不需要创建临时表(默认)
default:根据参数old_alter_table判断通过copy或者inplace算法。
lock: 创建索引或者删除索引时对表解锁的情况。
none: 不加锁,任意读写操作都不会收到阻塞。
share: 与FIC类似,执行索引创建或者删除操作时,对目标加S锁。可以并发读,阻塞写事务。
exclusive:执行索引创建或者删除时,对表增加一个X锁,阻塞所有的读写事务。
default:通过判断事物的最大并发性来判断执行DDL模式。none -> share-> exclusive.

远离:在执行创建或者删除操作时,将INsert、UPDATE、DELETE等DML操作日志写入缓冲中,等待索引创建完成后,再将重做应用到表上,达到数据一致性。因此索引在创建过程中SQL优化器不会选择正在创建中的索引。
缓存大小由innodb_online_alter_log_max_size=128MB控制。

Cardinality值

表示索引中不重复记录数量的预估值。InnoDB根据Cardinality来决定是否使用该索引。
Cardinality的值是通过采样的方法来完成的。

当insert、update的数据占有表中数据的1/16时。
每一行的更新次数:stat_modified_counter > 2 000 000 000时
就会更新Cardinality值。

当执行以下命令时会导致InnoDB存储引擎重新计算Cardinality值:

1
2
3
4
analyze table
show table status
show index
访问information_schema 下的表tables和表statistics

B+树索引的使用

不同应用中B+树索引的使用

OLTP应用和OLAP应用中索引的使用。

联合索引

覆盖索引

从辅助索引就可以查询到想要的数据,而不需要再查一次聚集索引中的记录。

优化器选择不使用索引的情况

当优化器发现辅助索引不能进行索引覆盖时,并且查找的数据量比较大时会放弃辅助索引,转向全表扫描。

使用force index();

Multi-range Read优化

为了减少磁盘的随机访问,并且将随机访问转化为较顺序的数据访问。

MRR的好处:

  • MRR 使得数据访问变得较为顺序,在查询辅助索引时,首先根据得到的查询结果,按照主键进行排序,并按照主键排序的顺序进行书签查找。
  • 减少缓冲池中页被替换的次数
  • 批量处理对键值的查询操作

对于InnoDB和MyISAM存储引擎的范围查询和JOIN查询操作,MRR的工作方式如下:

  • 将查询得到的辅助索引键值存放在一个缓存中,这时缓存中的数据时根据辅助索引键值排序的。
  • 将缓存中的键值根据rowID进行排序
  • 根据rowID的排序顺序来访问实际的数据文件。

如果InnoDB或者MyISAM存储引擎的缓冲池不是足够大,不能存放下一张表中的所有数据,此时频繁的离散读操作还是会导致缓存中的页被替换出缓冲池,然后又不断地被读入缓冲池。如果按照住建顺序进行访问,就可以将重复行为降到最低。
可以通过optimizer_swith中的flag来控制,当mrr为on时表示启用mrr优化。
mrr_cost_based标记表示是否通过cost_based方式选择是否启用mrr。
如果mrr = 0n , mrr_cost_based=off , 表示总启用mrr。

SET @@optimizer_switch=’mrr=on,mrr_cost_based=off’;

IndexConditionPushDown优化

IndexConditionPushDown 会在读取索引的同时,判断是否可以进行WHERE条件的过滤从而提升性能。

HASH算法

Hash表

InnoDB存储引擎中的哈希算法

自适应哈希索引

全文检索

倒排索引

InnoDB全文索引

全文检索

索引组织表

在InnoDB存储索引中,表时根据主键顺序组织存放的,这种存储方式的表称为索引组织表。

在InnoDB存储引擎表中,每张表都有个主键,如果在创建表时没有显式地定义主键,则会按照如下方式创建主键:

  • 判断表中是否有非空的唯一索引,如果有该列为主键;
  • 表中没有非空的唯一索引,InnoDB引擎自动创建一个6字节大小的指针;
    当表中有多个非空唯一索引时,InnoDB存储引擎将选择建表时第一个定义的非空唯一索引为主键(根据定义的索引顺序,而不是建表时列的顺序)。

InnoDB逻辑存储结构

表空间

表空间是InnoDB存储引擎逻辑结构的最高层,所有的数据都存放在表空间中。
表空间由段(Segment)、区(extent)、页(Page)组成。
表空间结构

在默认情况下,InnoDB存储引擎有一个共享表空间ibdata1,所有的数据都存放在这个表空间内。
如果配置了innodb_file_per_table,则每张表内的数据可以单独存放在一个表空间中。此时每张表的表空间存放的只是数据、索引和插入缓冲的BitMap页,其他类的数据如回滚(undo)、插入缓冲索引页、系统事务信息、二次写缓冲等还是存放在原来的共享表空间。

表空间是由各个段组成的,包含数据段、索引段、回滚段等。
数据段就是B+树的叶子节点,索引段就是B+树的非叶子结点。
回滚段???
在InnoDB存储引擎中,对段的管理都是由引擎自身完成的,DBA不能对其进行控制。

区是由连续的页组成的空间,在任何情况下每个区的大小都是1MB。为了保证区中页的连续行,InnoDB引擎每次从磁盘申请4~5个区。默认情况下InnoDB存储引擎页的大小为16K,一个区中存放64个连续的页。

页是InnoDB磁盘管理的最小单位,在InnoDB存储引擎中,默认每个页的大小为16KB。
可以通过参数 innodb_page_size 来设置页的大小为4K、8K、16K。
如果设置完成,则所有表中的页大小都变成innodb_page_size,不可用对其再次进行修改。除非通过mysqldump导入和导出来生成新的库。

页的类型有:

  • 数据页(B-Tree Node)
  • undo 页(undo log Page)
  • 系统页(System Page)
  • 事务数据页(Transaction System Page)
  • 插入缓冲位图页(Insert Buffer BitMap)
  • 插入缓冲空闲列表页(Insert Buffer Free List)
  • 未压缩的二进制大对象页(Uncompressed BLOB Page)
  • 压缩的二进制大对象页(Compressed BLOB Page)

Innodb存储引擎按行进行存放,每个页存放的行记录有影星的定义,最多允许存放16KB/2 - 200 行的记录。

InnoDB行记录格式

Compact 行记录格式

从Mysql5.0开始,设计目的是高效地存储数据。一个页中存放的行数据越多,其性能就越高。

|—|—|—|—|—|—|—|—|—|
| 变长字段长度列表 | NULL标志位 | 记录头信息 | 列1数据 | 列 2 数据 | …|事务ID列|回滚指针列|__rowid|

变长字段长度列表:按照列的顺序,逆序放置。
如果列的长度小于255个字节,占用1字节;
如果列的长度大于255个字节,占用2字节;
变长字段的长度最大不可以超过2个字节,因为Mysql数据库中VARCHAR类型的最大长度限制为65535.
NULL标志位:表示改行数据中是否有NULL值,有用1表示,占用1个字节。
记录头信息:固定占用5个字节。
最后的部分是实际存储没咧的数据。
事务ID列:隐藏列,占用6字节。
回滚指针列:隐藏列,占用7字节。
__rowid:占用6字节。

行溢出数据

InnoDB存储引擎的数据都是存放在页类型为B-tree Node中,当发生行溢出是,数据存放在页类型为uncompress BLOG页中。

InnoDB数据页结构

Named File Formats 机制

###约束

数据完整性

约束的创建和查找

约束和索引的区别

对错误数据的约束

ENUM和SET约束

触发器与约束

外键约束

视图

分区表

分区功能不是在存储引擎层面完成的。
支持分区的存储引擎有:MyISAM、InnoDB、NDB。
不支持分区的存储引擎有:CSV、FEDORATED、MERGE。

分区的过程是将一个表或者索引分解为多个更小的、更可管理的部分。
从逻辑上讲只有一个表或者一个索引库,但是在物理上这个表或者索引可能由数十个物理分区组成。每个分区都是独立的对象,可以独自处理,页可以作为一个更大的对象的一部分进行处理。

Mysql支持水平分区,不支持垂直分区(水平分区,将同一表中的不同行的记录分配到不同的物理文件;垂直分区,将同一表中的不同列分配到不同的物理文件)。
Mysql不支持全局分区,只支持局部分区。
局部分区:一个分区中既存放了数据由存放了索引。
全局分区:数据存放在各个区,所有数据的索引存放在一个对象中。

分区主要用来数据库高可用性的管理,对于某些SQL的性能带来提高。

分区类型

  • RANGE分区:行数据基于属于一个给定连续区间的列值被放入分区。
    1
    2
    3
    4
    5
    6
    7
    create table t (
    id int
    )engine=innodb
    partition by range(id)(
    partition p0 values less than (10),
    partition p1 values less than (20)
    );
1
alter table t add partition (partition p2 values less than maxvalue);

启用分区后,表是由建立各个分区时的各个分区ibd文件组成。

  • List分区:和RANGE分区类似,只是LIST分区面向的是离散的值。
1
2
3
4
5
6
7
8
create table t(
a INT,
b INT
) engine = INNODB
partition by list(b)(
partition p0 values in (1,3,5,7,9),
partition p1 values in (0,2,4,6,8)
)
  • HASH分区:根据用户自定义的表达式返回值进行分区,返回值不能为负数。
    Hash分区的目的是将数据均匀地分布到盂县定义的各个区中,保证各分区的数据数量大致都一样。
1
2
3
4
5
6
create table t(
a int,
b datetime
)engine=innodb
partition by hash(year(b))
partitions 4;
1
2
3
4
5
6
create table t(
a int,
b datetime
)engine=innodb
partition by linear hash(year(b))
prtions 4;

LINEAR HASH分区的优点在于,增加、删除、合并和拆分分区将变得更加快捷,有利于处理含有大量数据的表。
缺点在于,与使用HASH分区得到的数据分布相比,各个分区间数据的分布可能不大均衡。

  • KEY分区:根据MYSQL数据库提供的哈希函数来分区。
    与HASH分区相似,不同之处在于HASH分区使用用户定义的函数进行分区,KEY分区使用MYSQL数据库提供的函数进行分区。对于NDB Cluster引擎,MYSQL数据库使用MD5函数来分区;对于其他引擎,Mysql数据库使用内部的哈希函数。
    在KEY分区中,使用关键字LINEAR 和在HASH分区中具有同样的效果。

无论哪种类型的分区,如果表中存在主键或者唯一索引时,分区列必须时唯一索引的一个组成部分。
如果建表时没有指定主键,唯一索引,可以指定任何一个列为分区列。

以上四种RANGE、LIST、HASH、KEY分区条件是,数据必须是整形,如果不是整型,那应该需要通过函数将其转化成整型,如YEAR(), TO_DAYS(), MONTH()等函数。

  • Column分区,RANGE分区和LIST分区的一种进化。
    COLUMN分区可以直接使用菲整形的数据进行分区,分区根据类型直接比较得到,不需要转化为整形。
    RANGECOLUMNS分区可以对多个列的值进行分区。

COLUMN分区支持
所有的整型:INT、SMALLINT、TINYINT、BIGING。
日期类型:DATE、DATETIME
字符串类型:CHAR、VARCHAR、BINARY、VARBINARY。

1
2
3
4
5
6
7
8
create table t(
a INT,
b DATETIME
)engine=innodb
partition by range columns(b)(
partition p0 values less than ('2020-01-01'),
partition p1 values less than ('2020-02-01')
)

子分区

子分区是在分区的基础上再进行分区,有时页称为符合分区。 MySQL允许在RANGE和LIST的分区傻姑娘再进行KEY或者HASH的子分区。

1
2
3
4
5
6
7
8
9
10
11
create table t(
a int,
b date
)engine = innodb
partition by range(year(b))
subpartition by hash(to_days(b))
subpartitions 2(
partition p0 values less than (1990),
partition p1 values less than (2000),
partition p2 values less than maxvalue
);

分区中的NULL值

MysQL允许对NULL值做分区,对于RANGE分区,如果像分区列插入了NULL值,则Mysql数据库将该值放入到最左边的分区。
LIST分区下要使用NULL值,必须显式地指出将该值放入哪个分区。
HASH和KEY分区函数会将NULL值的记录返回0.

分区和性能

当数据量大时,分区可以有效降低B+树的层级。
当数据量小时,分区带来的收益并不明显,并且有可能导致扫描所有分区反而降低效率。

参数文件

动态参数

动态参数可以在Mysql实例运行中进行更改,可以通过SET命令对动态参数值进行修改。
SET [global|session] key = value;

静态参数

静态参数在整个实例生命周期内不得进行更改,需要重新启动实例才会生效。

日志文件

错误日志

记录了对Mysql的启动、运行、关闭过程中所有的错误、警告和正确的信息。

慢查询日志

可以帮助定位可能存在问题的SQL语句,从而进行SQL层面的优化。Mysql在启动时设置一个阈值,将运行时间超过该值的所有SQL语句都记录到慢查询日志文件中。
通过设置 long_query_time 来设置,单位为秒。
log_queries_not_using_indexes: 记录没有使用索引的查询日志。
log_throttle_queries_not_using_indexes:每分钟允许记录到showLog的且未使用索引的SQL语句次数,0表示不限制。

查看慢日志

1
2
3
mysqldumpslow *.log
//查看执行时间最长的10条sql语句
mysqldumpslow -s al -n 10 *.log

查询日志

查询日志记录了所有对MySQL数据库请求的信息,无论这些请求是否得到了正确的执行。

查看查询日志:

1
tail *.log

二进制日志

二进制日志记录了对MySQL数据库执行更改的所有操作,但是不包括SELECT和SHOW这类对数据本身没有修改的操作。二进制日志还包括了执行数据库更改操作的时间等其他额外信息。

作用

  • 恢复
    某些数据的恢复需要二进制日志,如在一个数据库全备文件恢复后,用户可以通过二进制日志进行pint-in-time的恢复。
  • 复制
    与恢复类似,通过复制和执行二进制日志使另一台远程的MySQL数据库与当前数据库进行实时同步。
  • 审计
    通过二进制日志中的信息来进行审计,判断是否有对数据库进行注入的攻击。

配置

  • max_binlog_size: 单个日志文件的最大值,默认1G
  • binlog_cache_size: 所有未提交的二进制日志会被记录到一个缓存中,等该事务提交时直接将缓冲中的二进制日志写入二进制日志文件,该缓冲的大小默认为32。该值是基于会话的,每个会话会申请32K的空间。
  • binlog_cache_use: 记录了使用临时文件写二进制日志的次数。
  • sync_binlog:表示每写缓冲多少次就同步到磁盘,为1时,表示采用同步写磁盘的方式来写二进制日志,这时写操作不使用操作系统的缓冲来写二进制日志。默认值为0.
  • binlog-do-db:写入哪些库的日志,默认为空表示同步所有库的日志到二进制日志。
  • binlog-ignore-db:忽略哪些库的日志
  • log-slave-update:当前库为slave时,如果需要将从master取得并执行的二进制日志写入自己的二进制日志文件中时需要设置该值。
  • binlog_format:STATEMENT/ROW/MIXED
    STATEMENT:格式基于SQL语句,如果主服务器运行rand、uuid等函数或者触发器等操作时,就会导致主从服务器上表中的数据库表中的数据库不一致。
    ROW:二进制的日志,记录了表的更改情况,解决了Statement下复制的问题。可以设置InnoDB的事务隔离设置为READ_COMMITTED,以获得更好的并发性。
    MIXED:默认采用STATEMENT,但是在特殊情况下会转为ROW。

套接字文件

名为mysql.sock,可以通过Unix域套接字方式进行与本地MySQL进行连接。套接字文件由参数socket控制。

PID文件

MySQL实例启动时,会将自己的进程ID写入一个文件中,该文件为PID文件。

表结构定义文件

因为MySQL插件式存储引擎的体系结构关系,MySQL数据的存储是根据表进行的,每个表都会有与之对应的文件。但不论表采用哪种存储引擎,MySQL都有一个以frm为后缀名的文件,记录了这个表结构或者视图的定义。

Innodb存储引擎文件

表空间文件

InnoDB采用将存储的数据按照表空间进行存放的设计。默认配置喜爱会有一个初始大小为10MB,名为ibdata1的默认表空间文件。

将ibdata1,ibdata2两个文件用来组成表空间。

1
innodb_data_file_path=/db/ibdata1:2000M;/dr2/db/ibdata2:2000M:autoextend

如果该两个文件位于不同的磁盘上,磁盘的负载可能被平均,因此可以提高数据库的整体性能。
设置innodb_data_file_path后,所有给予innoDB存储引擎的表的数据都会记录带该共享表空间中。

1
innodb_file_per_table=ON //每个基于InnoDB存储引擎的表产生一个独立表空间。命名规则是:表名.ibd

单独的表空间文件仅存储该表的数据、索引和插入缓冲BITMAP等信息,其余信息还存放在默认的表空间中。

表空间

重做日志文件

默认情况下,在InnoDB存储引擎的数据目录下会有两个名为ib_logfile0和ib_logfile1的文件。
他们记录了对于InnoDB存储引擎的事务日志。

重做日志用于由于主机宕机导致实例失败时,恢复到失败前的状态,一次来保证数据的完整性。

每个InnoDB存储引擎至少有1个重做日志文件组,每个文件组下至少有2个重做日志文件。为了得到更高的可靠性,用户可以设置多个的景象日志组。将不同的组存放在不同的磁盘上,以此提高重做日志的高可用性。在日志组中每个重做日志文件的大小一致,并以循环写入的方式运行。
InnoDB存储引擎献血重做日志文件1,当达到文件的最后时,会切换到重做日志文件2,再当重做日志文件2也被写满时,再切换到重做日志文件1中。

1
2
3
4
innodb_log_file_size:每个重做日志文件的大小
innodb_log_files_in_group: 日志文件组中重做日志文件的数量,默认为2
innodb_mirrored_log_groups: 日志镜像文件组的数量,默认为1,表示没有镜像。
innodb_log_group_home_dir: 日志文件组所在的路径,默认为./

重做日志文件的大小对InnoDB存储性能有非常大的影响。
如果设置太大,在恢复时可能需要很长的时间。
如果设置太小,会导致一个事务的日志需要多次切换重做日志文件。同时会导致频繁地发生 async checkpoint, 导致性能抖动。

二进制文件与重做日志

层次不同:
二进制日志:记录所有与MySQL数据库有关的日志记录,包括InnoDB,MyISAM,Heap等其他存储引擎的日志。
重做日志:值记录InnoDB存储引擎本身的事务日志。

内容不同:
二进制日志文件记录的是一个事务的具体操作内容,为逻辑日志。
重做日志记录的是关于每个页的更改的物理情况。

写入时间不同:
二进制文件仅在事务提交前进行,无论事务大小,只写磁盘一次。
在事务进行的过程后再难过,不断有重做日志条目被写入到重做日志文件中。

重做日志条目结构

redo_log_type | space | page_no | redo_log_body
redo_log_type : 占用1字节,表示重做日志的类型
space: 表示表空间的ID,采用压缩的方式,占用空间可能小于4字节
page_no:表示页的偏移量,压缩方式
redo_log_body:每个重做日志的数据部分,恢复时需要调用相应函数解析。

重做日志写入

重做日志的写入不是直接写,而是先写入重做日志缓冲(redo log buffer)中,然后按照一定的条件顺序地写入日志文件。

从重做日志缓冲网磁盘写入时,按照写入的最小单位(扇区大小:512个字节)进行写入,因此可以保证写入必定是成功的,因此在重做日志的写入过程不需要有doublewrite。

写入时机:
1) 不论事务已经提交,主线程每秒都会将重做日志缓冲写入到磁盘的重做日志文件中。
2) 事务提交时,设置了innodb_flush_log_at_trx_commit。
值为0时,不将事物的重做日志写入磁盘上的日志文件,
1表示在执行commit时将重做日志缓冲同步写到磁盘,
2表示将重做日志异步写到磁盘(也就是写到文件系统的缓存中)

因此,为了保证ACID中的D(持久性),必须将innodb_flush_log_at_trx_commit设置为1,当数据库因为意外宕机时,可以通过重做日志文件恢复,并保证可以恢复已经提交的事务。

InnoDB 体系架构

后台线程

Master Thread

主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性。
包括脏页的刷新、合并插入缓冲、UNDO页的会后等。

IO Thread

InnoDB中使用了AIO(AsyncIO)来处理写IO请求,提高了数据库性能。
IO线程的工作主要是负责AIO的回调处理。

可以使用innodb_read_io_threads和innodb_write_io_threads进行设置。

Purge Thread

事务被提交后,其所用的undolog可能不再需要,因此需要Perge Thread来回收已经使用并分配的undo页。
可以使用 innodb_purge_thread 来设置线程数量。

Page Cleaner Thread

为了减轻MasterThread的工作,以及用户查询线程的阻塞提升性能。
将之前版本中脏页的舒心操作都放入到单独的线程中完成。

内存

缓冲池

InnoDB存储引擎是基于磁盘存储的,将其中的记录按照页的方式进行管理。缓冲池就是一块内存区域,通过内存的速度来弥补磁盘速度较慢对数据库的性能影响。

在数据库中进行读取页的操作,首先从磁盘读取到的页放在缓冲池中,下一次再读取相同的页时,首先判断该页是否在缓冲池中。如果在缓冲池中,则直接读取该页,否则读取磁盘上的页。

对于数据库中页的修改操作,首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。

页从缓冲池刷新到磁盘上的操作是通过Checkpoint的机制刷新回到磁盘。

因此缓冲池的大小直接影响着数据库的整体性能。可以使用 innodb_buffer_pool_size 来设置缓冲池大小。

缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲、自使用哈希索引、InnoDB存储的锁信息、数据字典信息等。

缓冲池

InnoDB可以有多个缓冲池实例,每个页根据哈希值平均分配到不同的缓冲池实例中。这样减少了数据库内部的资源竞争,增加了数据库的兵法处理能力。可以使用 innodb_buffer_pool_instances来进行配置。

缓冲池管理算法

LRU List

LRU List 用来管理已经读取的页,最频繁使用的页存放在LRU列表的前端,最少使用的页存放在LRU列表的尾端。当缓冲池不能存放新读取到的页时,首先释放LRU列表中尾端的页。

新读取的页并不直接存放在LRUList列表的前端,而是存放在midPoint的位置。midPoint之后的列表称为Old列表,之前的列表称为New列表,New列表中的页都是活跃的热点数据。

这是因为,如果新读取的页如果放在最前端时,如果遇到需要访问和扫描大部分甚至全部页的时候,热点页面被刷出缓存,但是非热点数据却存放在了LRU列表的最前端。

MID POINT

使用参数:innodb_old_blocks_pct 来控制midPoint的位置。
innodb_old_blocks_times 来控制读取到MidPoint位置后需要等待多久才会被加入到LRU列表的New列表。

Free List

数据库刚启动的时候,LRUList是空的没有任何页。此时页都存放在FreeList中。当需要从缓冲中分页时,县从Free列表中查找是否有可用的空闲页,如果有可用的空闲页,则将该页从FreeList中删除,存放到LRUList中。如果FreeList中没有可用的空闲页,根据LRU算法,淘汰LRU尾部的页,将该页内存空间分配给新的页。

Flush List

在LRU列表中的页被修改后,该页需要通过CheckPoint机制刷回磁盘。
FlushList中的页称为脏页里诶包,脏页既存在于LRUList,也存在于FlushList。
LRUList用来管理缓冲池中页的可用性,FlushList用来管理将页刷新回磁盘。

重做缓冲日志

InnoDB存储引擎首先将重做日志信息先放入到这个缓冲区,然后按照一定的频率(每秒一次)将其刷新到重做日志文件。
可以使用参数:innodb_log_buffer_size来调整该缓冲区域的大小,默认为8MB。

额外的内存池

例如:分配了缓冲池(innodb_buffer_pool),但是每个缓冲池中的帧缓冲还有对应的缓冲控制对象,这些对象记录了一些如LRU、锁、等待信息,而这个对象的内存需要从额外内存池中申请。

在对一些数据结构本身的内存进行分配是,需要从额外的内存池中进行申请。因此在申请了很大的InnoDB缓冲池是,需要相应地增加额外的内存池。

Checkpoint技术

CheckPoint

Write Pos 是当前记录的位置,一边写好一遍往后移。
CheckPoint 是当前要擦除的位置,也就是往后推移并且循环的,擦除记录前要把记录更新到数据文件。
Write Pos和 CheckPoint之间是可以写入的部分,用来记录新的操作。

1) 缩短数据库的恢复时间;
当数据库发生宕机时,数据库不需要重做所有的日志,因为CheckPoint之前的页都已经刷新回磁盘,因此只需要对checkPoint之后的重做日志进行恢复,这样大大缩短了数据库的恢复时间。

2) 缓冲池不够用时,将脏页刷新到磁盘;
当缓冲池不够用时,根据LRU算法会移除最近最少使用的页,如果当前页为脏页,则需要刷新该脏页到磁盘。

3) 重做日志不可用时,刷新脏页;
重做日志中Write Pos和checkPint之间没有空间来记录RedoLog时,强制刷新脏页,使得CheckPoint后移。

InnoDB关键特性

插入缓冲

Insert Buffer

对于非聚集索引的插入或者更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,如果存在则直接插入;如果不在则先放入到一个InsertBuffer对象中。然后再以一定的频率进行InsertBuffer和辅助索引页子结点的Merge操作。
通常会将多个插入操作合并到一个操作中,大大提高了非聚集索引插入的性能。

InsertBuffer的使用要同时满足两个条件:
1) 索引是辅助索引(非聚集索引)
如果是聚集索引,则直接按照顺序写就行了,很高效。
2) 索引不是唯一的
因为在插入缓冲时,数据库并不去查找索引页来判断插入的记录唯一性。如果去查找肯定又会有离散读取的情况发生,从而导致InsertBuffer失去意义。

当满足索引是辅助索引且非唯一时,InnoDB存储引擎会使用InsertBuffer, 这样就可以提高插入操作的性能。

当应用程序进行大量的插入操作时数据库发生了宕机,这个时候会有大量的InsertBuffer并没有合并到实际的非聚集索引中,因此恢复可能需要很长的时间。

同时在写密集的情况下,InsertBuffer会占用过多的缓冲池内存(innodb_buffer_pool),默认最大可以占用1/2的缓冲池内存。修改IBUF_POOL_SIZE_PER_MAX_SIZE 可以对 InsertBuffer大小进行控制,比如改为3则最大只能使用1/3的缓冲池内存。

Change Buffer

InsertBuffer 的升级版本,可以对Insert、Delete、Update都进行缓冲,分别是InsertBuffer、DeleteBuffer、PurgeBuffer。

ChangeBuffer适用对象依然是非唯一的辅助索引。

对一条记录进行UPDATE操作需要分为:
1)将记录标记为删除(DeleteBuffer对应该过程);
2)真正将记录删除(PurgeBuffer对应删除操作)

开启Buffer选项:
innodb_change_buffering: [inserts、deletes、purges、changes、all、none]
changes表示启用inserts、deletes
all表示启用所有,默认值
none表示都不启用。

可以通过innodb_change_buffer_max_size控制changebuffer最大使用内存的数量:
该参数的最大有效值为50, 表示只能占用缓冲池的50%。

Merge InsertBuffer

  • 辅助索引页被读取到缓冲池时;
    执行SELECT语句时,要确认该辅助索引页是否有记录存放于InsertBufferB+树中。
    有,则将InsertBufferB+树中该页的记录插入到该辅助索引页中。
  • InsertBuffer BitMap 追踪到该辅助索引页已无可用空间时;
    InsertBuffer BitMap用来追踪每个辅助索引页的可用空间,并至少有1/32页的可用空间。如果插入辅助索引记录时监测到插入记录后可用空间小雨1/32页,则会强制进行一个合并操作。
    (强制读取辅助索引页,将InsertBufferB+树中该页的记录以及待插入的记录插入到辅助索引页中。)
  • Master Thread
    Master Thread 每秒或者每10秒 根据srv_innodb_io_capacity 的百分比来进行一个Merge InsertBuffer操作。

如果在merge时,要进行的merge表已经被删除,此时直接丢弃已经被Insert/Change Buffer的数据记录。

两次写

DoubleWrite为了提升InnoDB存储引擎的数据页可靠性。

当InnoDB存储引擎正在将某个页写入到表中,当这个页只被写了一部分(16k的页,只写了前4k)就发生了宕机,这种情况被称为写失效(partial page write)。

为了解决partial page write,InnoDB实现了Double write buffer,就是在写数据页之前,先把这个数据页写到一块独立的物理文件位置(ibdata),然后再写到数据页。
这样在宕机重启时,如果出现数据页损坏,就需要通过该页的副本来还愿该页,然后再进行redo log重做。

两次写

double write由两部分组成,一部分是内存中的double write buffer,大小是2MB, 另一部分是磁盘上的共享表空间中连续的128个页,大小也是2M。
1)当触发数据缓冲池中的脏页刷新时,并不直接写入磁盘数据文件中,而是先拷贝到内存中的doublewrite buffer中;
2)接着从两次写缓冲区分两次写入磁盘共享表空间中(连续存储、顺序写)每次写1MB;
3)再将doublewrite buffer中的脏页数据写入实际的各个表空间中(离散写)。

两次写恢复

当不需要开启doublewrite时,使用skip_innodb_doublewrite 关闭。

innodb_buffer_pool_flushed : 当前从缓冲池中刷新到磁盘页的数量
innodb_dblwr_pages_written: double write 的数量

自适应哈希索引

InnoDB存储引擎会监控对标上各索引页的查询,如果观察到建立哈希索引可以带来速度上的提升,则建立哈希索引。自适应哈希索引是通过缓冲池的B+树页构造而来,因此建立的速度很快,不需要对整张表建立哈希索引。 InnoDB存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引。

自使用哈希索引有一个要求,对这个页的查询条件是一样的。
如:where a = xxx;
where a = xxx and b = xxx;

自使用哈希索引是数据库自优化的,无需DBA对数据库进行调整。
可以通过innodb_adaptive_hash_index 来启用或者禁用。

异步IO

1)提升吞吐量
2)IO Merge操作

刷新邻接页

当刷新一个脏页时,InnoDB存储引擎会检测该页所在区的所有页,如果是脏页,那么一起进行刷新。
通过AIO可以将多个IO卸乳操作合并为一个IO操作。

可以通过innodb_flush_neighbors 来关闭或者开启该特性。

select version();
show variables like ‘innodb_%_threads’;
show variables like ‘innodb_buffer_pool_size’;
show variables like ‘innodb_buffer_pool_instances’;
show variables like ‘innodb_change_buffering’;
show engine innodb status;

Mysql 体系结构

MySQL体系结构

  • 链接池组件
  • 管理服务和工具组件
  • SQL接口组件
  • 查询分析器组件
  • 优化器组件
  • 缓存组件
  • 插件式表存储引擎
  • 物理文件

存储引擎

可以根据MySQL官方手册给出的定义存储引擎的过程来编写符合自己需求的存储引擎。
或者使用官方提供的存储引擎。

InnoDB存储引擎

InnoDB存储引擎支持事务,其设计目标主要面向在线事务处理的应用。其特点是行锁设计、支持外间,并支持类似于Oracle的非锁定读(默认读取操作不会产生锁)。

InnoDB存储引擎将数据放在一个逻辑的表空间中,这个表空间就像黑盒一样由InnoDB存储引擎自身进行管理。可以将每个独立的表单独存放在一个idb文件中。InnoDB存储引擎支持使用裸设备来建立其表空间。

InnoDB通过多版本并发控制(MVCC)来获得高并发性;
实现了SQL标准的4种隔离级别,默认为REPEATABLE;
使用Next-KeyLocking策略避免欢度现象的产生;
提供了插入缓冲(Insert Buffer);
二次写(Double Write);
预读(Read Ahead)。

Innodb存储引擎采用了聚集的方式,每张表的存储都是按照主键的顺序进行存放。如果没有显式地在表定义时制定主键,InnoDB存储引擎会为每一行生成一个6字节的RowId,并以此作为主键。

MyISAM存储引擎

MyISAM引擎主要面向一些OLAP(联机分析处理)的应用。 因此不支持事务、表锁设计,但支持全文索引。

它的缓存池只缓存索引文件,不缓存数据文件,数据文件的缓存交给操作系统来完成。
MYISAM存储引擎表由MYD和MYI组成,MYD用来存放数据文件,MYI用来存放索引文件。

Maria存储引擎

其目标是用来替代原有的MYISAM,可以看作是MyISAM的后续版本。
特点是支持缓存数据和索引文件,应用了行锁设计,提供了MVCC功能,支持事务和非事务安全的选项。
更好的BLOB自负类型的处理性能。

NDB存储引擎

也叫NDBCluster存储引擎,壮阳药用于MySQL Cluster分布式集群环境。

NDB的特点是数据全部放在内存中,因此主键查找的速度极快,并且通过添加NDB数据存储结点可以线性地提高数据库性能,是高可用、高性能的集群系统。
由于其存储引擎特性,Join操作是在Muysql数据库层完成,而不是在存储引擎层完成。

Memory存储引擎

表中的数据存放在内存中,适合用于存储临时数据的临时表,以及数据仓库中的纬度表。Memory存储引擎默认使用哈希索引。

Memory存储引擎不支持表锁,并发行能差,不支持TEXT和BLOB列类型。存储varchar时是按照char的方式进行的。

MySQL数据库使用Memory存储引擎作为临时表来存放查询的中间结果集。
如果中间爱护你接过机大于Memory存储引擎表的容量设置,又或者包含有TEXT或者BLOB列类型的字段,Mysql会把中间结果集转换到MyISAM存储引擎表存放到磁盘中(因为MyISAM不缓存数据文件),因此会有性能损失。

Archive存储引擎

用来存储归档数据,如日志信息等。
使用zlib算法将数据行进行压缩后存储,压缩比达到1:10.
该引擎只支持INSERT和SELECT操作,该引擎使用行锁来实现高并发的插入操作。