第一章 概述

1. 数据库和实例
数据库:物理操作系统文件或其他形式文件类型的集合。在MySQL数据库中,数据库文件可以是frm、MYD、MYI、ibd结尾的文件。当使用NDB引擎时,数据库的文件可能不是操作系统上的文件,而是存放于内存之中的文件,但是定义仍然不变。
实例:MySQL数据库由后台线程以及一个共享内存区组成。共享内存可以被运行的后台线程所共享。需要牢记的是,数据库实例才是真正用于操作数据库文件的
从概念上来说,数据库是文件的集合,是依照某种数据模型组织起来并存放于二级存储器中的数据集合;数据库实例是程序,是位于用户与操作系统之间的一层数据管理软件,用户对数据库数据的任何操作,包括数据库定义、数据查询、数据维护、数据库运行控制等都是在数据库实例下进行的,应用程序只有通过数据库实例才能和数据库打交道。
一种更为直白的方式来解释:数据库是由一个个文件组成(一般来说都是二进制的文件)的,要对这些文件执行诸如SELECT、INSERT、UPDATE和DELETE之类的数据库操作是不能通过简单的操作文件来更改数据库的内容,需要通过数据库实例来完成对数据库的操作

2.MySQL被设计为一个单进程多线程架构的数据库,MySQL数据库实例在系统上的表现就是一个进程。当启动实例时,MySQL数据库会去读取配置文件,根据配置文件的参数来启动数据库实例。在MySQL数据库中,可以没有配置文件,在这种情况下,MySQL会按照编译时的默认参数设置启动实例


3.mysql体系结构


MySQL由以下几部分组成:
□ 连接池组件
□ 管理服务和工具组件
□ SQL接口组件
□ 查询分析器组件
□ 优化器组件
□ 缓冲(Cache)组件
□ 插件式存储引擎
□ 物理文件

4.存储引擎

MySQL数据库区别于其他数据库的最重要的一个特点就是其插件式的表存储引擎。MySQL插件式的存储引擎架构提供了一系列标准的管理和服务支持,这些标准与存储引擎本身无关,可能是每个数据库系统本身都必需的,如SQL分析器和优化器等,而存储引擎是底层物理结构的实现,每个存储引擎开发者可以按照自己的意愿来进行开发。

需要特别注意的是,存储引擎是基于表的,而不是数据库

由于MySQL数据库的开源特性,用户可以根据MySQL预定义的存储引擎接口编写自己的存储引擎。若用户对某一种存储引擎的性能或功能不满意,可以通过修改源码来得到想要的特性

4.1 InnoDB存储引擎
InnoDB存储引擎支持事务,其设计目标主要面向在线事务处理(OLTP)的应用。其特点是行锁设计、支持外键,并支持类似于Oracle的非锁定读,即默认读取操作不会产生锁。从MySQL数据库5.5.8版本开始,InnoDB存储引擎是默认的存储引擎。
OLTP(on-line transaction processing)翻译为联机事务处理, OLAP(On-Line Analytical Processing)翻译为联机分析处理,从字面上来看OLTP是做事务处理,OLAP是做分析处理。从对数据库操作来看,OLTP主要是对数据的增删改,OLAP是对数据的查询。
InnoDB通过使用多版本并发控制(MVCC)来获得高并发性,并且实现了SQL标准的4种隔离级别,默认为REPEATABLE级别。同时,使用一种被称为next-key locking的策略来避免幻读(phantom)现象的产生。除此之外,InnoDB储存引擎还提供了插入缓冲(insert buffer)、二次写(double write)、自适应哈希索引(adaptive hash index)、预读(read ahead)等高性能和高可用的功能。

对于表中数据的存储,InnoDB存储引擎采用了聚集(clustered)的方式,因此每张表的存储都是按主键的顺序进行存放。如果没有显式地在表定义时指定主键,InnoDB存储引擎会为每一行生成一个6字节的ROWID,并以此作为主键

4.2 MyISAM存储引擎
MyISAM存储引擎不支持事务、表锁设计,支持全文索引,主要面向一些OLAP数据库应用。在MySQL 5.5.8版本之前MyISAM存储引擎是默认的存储引擎(除Windows版本外)。数据库系统与文件系统很大的一个不同之处在于对事务的支持,然而MyISAM存储引擎是不支持事务的

