本文概述了MySQL的服务器架构、各种存储引擎之间的主要区别,以及这些区别的重要性。
MySQL逻辑架构整体分为三层
>最上层并非MySQL所独有,主要进行如连接处理、授权认证、安全等功能的处理。
>MySQL大多数核心服务均在第二层架构,包括查询解析、分析、优化、缓存、内置函数(如日期、时间、数学和加密等函数)。所有的跨存储引擎的功能也在这一层实现:存储过程、触发器、视图等。
>最下层为存储引擎,负责MySQL中的数据存储和提取。每种存储引擎都有其优势和劣势。服务器通过API与存储引擎进行通信,这些API接口屏蔽了不同存储引擎间的差异。存储引擎API包含几十个底层函数用于执行,但不会去解析SQL(InnoDB是个例外,他会解析外键定义,因为MySQL服务器没有实现该功能);不同引擎只会简单的响应上层服务器的请求,而不会相互通信。
对于存储引擎,本文以及之后的文章只对MyISAM和InnoDB进行探究。
1.连接管理与安全性
对于每个客户端连接,服务器都会在进程中新建一个线程处理(如果是线程池的话,则是分配一个空的线程),这个连接的查询只会在这个单独的线程中执行(每个线程相互独立),该线程只能轮流在某个CPU核心(多核CPU)或者CPU中运行。服务器会负责缓存线程,因此不需要为每个新建的连接创建或者销毁线程(线程的重用和销毁都由服务器控制)。
当客户端连接到MySQL服务器时,服务器需要对其进行认证,如基于用户名、原始主机信息和密码;一旦连接成功,服务器会继续验证客户端是否具有执行某个特定查询的权限。
2.优化与执行
MySQL会解析查询,并创建内部数据结构(解析树),然后对其进行各种优化,包括重写查询、决定表的读取顺序,以及选择合适的索引等。
优化器并不关心表使用的是什么存储引擎,但存储引擎对于优化查询查询是有影响的。优化器会请求存储引擎提供容量或某个具体操作的开销信息,以及表数据的统计信息等。
对于SELECT语句,服务器会优先查询缓存(Query Cache)。如果有缓存就直接返回缓存中的结果集,否则就执行查询解析、优化和执行的整个过程。
具体优化措施我们之后再进行探讨。
3.并发控制
无论何时,只要有多个查询需要在同一时刻修改数据,都会产生并发控制的问题。在处理并发读或者写的时候,可以通过实现一个由两种类型的锁组成的锁系统来解决问题。这两种类型的锁通常被称为共享锁(读锁)和排它锁(写锁)。
读锁是共享的,多个客户在同一时刻可以同时读取一个资源,互不干扰;而写锁是排他的,也就是说一个写锁会阻塞其他的写锁和读锁,这样才能保证数据安全。
一种提高共享资源并发性的方式就是让锁定对象更有选择性,尽量只锁定需要修改的部分数据而不是所有的资源。在给定的资源上,锁定的数据量越少,则系统的并发程度越高,只要相互之间不发生冲突即可。
但加锁也需要消耗资源,如果花费大量时间和资源来管理所而不是存储数据,那就得不偿失了。所以需要一种锁策略,在锁的开销和数据的安全性之间寻求平衡。
MySQL的每种存储引擎都可以实现自己的锁策略,其中有两种最重要的锁策略:表锁和行锁。
表锁是MySQL中最基本的锁策略,并且是开销最小的策略,他会锁定整张表;在特定场景中表锁可以有良好的性能。另外写锁也比读锁有更高的优先级,一个写锁请求可能会被插到读锁队列的前面。
行锁可以***程度的支持并发,但同时开销也是***,在InnoDB中实现的就是行锁(在存储引擎层实现)。
4.MySQL的InnoDB引擎支持事务
有关事务的描述可以参考《MyBatis:Spring事务管理(十一)》
MySQL服务器层不管理事务,事务是由下层存储引擎实现的。所以在同一个事务中,使用多种存储引擎是不可靠的。
InnoDB采用的是两阶段锁定协议,即在事务执行过程中,随时都可以执行锁定,锁只有在执行COMMIT或者ROLLBACK的时候才会释放,并且所有的锁是在同一时刻被释放的,InnDB会根据隔离级别在需要的时候自动加锁。
5.多版本并发控制
MySQL的大多数事务性存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,他们一般都同时实现了多版本并发控制(MVCC),他可以认为是行级锁的一种变种,在很多情况下避免了加锁操作,所以开销更低。
MVCC的实现,是通过保存数据在某个时间点的快照来实现的,不管需要执行多长时间,每个事务看到的数据都是一致的。根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。
下面我们通过InnoDB的简化版行为来说明MVCC是如何工作的。
InnoDB的MVCC是通过在每行记录后面保存两个隐藏列来实现:一个保存了行的创建时间,另一个保存行的过期时间(并不是实际时间值,而是系统版本号)。每开始一个新的事务,系统版本号就会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。当在默认可重复读隔离级别下时:
SELECT:InnoDB会根据以下两个条件检查每行记录:
>InnoDB只查找版本早于当前事务版本的数据行,这样可以确保事务读取的行,要么是在事务开始之前已经存在的,要么是事务自身插入或者修改过的。
>行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。
只有符合上述两个条件的记录,才能返回作为查询结果。
INSERT:InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
DELETE:为删除的每一行保存当前系统版本号作为行删除标识。
UPDATE:InnoDB为插入一行新纪录,保存当前版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。
保存了这两个额外系统版本号,可以使大多数读操作都可以不用加锁,使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作以及一些额外的维护工作。
MVCC只在可重复读和读写提交两个隔离级别下工作。读未提交下总是读取***的数据行,而不是符合当前事务版本的数据行;而序列化则会对所有读取的行都是加锁,所以这两个隔离级别与MVCC不兼容。
6.InnoDB存储引擎
InnoDB的数据存储在表空间(tablespace)中,表空间是由InnoDB管理的一个黑盒子,由一系列的数据文件组成。在MySQL4.1后,InnoDB可以将每个表的数据和索引存放在单独的文件中。
InnoDB采用MVCC来支持高并发,并且实现了四个标准的隔离界别,默认是可重复读,并且通过间隙锁策略防止幻读的出现。间隙锁使得InnoDB不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定,防止幻影行的插入。
InnoDB表是基于聚簇索引建立的(后面再详细介绍),对主键查询有很高的性能。不过他的二级索引(非主键索引)中必须包含主键列,所以如果主键列很大的话,其他的所有索引都会很大。
InnoDB内部做了很多优化,包括从磁盘读取时间时采用的可预测性预读,能够自动在内存中创建hash索引以加速读操作的自适应哈希索引,以及能够加速插入操作的插入缓冲区等,这些之后再具体分析其实现。同时作为事务型的存储引擎,InnoDB通过一些机制和工具支持真正的热备份。
7.MyISAM存储引擎
在MySQL5.1及之前的版本MyISAM是默认的存储引擎,他提供了大量的特性如全文索引、压缩、空间函数等,但他不支持事务和行级锁,而且崩溃后无法安全恢复。对于只读的数据,或者表比较小、可以忍受修复操作的,依然可以继续使用。
>加锁与并发:MyISAM对整张表加锁,读取时会对需要读到的所有表加共享锁,写入时加排它锁。但在表有读取查询的同时,也可以往表中插入新的记录(并发插入)。
>修复:对于MyISAM表,可以手动或者自动执行检查和修复工作,但会造成一些数据丢失,而且修复操作很慢。
>索引特性:对于MyISAM即使是BLOB和TEXT字段也可以基于前500个字符创建索引。他也支持全文索引(基于分词创建的索引),可以支持复杂的查询。
>延迟更新索引键:在创建表时,如果指定了DELAY_KEY_WRITE,在每次修改执行完成时,不会立刻将修改的数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这样可以极大提升写入性能,但遇到数据库或服务器崩溃时会造成索引损坏。
如果表创建并导入数据行不会再进行修改操作,这时可以采用MyISAM压缩表(myisampack)。这样可以极大减少磁盘空间占用、减少磁盘I/O,从而提升查询性能。压缩表支持索引,但索引也都是只读。