MySQL学习笔记之MySQL架构

MySQL 最重要、最不同凡响的特性是它的存储引擎架构,这种架构的设计将查询处理及其余系统任务和数据的存储/提取相分离。这种处理和存储分离的设计能够在使用时根据性能、特性,以及其余需求来选择数据存储的方式。git

MySQL 的逻辑架构

MySQL 逻辑架构图github

最上层的服务并非MySQL 所独有的,大多数基于网络的客户端/服务器的工具或者服务都有相似的架构。好比链接处理、受权验证、安全等。数据库

第二层架构是MySQL 比较有意思的部分。大多数MySQL的核心服务功能都在这一层,包括查询解析、分析、优化、缓存以及全部的内置函数(例如,日期、时间、数学和加密函数),全部跨存储引擎的功能都在这一层实现:存储过程、触发器、视图等。缓存

第三层包含了存储引擎。存储引擎负责MySQL中数据的存储和提取。和GNU/Linux下的各类文件系统同样,每一个存储引擎都有它的优点和劣势。服务器经过API于存储引擎进行通讯。这些接口屏蔽了不一样存储引擎之间的差别,使得这些差别对上层的查询过程透明。存储引擎API包含了几十个底层函数,用于执行诸如“开始一个事务”或者“根据主键提取一行记录”等操做。但存储引擎不会去解析SQL,不一样存储引擎之间也不会相互通讯,而只是简单地响应上层服务器的请求。安全

链接管理与安全性

每一个客户端链接都会在服务器进行中拥有一个线程,这个链接的查询只会在这个单独的线程中执行,该线程只能轮流在某个CPU核心或者CPU中运行。服务器会负责缓存线程,所以不须要为每个新建的链接建立或者销毁线程。服务器

当客户端(应用)链接到MySQL 服务器时,服务器须要对其进行认证。认证基于用户名、原始主机信息和密码。若是使用了安全套接字(SSL)的方式链接,还可使用X.509证书认证。一旦客户端链接成功,服务器会继续认证该客户端是否具备执行某个特定查询对方的权限(例如,是否容许客户端对world数据库的Country表执行SELECT语句)。网络

优化与执行

MySQL 会解析查询,并建立内部数据结构(解析树),而后对其进行各类优化,包括重写查询、决定表的读写顺序,以及选择合适的索引等。用户能够经过特殊的关键字提示(hint)优化器,影响它的决策过程。也能够请求优化解释器(explain)优化过程的各个因素,使用户能够知道服务器是如何进行优化决策的,并提供一个参考基准,便于用户重构查询和schema、修改相关配置,使应用尽量高效运行。数据结构

优化器并不关心表使用的是什么存储引擎,但存储引擎对优化查询是有影响的。优化器会请求存储引擎提供容量或某个操做的开销信息,以及表数据的统计信息等。例如,某些存储引擎的某种索引,可能对一些特定的查询有优化。架构

对于SELECT语句,在解析查询以前,服务器会线检查查询缓存(Query Cache),若是可以在其中找到对应的查询,服务器就没必要再执行查询解析、优化和执行的整个过程,而是直接返回查询缓存中的结果集。并发

并发控制

不管什么时候,只要有多个查询须要在同一时刻修改数据,都会产生并发控制的问题。

以Unix 系统的email box为例,典型的mbox文件格式是很是简单的。一个mbox邮箱中全部的邮件都串行在一块儿,彼此首位相连。这种格式对于读取和分析邮件信息很是友好,同时投递邮件也很容易,只要在文件末尾加新的邮件内容便可。

但若是两个进程在同一时刻对同一个邮箱投递邮件,会发生什么状况?显然,邮箱的数据会被破坏,两封邮件的内容会交叉地附加在邮箱文件的莫问。设计良好的邮箱投递系统会经过锁(lock)来防止数据损坏。若是客户驶入投递邮件,而邮箱已经被其余客户锁住,那就必须等待,直到锁释放才能进行投递。

这种锁的方案在实际应用环境中虽然工做良好,但并不支持并发处理。由于在任意一个时刻,只有一个进程能够修改邮箱数据,这在大容量的邮箱系统中是个问题。

读写锁

从邮箱中读取数据没有这样的麻烦,即便同一时刻多个用户并发读取也不会有什么问题。由于读取不会修改数据,因此不会出错。但若是某个客户正在读取邮箱,同时另外一个用户试图删除编号为25的邮件,会产生什么结果?结论是不肯定,读的客户可能会报错退出,也可能读到不一致的邮箱数据。因此,为安全起见,即便读取邮箱也须要特别注意。

若是把上述的邮箱当成数据库中的一张表,把邮件当成表中的一行记录,就很容易看出,一样的问题依然存在。从不少方面来讲,邮箱就是一张简单的数据库表。修改数据库表中的记录,和删除或者修改邮箱中的邮件信息,十分相似。