在数据仓库中,如果没有ETL这些操作,只是简单的报表查询是否还需要事务的支持呢?此外,MyISAM存储引擎的另一个与众不同的地方是它的缓冲池只缓存(cache)索引文件,而不缓冲数据文件,这点和大多数的数据库都非常不同。
【ETL数据仓库技术,是英文Extract-Transform-Load的缩写,用来描述将数据从来源端经过抽取(extract)、转换(transform)、加载(load)至目的端的过程。ETL一词较常用在数据仓库,但其对象并不限于数据仓库

MyISAM存储引擎表由MYD和MYI组成,MYD用来存放数据文件,MYI用来存放索引文件
4.3 Memory存储引擎
Memory存储引擎(之前称HEAP存储引擎)将表中的数据存放在内存中,如果数据库重启或发生崩溃,表中的数据都将消失。它非常适合用于存储临时数据的临时表,Memory存储引擎默认使用哈希索引,而不是我们熟悉的B+树索引。
虽然Memory存储引擎速度非常快,但在使用上还是有一定的限制。比如,只支持表锁,并发性能较差,并且不支持TEXT和BLOB列类型。

5.连接


连接MySQL操作是一个连接进程和MySQL数据库实例进行通信。从程序设计的角度来说,本质上是进程通信。如果对进程通信比较了解,可以知道常用的进程通信方式有管道、命名管道、命名字、TCP/IP套接字、UNIX域套接字。MySQL数据库提供的连接方式从本质上看都是上述提及的进程通信方式。

5.1 TCP/IP
TCP/IP套接字方式是MySQL数据库在任何平台下都提供的连接方式,也是网络中使用得最多的一种方式。这种方式在TCP/IP连接上建立一个基于网络的连接请求,一般情况下客户端(client)在一台服务器上,而MySQL实例(server)在另一台服务器上,这两台机器通过一个TCP/IP网络连接。
在通过TCP/IP连接到MySQL实例时,MySQL数据库会先检查一张权限视图,用来判断发起请求的客户端IP是否允许连接到MySQL实例

第二章 InnoDB存储引擎

1. InnoDB存储引擎有多个内存块,可以认为这些内存块组成了一个大的内存池,负责如下工作:


后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据。此外将已修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下InnoDB能恢复到正常运行状态。
1.1 后台线程
InnoDB存储引擎是多线程的模型,因此其后台有多个不同的后台线程,负责处理不同的任务。

Master Thread
Master Thread是一个非常核心的后台线程,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲(INSERT BUFFER)、UNDO页的回收等。2.5节会详细地介绍各个版本中Master Thread的工作方式。
Master Thread具有最高的线程优先级别。其内部由多个循环(loop)组成:主循环(loop)、后台循环(backgrouploop)、刷新循环(flush loop)、暂停循环(suspendloop)。Master Thread会根据数据库运行的状态在loop、background loop、flush loop和suspend loop中进行切换。Loop被称为主循环,因为大多数的操作是在这个循环中,其中有两大部分的操作——每秒钟的操作和每10秒的操作
每秒一次的操作包括:
□ 日志缓冲刷新到磁盘,即使这个事务还没有提交(总是);
□ 合并插入缓冲(可能);
□ 至多刷新100个InnoDB的缓冲池中的脏页到磁盘(可能);
□ 如果当前没有用户活动,则切换到background loop(可能)。
即使某个事务还没有提交,InnoDB存储引擎仍然每秒会将重做日志缓冲中的内容刷新到重做日志文件。这一点是必须要知道的,因为这可以很好地解释为什么再大的事务提交(commit)的时间也是很短的。

IO Thread
在InnoDB存储引擎中大量使用了AIO(Async IO)来处理写IO请求,这样可以极大提高数据库的性能。而IO Thread的工作主要是负责这些IO请求的回调(call back)处理

Purge Thread

事务被提交后,其所使用的undolog可能不再需要,因此需要PurgeThread来回收已经使用并分配的undo页


Page Cleaner Thread

