1. MySQL体系结构
从概念上讲,数据库是文件的集合,是依照某种数据模型组织起来并存放于二级存储器中的数据集合;数据库实例是程序,是位于用户与操作系统之间的一层数据管理软件,用户对数据库数据的任何操作,包括数据库定义、查询、维护、运行控制等都是在数据库实例下进行的,应用程序只有通过数据库实例才能和数据库打交道。
MySQL的体系结构如上图所示,主要由以下部分组成:
连接池组件
管理服务和工具组件
SQL接口组件
查询分析器组件
优化器组件
缓冲组件
插件式存储引擎
物理文件
InnoDB存储引擎,设计目标是面向在线事务处理(Online Transaction Processing,OLTP),特点是支持事务、行锁设计、支持外键,支持非锁定读,即默认读取操作不会产生锁,现在MySQL的默认引擎是InnoDB。
MyISAM存储引擎,不支持事务、支持全文索引,主要面向一些OLAP数据库应用。
2. InnoDB存储引擎
关键特性:
插入缓冲(Insert Buffer),当索引是辅助索引(secondary index)和索引不是唯一索引的时候,就会使用插入缓冲。对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引是否在缓冲池中,若在则直接插入;如偶在,则先放入到一个Insert Buffer中。数据结构是一棵B+树。
两次写(Double Write),解决了部分写失效(partial write)问题。由内存中的doublewrite buffer和磁盘上共享表空间中连续的128个页(2个区(extent))组成,两个部分的大小都是2M。
自适应哈希索引(Adaptive Hash Index),引擎会监控对表上个索引页的查询,如果观察到建立哈希索引可以带来查询速度的提升,就会建立哈希索引,因此叫做自适应哈希索引(AHI)。
异步IO(Asynchronous IO,AIO),目的是为了提高磁盘操作性能。Sync IO和Async IO,前一个得等到一个IO操作完成才会进行下一个IO操作,而AIO则是发出一个IO请求后会继续发出下一个IO请求,发送完所有请求后等所有IO操作的完成。
刷新邻接页(Flush Neighbor Page),当属新一个脏页时,引擎会检测改业所在区的所有页,如果是脏页,则进行一起刷新,通过AIO将多个IO进行合并为一个IO,在机械硬盘中有优势,在SSD中则me这个必要,可以通过设置参数innodb_flush_neighbors为0关闭该特性。
3. 文件
文件包括MySQL数据库和InnoDB存储引擎表的一部分文件。
参数文件:MySQL实例启动时在哪里可以找到数据文件,并指定某些初始化参数,这些参数定义了某种内存结构的大小等设置;
日志文件:记录MySQL实例对某种条件作出响应时写入的文件,如错误日志文件、二进制日志文件、慢查询日志文件、查询日志文件等;
socket文件:当用UNIX域套接字方式进行连接时需要的文件;
pid文件:MySQL实例的进程ID文件;
MySQL表结构文件:用来存放MySQL表结构定义的文件;
存储引擎文件:每个存储引擎都会有自己的文件用来保存各种数据。这些存储文件存储了记录和索引的数据。
日志文件
错误日志:MySQL的启动、运行、关闭过程进行记录。从中可以得到一些关于数据库优化等等信息。
慢查询日志:定位可能存在问题的SQL语句,从SQL语句层面进行优化。比如可以把运行时间超过某个阈值的SQL语句记录到慢查询日志文件中,然后每过一段时间对其检查是否需要对语句进行优化。阈值可以通过参数long_query_time设置。
1. 当某条SQL语句的时间刚好为阈值的值的时候是不会被记录的,只记录大于long_query_time的语句。
2. 现在(版本5.1开始)long_query_time的值一般是一微秒为值。
查询日志:记录了所有对MySQL数据库请求的信息,无论这些请求是否得到了正确的执行,默认文件名为:主机名.log。
二进制日志:记录了所有对MySQL数据库执行更改的操作,只有更改数据库的操作才进行记录,因此select、show等操作是没有的。如果是update,尽管没有更改也会记录。主要作用有:恢复数据库、复制数据库、审计(对二进制日志中的信息进行审计,判断是否有对输入库的注入攻击)。
4. 表
主要是InnoDB存储引擎表的逻辑存储及实现。
4.1 索引组织表(Index Organized Table)
在InnoDB存储引擎中,表都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表。在InnoDB存储引擎表中,每张表都有个主键(primary key),如果在创建表时没有显示的定义主键,则InnoDB存储引擎会根据如下方式选择或创建主键:
首先判断表中是否有非空唯一索引(Unique NOT NULL),如果有,则该列为主键;
如果没有,InnoDB存储引擎会自动创建一个6字节大小的指针作为主键。
当表中有多个非空唯一索引时,引擎会选择建表时第一个定义的非空唯一索引为主键。注:主键的选择根据的是定义索引的顺序,而不是建表时列的顺序。
4.2 InnoDB逻辑存储结构
从InnoDB存储引擎逻辑存储结构看,所有数据都放在一个空间中,成为表空间(tablespace)。表空间由段(segment)、区(extent)、页(page,有的也叫块block)组成。
InnoDB存储引擎逻辑结构如上图。
表空间可以看做是InnoDB存储引擎逻辑结构的最高层,所有的数据都存放在表空间中。表空间中的段由各个部分组成,常见的有数据段、索引段、回滚段等等,因为InnoDB存储引擎表时索引组织,因此数据即索引,索引即数据,所以数据段为B+树的叶子结点(图中的Leaf node segment),索引为B+树的非叶子结点(图中的Non-leaf node segment)。段中的区是由连续的页组成,在任何情况下每个区的大小都为1MB,默认的页大小为16KB,因此一个区有64个连续的页,因此为了保证区中页的连续性,引擎一次会从磁盘申请4~5个区。
4.3 约束机制
数据完整性,关系型数据库本身保证了存储数据的完整性。通过Primary Key或Unique Key约束来保证完整性。还可以编写触发器保证数据的完整性。
约束的创建和查找,可以建表时就进行约束定义,或是利用Alter Table命令创建约束。
约束和索引的区别,创建了一个唯一索引就创建了一个唯一的约束。约束是一个逻辑概念,用来保证数据的完整性;而索引则是一个数据结构,既有逻辑上的概念,也代表了数据库中的物理存储方式。
对错误数据的约束,如向NOT NULL的字段插入一个NULL值,MySQL数据库会将其更改为0在进行插入。
外键约束,用来保证参照完整性,关键字为foreign key。
4.4 视图
MySQL数据库中,视图(View)是一个命名的虚表,由一个SQL查询来定义,可以当做表使用。与持久表(permanent table)不同的是,视图中的数据没有实际的物理存储。
作用:1)被用作一个抽象装置,可以起到一个安全层的作用。某些应用程序本身不关心基表(base table)的结构,只是按照视图定义来取数据或更新数据,即通过视图来更新基本表。
5. 索引与算法
索引太多,影响性能;索引太少,也影响性能。
InnoDB存储引擎支持的几种常见索引:
B+树索引
全文索引
哈希索引
注:B+树中的B不是代表二叉(binary),而是代表平衡(balance),因为B+树是从最早的平衡二叉树演化而来,并且B+树不是二叉树。
B+树索引并不能找到一个给定键值的具体行,智能查到数据行所在的页,然后把页读入到内存,再从内存中进行查找,最后得到查找的数据。
5.1 数据结构与算法
关于索引中的B+树数据结构和相关算法,如下链接。
因为每页的数据是按照主键的顺序存放,因此在内存中的查询就是通过对页的二分查找而获取。
5.2 B+树索引
B+树索引的本质就是B+树在数据库中的实现。在数据库中B+树的高度一般是在2~4层,即查找某一个键值的行记录时最多只需要2~4次IO。
MySQL中的B+树索引分为聚集索引(clustered index)和辅助索引(secondary index),两个索引的内部实现都是B+树 ,叶子结点存放所有的数据。两个索引的区别是:叶子结点存放的数据是否是一整行数据信息。
聚集索引:InnoDB存储引擎表是索引组织表,即表中数据按照主键顺序存放,聚集索引就是按照每张表的主键构造一棵B+树,同时叶子节点中存放的为整张表的行记录数据,另外也将聚集索引的叶子节点称为数据页。因此索引组织表中的数据也是索引的一部分,么个索引页都通过一个双向链表进行链接。每张表只有一个聚集索引。
辅助索引:辅助索引也称非聚集索引,叶子结点并不包含行记录的全部数据。叶子结点除了包含键值之外,每个叶子节点中的索引行还包含一个书签(bookmark),概述前用来高数引擎那里可以找到与索引相对应的行数据,因此辅助索引的书签就是相应行数据的聚集索引键。辅助索引不影响数据在聚集索引中的组织,每张表上可以有多个辅助索引。通过辅助索引来寻找数据时,InnoDB存储引擎会遍历辅助索引并通过叶子级别的指针来获得指向主键索引的主键,然后通过主键索引来找到一个完整的行记录。
B+树索引的使用
联合索引
指对表上的多个列进行索引。本质也是一棵B+树,不同的地方在于键值数量大于等于2。
联合索引(a, b)首先是根据a,b进行排过序的,并且查询键值(a),(a, b)都可以使用联合索引(a, b),但是(b)则不能使用联合索引。
覆盖索引
也称索引覆盖,即从辅助索引中可以查询的记录,就不需要查询聚集索引中的记录,辅助索引不包含整行的信息,大小会小于句句索引,可以减少IO操作。
6. 锁
InnoDB存储引擎不需要所升级,因为一个锁和多个锁的开销是相同的。
InnoDB的3中行锁算法:
Record Lock: 锁定单个行记录;
Gap Lock: 锁定一个范围,并且不锁定记录本身;
Next-Key Lock: Gap Lock + Record Lock,锁定一个范围,并且锁定记录本身。
lock与latch的区别
InnoDB实现的 两种标准的行级锁
共享锁(S Lock),允许事务读一行数据
排它锁(X Lock),允许事务删除或更新一行数据
InnoDB存储引擎支持多粒度锁定,这种锁定允许事务在行级上的锁和表级上的锁同时存在。若要对最细粒度的对象进行上锁,那么首先要对粗粒度的对象上锁。如需要对也上的r行进行上X锁,分别需要对数据库、表、页上锁,最后对行r上X锁。
一致性非锁定读(consistent nonlocking read),指InnoDB通过多版本并发控制(Multi Version Concurrency Control,MVCC)的方式读取当前执行时间数据库中的行数据,即如果读取行正在执行DELETE或UPDATE操作,这时的读操作不会去等待行锁的释放,而是去读取行数据的一个快照数据。
快照数据:指该行之前版本的数据,实现通过undo段完成,undo用来在事务中进行回滚数据。
在InnoDB存储引擎默认设置下,一致性非锁定读是默认的读取方式。
在事务隔离级别中:
RREAD COMMITTED中,使用一致性非锁定读,读取被锁定行的最新一份快照数据。
REPEATABLE READ(InnoDB默认事务隔离级别),使用一致性非锁定读,读取事务开始时的行数据。
一致性锁定读,对于语句加锁,即使是SELECT的只读操作。SELECT语句有两种一致性锁定读(locking read)操作:
SELECT...FOR UPDATE,对读取行加X锁,后续事务不能对锁定的行再加上任何所;
SELECT...LOCK IN SHARE MODE,对读取行加S锁,其他事物可以对该行继续加S锁,当不能加X锁,不然会被阻塞。
锁引发的几个问题:
1. 脏读
脏数据:指事务对缓冲池中行记录的修改,并且修改后还没有被提交。
脏页:指在缓冲池已经被修改的页,但还没有刷新到磁盘中,即数据库实例内存中的页和磁盘中的页的数据不一致,在刷新到磁盘之前,日志已经写到重做日志文件中。主要因为数据库实例内存和磁盘的异步造成的,不影响数据的一致性。
脏读:在不同的事务下,当前事务读取到另外事务未提交的数据。一般不发生,因为该种操作需要的事务隔离级别为READ UNCOMMITTED,但是一般情况下至少都是READ COMMITTED级别,而InnoDB的默认级别是READ REPEATABLE级别。违反了数据库的隔离性。
2. 不可重复读(幻读)
指在一个事务内多次读取同一数据集合的时候,在该事务还没有结束前,另外一个事务也访问了同一数据集合并作了DML操作,结果第一个事务的两次读取结果不同。违反了数据库事务一致性的要求。InnoDB的READ REPEATABLE通过Next-Key Lock算法,避免了该情况。
与脏读的区别是:脏读读取的是未提交的数据,不可重复读读取的是已经提交的数据。
3. 丢失更新
另一个锁导致的问题,即一个事务的更新操作被另一个事务的更新操作所覆盖,导致数据的不一致。
几个事务隔离级别都不会导致丢失更新的问题。
但是有另一种逻辑意义上的丢失更新问题:
1)事务T1查询一行记录,显示给USER1;
2)事务T2查询同一行记录,显示给USER2;
3)USER1修改这行记录,更新数据库并提交;
4)USER2修改这行记录,更新数据库并提交。
如银行账户有1000块,T1转了900,因为网路和数据关系,需要等待;此时T2操作该账户转走1元,当量比操作都成功的时候,账户剩下的不是99元,而是999元,操作T2将T1覆盖。
因此避免丢失更新发生,让事务在该种情况下的操作变成串行化,而不是并行化。即在操作1)中加一个排它锁X锁,2)中也加一个排它锁X锁,那么2)会等1)提交后才会继续读取。
死锁,指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。
一种是通过超时机制解决,根据FIFO顺序选择回滚操作;更普遍的是采用等待图(wait-for graph)的方式进行死锁检测,通过数据库保存的锁的信息链表和事务等待链表可以构造出一张图,当图中存在回路的时候,就代表存在死锁,这时InnoDB选择回滚undo量最小的事务。
所升级,指将当前锁的粒度降低。比如将一个表的1000个行锁升级为一个页锁,或将页锁升级为表锁。
7. 事务
事务会把数据库从一种一致状态转换为另一种一致状态。
事务符合ACID特性:
原子性(Atomicity)——redo log
一致性(Consistency)——undo log
隔离性(Isolation)——锁
持久性(Durability)——redo log
redo恢复提交事务修改的页操作,undo回滚行记录到某个特定版本。
redo一般是物理日志,记录页的物理修改操作;undo是逻辑日志,根据每行记录进行行记录。
参照《MySQL技术内幕——InnoDB存储引擎》第2版所写。