解决这类经典问题的方法就是并发控制,其实很是简单。在处理并发读或者写时,能够经过实现一个由两种类型的锁组成的锁系统来解决问题。这两种类型的锁一般被称为共享锁(shared lock)和排他锁(exclusive lock),也叫读锁(read lock)和写锁(write lock)。

锁的概念以下:读锁是共享的,或者说是相互不阻塞的。多个客户在同一时刻能够同时读取同一个资源,而互不干扰。写锁则是排他的,也就是说一个写锁会阻塞其余的写锁和读锁,这是处于安全策略的考虑,只有这样,才能确保在给定的时间里,只有一个用户能执行写入,并防止其余用户读取正在写入的同一资源。

在实际的数据库系统中,每时每刻都在发生锁定,当某个用户在修改某一部分数据时,MySQL 会经过锁定防止其余用户读取同一数据。大多数时候,MySQL 锁的内部管理都是透明的。

锁粒度

一种提升共享资源并发性的方式就是让锁定对象更有选择性。尽可能只锁定须要修改的部分数据,而不是全部的资源。更理想的方式是,只对会修改的数据片进行精确的锁定。任什么时候候,在给定的资源上,锁定的数据量越少,则系统的并发程度越高,只要相互之间不发生冲突便可。

问题是加锁也须要消耗资源。锁的各类操做,包括得到锁、检查锁是否已经解除、释放锁等,都会增长系统的开销。若是系统话费大量的时间来管理锁,而不是存取数据,那么系统的性能可能会所以受到影响。

所谓的锁策略,就是在锁的开销和数据安全性之间寻求平衡,这种平衡固然也会影响到性能。

MySQL 数据库提供了多种选择。每种MySQL存储引擎均可以实现本身的锁策略和锁粒度。在存储引擎设计中,锁管理是个很是重要的决定。将锁粒度固定在某个级别,能够为某些特定的应用场景提供更好的性能,但同时会失去对另一些应用场景的良好支持。

表锁(table lock)

表锁是 MySQL中最基本的锁策略,而且是开销最小的策略。表锁很是相似于前文中描述的邮箱加锁机制:它会锁定整张表。一个用户对表进行写操做(插入、删除、更新等)前,须要先得到写锁,这回阻塞其余用户对该表的全部读写操做。只有没有写锁时,其余读取的用户才能得到读锁,读锁之间是不互相阻塞的。

在特定的场景中,表锁也可能有良好的性能。例如,READ LOCK 表锁支持某些类型的并发写操做。另外,写锁也比读锁有更高的优先级,所以一个写锁的请求可能会被插入到读锁队列的前面(写锁能够插入到锁队列的前面,反之读锁则不能插入到写锁的前面)。

进过存储引擎能够管理本身的锁,MySQL 自己仍是会使用各类有效的表锁来实现不一样的目的。例如,服务器会为诸如ALTER TABLE之类的语句使用表锁,而忽略存储引擎的锁机制。

行级锁(row lock)

行级锁能够最大成都的支持并发处理(同时也带来了最大的开销)。众所周知,在InnoDB和XtraDB,以及其余一些存储引擎中实现了行级锁。行级锁只在存储引擎层实现,而MySQL服务器没有实现。服务器层彻底不了解存储引擎中的锁实现。

事务

事务就是一组原子性的SQL查询,或者说是一个独立的工做单元。若是数据库存储引擎可以成功地对接数据库应用该组查询的所有语句,那么就执行该组查询。若是其中有任何一条语句由于崩溃或者其余缘由没法执行,那么全部的语句都不会执行。也就是说,事务内的语句,要么所有执行成功,要么所有执行失败。

原子性(atomicity)

一是个事务必须被视为一个不可分割的最小工做单元,整个事务中的全部操做要么所有提交成功,要么所有失败回滚,对于一个事务来讲,不可能只执行其中的一部分操做,这就是事务的原子性。

一致性(consistency)

数据库老是从一个一致性状态转换到另外一个一致性的状态。

隔离性(isolation)

一般来讲,一个事务所作的修改在最终提交之前,对其余事务是不可见的。

持久性(durability)

一旦事务提交,则其所作的修改就会永久保存到数据库中。

隔离级别

READ UNCOMMITTED(未提交读)

在READ UNCOMMITED级别,事务中的修改,即便没有提交,对其余事务也都是可见的。事务能够读取未提交的数据,这也被称为胀读(Dirty Read)。

READ COMMITED(提交读)