Page Cleaner Thread是在InnoDB 1.2.x版本中引入的。其作用是将之前版本中脏页的刷新操作都放入到单独的线程中来完成。而其目的是为了减轻原Master Thread的工作及对于用户查询线程的阻塞,进一步提高InnoDB存储引擎的性能。

1.2 内存
缓冲池
InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可将其视为基于磁盘的数据库系统(Disk-base Database)。在数据库系统中,由于CPU速度与磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池技术来提高数据库的整体性能。缓冲池简单来说就是一块内存区域,通过内存的速度来弥补磁盘速度较慢对数据库性能的影响在数据库中进行读取页的操作,首先将从磁盘读到的页存放在缓冲池中,这个过程称为将页“FIX”在缓冲池中。下一次再读相同的页时,首先判断该页是否在缓冲池中若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。这里需要注意的是,页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为Checkpoint的机制刷新回磁盘。同样,这也是为了提高数据库的整体性能。综上所述,缓冲池的大小直接影响着数据库的整体性能。
缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲(insert buffer)、自适应哈希索引(adaptive hash index)、InnoDB存储的锁信息(lockinfo)、数据字典信息(data dictionary)等。不能简单地认为,缓冲池只是缓存索引页和数据页,它们只是占缓冲池很大的一部分而已。

从InnoDB 1.0.x版本开始,允许有多个缓冲池实例。每个页根据哈希值平均分配到不同缓冲池实例中。这样做的好处是减少数据库内部的资源竞争,增加数据库的并发处理能力

内存的管理LRU List、Free List和Flush List
数据库中的缓冲池是通过LRU(Latest RecentUsed,最近最少使用)算法来进行管理的。即最频繁使用的页在LRU列表的前端,而最少使用的页在LRU列表的尾端。当缓冲池不能存放新读取到的页时,将首先释放LRU列表中尾端的页。


在InnoDB存储引擎中,缓冲池中页的大小默认为16KB,同样使用LRU算法对缓冲池进行管理。稍有不同的是InnoDB存储引擎对传统的LRU算法做了一些优化。在InnoDB的存储引擎中,LRU列表中还加入了midpoint位置。新读取到的页,虽然是最新访问的页,但并不是直接放入到LRU列表的首部,而是放入到LRU列表的midpoint位置(可以配置)。这个算法在InnoDB存储引擎下称为midpoint insertion strategy。在默认配置下,该位置在LRU列表长度的5/8处。
在InnoDB存储引擎中,把midpoint之后的列表称为old列表,之前的列表称为new列表。可以简单地理解为new列表中的页都是最为活跃的热点数据。参数innodb_old_blocks_pct默认值为37,表示新读取的页插入到LRU列表尾端的37%的位置(差不多3/8的位置)。

那为什么不采用朴素的LRU算法,直接将读取的页放入到LRU列表的首部呢?这是因为若直接将读取到的页放入到LRU的首部,那么某些SQL操作可能会使缓冲池中的页被刷新出,从而影响缓冲池的效率。常见的这类操作为索引或数据的扫描操作。这类操作需要访问表中的许多页,甚至是全部的页,而这些页通常来说又仅在这次查询操作中需要,并不是活跃的热点数据。如果页被放入LRU列表的首部,那么非常可能将所需要的热点数据页从LRU列表中移除,而在下一次需要读取该页时,InnoDB存储引擎需要再次访问磁盘。

为了解决这个问题,InnoDB存储引擎引入了另一个参数来进一步管理LRU列表,这个参数是innodb_old_blocks_time,用于表示页读取到mid位置后需要等待多久才会被加入到LRU列表的热端。因此当需要执行上述所说的SQL操作时,可以通过下面的方法尽可能使LRU列表中热点数据不被刷出。


LRU列表用来管理已经读取的页,但当数据库刚启动时,LRU列表是空的,即没有任何的页。这时页都存放在Free列表中。当需要从缓冲池中分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表中删除,放入到LRU列表中。否则,根据LRU算法,淘汰LRU列表末尾的页,将该内存空间分配给新的页。当页从LRU列表的old部分加入到new部分时,称此时发生的操作为page made young,而因为innodb_old_blocks_time的设置而导致页没有从old部分移动到new部分的操作称为page not made young。因为缓冲池中的页还可能会被分配给自适应哈希索引、Lock信息、Insert Buffer等页,而这部分页不需要LRU算法进行维护,因此不存在于LRU列表中。


