我们详细深入介绍MySQL的方方面面之前,让我们首先来看一下MySQL的总体架构,从总体上对MySQL有所了解,这样我们在后面的具体介绍部分的时候才能够知道这些部分属于哪个模块,大概有什么样的作用。

MySQL的逻辑架构

首先我们来看一下MySQL的逻辑架构,如下图所示。

架构分享 架构梳理_架构分享

我们可以看到大概可以分成三层:

  • Connection/thread handing,这一层其实和MySQL没有什么大的关系,在一般的C/S架构中都会存在,一般用来处理相关的连接,认证,安全等等方面,我们不具体介绍。
  • Parser/Optimizer。这一层就是MySQL的大脑了,它主要工作就是解析查询语句,分析并进行优化,所有跨storage engines的函数都是在这里实现的,比如Stored Procedures,views等等。
  • Storage Engines。顾名思义,这一层的主要作用就是存储和查询数据。MySQL支持很多种不同的Storage Engine,我们在后面一一给大家介绍。

并行控制

在了解了MySQL的逻辑架构之后,我们来看一下MySQL对一些常见数据库的问题的处理方法。第一个问题当然就是我们常见的并行控制,简单说就是有读写同时发生的时候会如何处理。我们在之前的文章介绍过常见的处理方法,这里MySQL选用的是读写锁,也就是说读获取锁的时候,可以有多个读同时进行,但是写的锁是排外的,也就是说有写获取锁,那么任何别的读和写都没有办法获取相应的锁。

使用锁有一个重要的问题就是如何确定锁的粒度,说白了,你把锁加在越大的粒度上,并行的性能就会降低,因为拿不到锁的概率就会变大。假如你把锁的粒度搞到比较小,拿不到锁的概率就会变低,这样并行的性能就会变好,但是问题是锁消耗的资源其实是很大的看,粒度越小就意味着需要更多的锁,从而有更多的资源消耗。这就是我们需要考虑的trade off。

MySQL在这个方面提供了一些选择,根据不同的Storage engine可以实现不同的锁机制和锁粒度。不过通常来说有两种锁的机制比较常见:

  • 表锁:顾名思义,即使lock整个表。每次写一个表的时候都去抓这个表锁,也就意味着其它任何读写都需要等待这个锁。
  • 行锁:这个锁的粒度相比表锁来说就细了很多,它是应用到一行上面的。也就是说只有这行的数据被修改的时候,才会影响到需要修改这行数据的读和写。这样一来其实并行读写的性能就会变得比较好。

大多数MySQL使用的不是简单的行锁。他们使用的是行锁和一种称之为多版本同步控制(MVCC)的技术。这个技术我们在之前的《Snapshot的隔离和Repeatable的读》中有详细的介绍。

Transaction

Transaction是一个通用的概念,这里我们不详细介绍,大家可以参考《Transactions的基本概念和介绍》这篇文章。

具体到MySQL,它有不同的storage engine支持Transaction,我们这里以最推荐的InnoDB来说明。

默认来说,单独的Insert,Update以及Delete操作是被隐式地转变成 一个Transaction并且立即commit的。也就是我们常说的AUTOCOMMIT。你可以使用SET AUTOCMMIT = 0/1来关掉或者打开这个功能。注意关掉之后,你就会一直在一个transaction里面,直到你commit或者rollback。

另外MySQL还可以自己设置隔离的级别,你可以简单使用下面这个命令来进行设置

架构分享 架构梳理_架构分享_02

如果你想了解具体关于transaction的隔离相关知识,可以参见这些文章:

Transaction弱隔离之读提交的介绍和实现

Snapshot的隔离和Repeatable的读

Transaction弱隔离之更新丢失

Transaction弱隔离之Write Skew和Phantoms

Transaction Serializable隔离之串行执行

Transaction Serializable隔离之两阶段锁

一文带你深入理解Serializable隔离最新技术SSI

相信读了这些文章之后,你会对Transaction的隔离相关内容有一个非常清晰的理解。