大多数数据库系统的默认隔离级别都是READ COMMITED(但MySQL不是)。READ COMMITED知足前面提到的隔离性的简单定义:一个事务开始时,只能“看见”已经提交的事务所作的修改。换句话说,一个事务从开始直到提交以前,所作的任何修改对其余事务都是不可见的。这个级别有时候也叫作不可重复读,由于两次执行一样的查询,可能会获得不同的结果。

REPEATABLE READ(可重复读)

REPEATABLE READ 解决了脏读的问题。该级别保证了在同一个事务中屡次读取一样记录的结果是一致的。可是理论上,可重复读隔离级别仍是没法解决另外一个幻读的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当以前的事务再次读取该范围内的记录时,会产生幻行。InnoDB和XtraDB 存储引擎经过过版本并发控制(MVCC)解决了幻读的问题。

SERIALIZABLE(可串行化)

SERIALIZABLE 是最高的隔离级别。它经过强制事务串行化执行,避免了幻读问题。简单来讲,SERIALIZABLE 会在读取的每一行数据上都加锁,因此可能致使大量的超时和锁争用问题。实际应用中也不多用到这个隔离级别,只有在很是须要确保数据的一致性并且能够接受没有并发的状况下,才考虑采用该级别。

ANSI SQL 隔离级别

死锁

死锁是指两个或者多个事务在同一资源上互相占用,并请求锁定对方占用的资源,从而致使而行循环的现象。当多个事务试图以不一样顺序锁定资源时,就可能会产生死锁。多个事务同时锁定同一个资源时,也会产生死锁。

多版本并发控制

MySQL 的大多数事务型存储引擎实现的都不是简单的行级锁。基于提高并发性能的考虑,它们通常都同时实现了多版本并发控制(MVCC)。不只是MySQL,包括Oracle、PostgreSQL 等其余数据库系统也都实现了MVCC,但各自的实现机制不尽相同,由于MVCC没有一个统一的实现标准。

能够认为MVCC是行级锁的一个变种,可是它在不少状况下避免了加锁操做,所以开销更低。虽然实现机制有所不一样,但大都实现了非阻塞的读操做,写操做也只锁定必要的行。

MVCC的实现,是经过保存数据在某一个时间点的快照来实现的。也就是说,无论须要执行多长时间,每一个事务看到的数据都是一致的。根据事务开始的时间不一样,每一个事务对同一张表,同一时刻看到的数据多是不同的。

不一样存储引擎的MVCC的实现是不一样的,典型的有乐观并发控制和悲观并发控制。

InnoDB的MVCC,是经过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的建立时间,一个保存了行的过时时间(或删除时间)。固然存储的并非实际的时间值,而是系统版本号。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会做为事务的版本号,用来和查询到的每行记录的版本号进行比较。下面是在REPPEATABLE READ隔离级别下,MVCC具体是如何操做的。

SELECT

InnoDB 会根据如下两个条件去检查每行记录:

  1. InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样能够确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
  2. 行的删除版本要么未定义,要么大于当前事务版本号。这能够确保事务读取到的行,在事务开始以前未被删除。

INSERT

InnoDB 为新插入的每一行保存当前系统版本号做为行版本号。

DELETE

InnoDB 为删除的每一行保存当前系统版本号做为删除标识。

UPDATE

InnoDB 为插入一行新纪录,保存当前系统版本号做为行版本号,同时保存当前系统版本号到原来的行做为删除标识。

保存这两个额外系统版本号,使大多数读操做均可以不通加锁。这样设计使得读操做很简单,性能很好,而且也能保证只会读取到符合标准的行。不足之处是每行记录都须要额外的存储空间,须要作不少的行检查工做,以及一些额外的维护工做。

MVCC只在REPEATABLE READ和READ COMMITED两个隔离级别下工做。其余两个隔离级别都和MVCC不兼容,由于READ UNCOMMITED老是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE则会对全部读取的行都加锁。

总结

MySQL 拥有分层的架构。上层是服务器层的服务和查询执行引擎,下层则是存储引擎。虽然有不少不一样做用的插件API,但存储引擎API仍是最重要的。若是能理解MySQL 在存储引擎和服务层之间处理查询时如何经过API来回交互,就能抓住MySQL 的核心基础架构的精髓。

书读百遍,其义自现。

有时候不得不认可本身的无知。从大二就开始接触MySQL,可是一直都在尝试着去用。随着时间的流逝,现在用MySQL 对我来讲问题已经不大了,到了学习MySQL是如何实现的阶段了。

这里面的InnoDB的MVCC给我十分深入的印象,由于我最近作的一个供应商信息版本控制,实现的设计和这里大体相同,这种思想上的共通,给人以启迪。




本文做者: 荒古
本文连接: https://haxianhe.com/2019/08/...
版权声明: 本博客全部文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!


欢迎关注个人公众号:荒古传说

相关文章
相关标签/搜索