在LRU列表中的页被修改后,称该页为脏页(dirty page),即缓冲池中的页和磁盘上的页的数据产生了不一致。这时数据库会通过CHECKPOINT机制将脏页刷新回磁盘,而Flush列表中的页即为脏页列表。需要注意的是,脏页既存在于LRU列表中,也存在于Flush列表中。LRU列表用来管理缓冲池中页的可用性,Flush列表用来管理将页刷新回磁盘,二者互不影响。


重做日志缓冲

InnoDB存储引擎的内存区域除了有缓冲池外,还有重做日志缓冲(redo log buffer)。InnoDB存储引擎首先将重做日志信息先放入到这个缓冲区,然后按一定频率将其刷新到重做日志文件。重做日志缓冲一般不需要设置得很大,因为一般情况下每一秒钟会将重做日志缓冲刷新到日志文件,因此用户只需要保证每秒产生的事务量在这个缓冲大小之内即可。
在通常情况下,8MB的重做日志缓冲池足以满足绝大部分的应用,因为重做日志在下列三种情况下会将重做日志缓冲中的内容刷新到外部磁盘的重做日志文件中。
□ Master Thread每一秒将重做日志缓冲刷新到重做日志文件;
□ 每个事务提交时会将重做日志缓冲刷新到重做日志文件;
□ 当重做日志缓冲池剩余空间小于1/2时,
重做日志缓冲刷新到重做日志文件。


 额外的内存池
在InnoDB存储引擎中,对内存的管理是通过一种称为内存堆(heap)的方式进行的。在对一些数据结构本身的内存进行分配时,需要从额外的内存池中进行申请,当该区域的内存不够时,会从缓冲池中进行申请。例如,分配了缓冲池(innodb_buffer_pool),但是每个缓冲池中的帧缓冲(frame buffer)还有对应的缓冲控制对象(buffercontrol block),这些对象记录了一些诸如LRU、锁、等待等信息,而这个对象的内存需要从额外内存池中申请。因此,在申请了很大的InnoDB缓冲池时,也应考虑相应地增加这个值。

1.3 Checkpoint技术
缓冲池的设计目的为了协调CPU速度与磁盘速度的鸿沟。因此页的操作首先都是在缓冲池中完成的。如果一条DML语句,如Update或Delete改变了页中的记录,那么此时页是脏的,即缓冲池中的页的版本要比磁盘的新。数据库需要将新版本的页从缓冲池刷新到磁盘。

倘若每次一个页发生变化,就将新页的版本刷新到磁盘,那么这个开销是非常大的。若热点数据集中在某几个页中,那么数据库的性能将变得非常差。同时,如果在从缓冲池将页的新版本刷新到磁盘时发生了宕机,那么数据就不能恢复了。为了避免发生数据丢失的问题,当前事务数据库系统普遍都采用了Write AheadLog策略,即当事务提交时,先写重做日志,再修改页。当由于发生宕机而导致数据丢失时,通过重做日志来完成数据的恢复。这也是事务ACID中D(Durability持久性)的要求。

思考下面的场景,如果重做日志可以无限地增大,同时缓冲池也足够大,能够缓冲所有数据库的数据,那么是不需要将缓冲池中页的新版本刷新回磁盘。因为当发生宕机时,完全可以通过重做日志来恢复整个数据库系统中的数据到宕机发生的时刻。但是这需要两个前提条件:
□ 缓冲池可以缓存数据库中所有的数据;
□ 重做日志可以无限增大。
对于第一个前提条件,有经验的用户都知道,当数据库刚开始创建时,表中没有任何数据。缓冲池的确可以缓存所有的数据库文件
重做日志可以无限增大。也许是可以的,但是这对成本的要求太高,同时不便于运维
宕机后数据库的恢复时间。当数据库运行了几个月甚至几年时,这时发生宕机,重新应用重做日志的时间会非常久,此时恢复的代价也会非常大。