需要注意的是在MySQL中,transaction是在storage engine level实现的,这也就意味着假如你一个transaction设计多个不同的engine,那么这个transaction是不可靠的,因为不同的engine都有自己的设置,尤其是需要rollback的时候,可能transaction在其中一个engine中的内容被rollback了,而另外一个则没有。当然有时它会报错,但是很多时候都不会有这样的错误出现,所以你自己需要小心。

另外InnoDB使用的是两阶段锁,同时它也支持现实锁的设置,如下所示,这个是MySQL 8.0的新的功能。

架构分享 架构梳理_后端_03

Replication

MySQL中的replication使用的是pull的模式,也就是说它的每个replica是周期性的到source那边进行pull最新的log。简单的示意图如下所示:

架构分享 架构梳理_mysql_04

通常来说为了更好地达到replication的目的,我们需要有最少3个replica,他们最好能分布在不同的数据中心(位置)。这样即使有灾难发生,比如地震等等,我们也能保证有至少一个repica是可以使用的。有关replication的基础知识可以参见这篇文章《分布式系统之leader-followers Replication深入介绍》。

Storage Engines

MySQL支持很多不同的Storage Engine,你可以根据自己的应用需求来进行选择。每个版本的MySQL对Storage Engine都有不同的支持。具体可能需要大家参考各个版本的说明。

在8.0之前,MySQL每个数据库是按照文件系统中的子目录格式来存储的。你创建了一个表,MySQL把表保存成一个.frm文件(定义)和一个.ibd文件(存储数据)。所以你创建了一个表Test,就会有两个文件出现,一个是Test.frm一个是Test.ibd。假如你使用了partition的话,还会有一个Test.par文件。

在8.0之中,MySQL重新定义了表的metadata,把它直接包含到标的.ibd文件中。这样一来表的结构就支持了transaction和atomic数据定义的改变。这样我们可以使用一个基于LRU的memory cache来保存partition的定义,表的定义等等。这样可以有效的降低了IO的消耗。

下面我们来看几种常见的存储引擎:

InnoDB引擎

我们其实上文也提到过,InnoDB是MySQL默认的也是最常见的存储引擎。它主要应用于那些很短的transaction。另外它的性能很好,且支持自动crash恢复,所以常见的无transaction应用也会使用它。

InnoDB使用的MVCC来实现concurrency的处理,并且实现了所有的四种隔离。默认是Repeatable read的隔离。它使用的cluster index,所以primary key的查询很快,但是secondary index则有可能受primary key影响。

InnoDB有一些内置的优化,比如预测读,内存哈希索引加速查询,插入buffer来加速插入操作等。后面我们会再写几篇文章来详细介绍这些内容。

JSON文档支持

从5.7版本开始,MySQL的InnoDB开始支持JSON格式的自动validation以及快速读。和以前的BLOB的存储方式相比有了很大的进步。紧接着InnoDB设置可以支持一些基于JSON的SQL函数。8.0.7之后,开始支持JSON数组的多指索引。这可以进一步加速JSON格式的匹配某种pattern的读速度。

MYISAM

这个引擎是MySQL的第一个引擎,也是5.6之前的默认引擎。它不支持transaction也不支持行锁。在8.0中已经完全移除了。

Archive Engine

Archive Engine只支持INSERT和SELECT查询,直到5.1才支持索引,相比来说,磁盘IO消耗会小很多。它只是一个简单的为高速插入和压缩设计的引擎。

CSV Engine

CSB引擎可以把逗号分割文件解析成表格,但是不支持索引。所以可以很方便地进行csv文件的导入和导出。

Memory Engine

这个引擎是用来匹配快速读写或者不需要在重启时保存的场景的。所有的数据都是保存在memory中,所以查询速度很快,不需要等待I/O。但是不会有数据保存。其实MySQL内部会使用它来作为一些临时表的结果保存。当然数据太大,也会把它转变成其他格式比如MyISAM表保存到磁盘中等。

Merge Storage Engine

它是多个MyISAM的组合,现在已经被废弃了。

总结

至此,本文详细介绍了MySQL的基本架构以及常见的并行控制,transaction, replication的实现。最后还介绍了各种不同的Storage Engine,希望大家阅读后能对MySQL有一个大概的了解。