因此Checkpoint(检查点)技术的目的是解决以下几个问题:
□ 缩短数据库的恢复时间;
□ 缓冲池不够用时,将脏页刷新到磁盘;
□ 重做日志不可用时,刷新脏页。
当数据库发生宕机时,数据库不需要重做所有的日志,因为Checkpoint之前的页都已经刷新回磁盘。故数据库只需对Checkpoint后的重做日志进行恢复。这样就大大缩短了恢复的时间。
当缓冲池不够用时,根据LRU算***溢出最近最少使用的页,若此页为脏页,那么需要强制执行Checkpoint,将脏页也就是页的新版本刷回磁盘
重做日志出现不可用的情况是因为当前事务数据库系统对重做日志的设计都是循环使用的,并不是让其无限增大的,这从成本及管理上都是比较困难的。重做日志可以被重用的部分是指这些重做日志已经不再需要,即当数据库发生宕机时,数据库恢复操作不需要这部分的重做日志,因此这部分就可以被覆盖重用。若此时重做日志还需要使用,那么必须强制产生Checkpoint,将缓冲池中的页至少刷新到当前重做日志的位置。


对于InnoDB存储引擎而言,其是通过LSN(Log SequenceNumber)来标记版本的。而LSN是8字节的数字,其单位是字节。每个页有LSN,重做日志中也有LSN,Checkpoint也有LSN

在InnoDB存储引擎中,Checkpoint发生的时间、条件及脏页的选择等都非常复杂。而Checkpoint所做的事情无外乎是将缓冲池中的脏页刷回到磁盘。不同之处在于每次刷新多少页到磁盘,每次从哪里取脏页,以及什么时间触发Checkpoint。在InnoDB存储引擎内部,有两种Checkpoint,分别为:
□ Sharp Checkpoint 
    Sharp Checkpoint发生在数据库关闭时将所有的脏页都刷新回磁盘,这是默认的工作方式。若数据库在运行时也使用Sharp Checkpoint,那么数据库的可用性就会受到很大的影响。
□ Fuzzy Checkpoint
    Fuzzy Checkpoint进行页的刷新,即只刷新一部分脏页,而不是刷新所有的脏页回磁盘。 这里又可以进行细分,刷新根据时间或者空闲资源、或者重做日志文件不可用的时候等等
            Master Thread(2.5节会详细介绍各个版本中MasterThread的实现)中发生的Checkpoint,差不多以每秒或每十秒的速度从缓冲池的脏页列表中刷新一定比例的页回磁盘。这个过程是异步的,即此时InnoDB存储引擎可以进行其他的操作,用户查询线程不会阻塞。
            FLUSH_LRU_LIST Checkpoint是因为InnoDB存储引擎需要保证LRU列表中需要有差不多100个空闲页可供使用。在InnoDB1.1.x版本之前,需要检查LRU列表中是否有足够的可用空间操作发生在用户查询线程中,显然这会阻塞用户的查询操作。倘若没有100个可用空闲页,那么InnoDB存储引擎会将LRU列表尾端的页移除。如果这些页中有脏页,那么需要进行Checkpoint,而这些页是来自LRU列表的,因此称为FLUSH_LRU_LIST Checkpoint。这个检查被放在了一个单独的Page Cleaner线程中进行,并且用户可以通过参数innodb_lru_scan_depth控制LRU列表中可用页的数量,该值默认为1024
            Async/Sync Flush Checkpoint指的是重做日志文件不可用的情况,这时需要强制将一些页刷新回磁盘,而此时脏页是从脏页列表中选取的。若将已经写入到重做日志的LSN记为redo_lsn,将已经刷新回磁盘最新页的LSN记为checkpoint_lsn。Async/Sync Flush Checkpoint是为了保证重做日志的循环使用的可用性。这部分的刷新操作同样放入到了单独的Page Cleaner Thread中,故不会阻塞用户查询线程。


1.4
InnoDB存储引擎的关键特性包括:
□ 插入缓冲(Insert Buffer)
在InnoDB存储引擎中,主键是行唯一的标识符。通常应用程序中行记录的插入顺序是按照主键递增的顺序进行插入的。因此,插入聚集索引(Primary Key)一般是顺序的,不需要磁盘的随机读取。
□ 两次写(Double Write)
□ 自适应哈希索引(Adaptive Hash Index)
□ 异步IO(Async IO)
□ 刷新邻接页(Flush Neighbor Page)