MySQL为什么灵活?灵活性体现在哪?
可以通过配置使它在不同的硬件上都运行得很好,也可以支持多种不同的数据类型。但是最重要、最与众不同的特性是它的存储引擎架构,这种架构的设计将查询处理
及其他系统任务和数据的存储/提取
相分离。
这种处理和存储分离的设计可以在使用时根据性能、特性,以及其他需求来选择数据存储的方式。
学习目标:
- 掌握MySQL服务器架构
- 掌握各种存储引擎之间的主要区别,以及这些区别的重要性
- 了解MySQL的历史背景和基准测试
- 初步掌握MySQL的原理
1.1 MySQL逻辑架构
头脑中构建出一幅MySQL各组件之间如何协同工作的架构图,有助于深入理解MySQL服务器。

第一层:连接处理、授权认证、安全
第二层:大多数MySQL的核心服务,包括查询解析、分析、优化、缓存以及所有的内置函数(例如,日期、时间、数学和加密函数),所有跨存储引擎的功能都在这一层实现:存储过程、触发器、视图等。
第三层:存储引擎。存储引擎负责MySQL中数据的存取和提取。服务器通过API与存储引擎进行通信。存储引擎API包含几十个底层函数,用于执行诸如“开始一个事务”或者“根据主键提取一行记录”等操作。但存储引擎不回去解析SQL(InnoDB例外,会解析外健定义,因为MySQL服务器本身没有实现该功能),不同存储引擎之间也不会相互通信,而只是简单地响应上层服务器等请求。
1.1.1 连接管理与安全性
每个客户端连接都会在服务器进程中拥有一个线程,这个连接的查询只会在这个单独的线程中执行,该线程只能轮流在某个CPU核心或者CPU中运行。服务器会负责缓存线程,因此不需要为每一个新建的连接创建或者销毁线程。
线程池是一个更优秀的资源管理策略
当客户端(应用)连接到MySQL服务器时,服务器需要对其进行认证。认证基于用户名、原始主机信息和密码。如果使用了安全套接字(SSL)的方式连接,还可以使用X.509证书认证。一旦客户端连接成功,服务器会继续验证该客户端是否具有执行某个特定查询的权限(例如,是否允许客户端对world数据库的Country表执行Select语句)。
1.1.2 优化与执行
MySQL会解析查询,并创建内部数据结构(解析树),然后对其进行各种优化,包括重写查询、决定表的读取顺序,以及选择合适的索引等。
用户可以通过特殊的关键字提示(hint)优化器,影响它的决策过程。
也可以请求优化器解释(explain)优化过程的各个因素,使用户可以知道服务器是如何进行优化决策的,并提供一个参考基准,便于用户重构查询和schema、修改相关配置,使应用尽可能高效运行。
第6章将讨论更多优化器细节。
优化器会请求存储引擎提供容量或某个具体操作的开销信息,以及表数据的统计信息等。例如,某些存储引擎的某种索引,可能对一些特定的查询有优化。
关于索引与schema的优化,参见第4章和第5章。
对于SELECT语句,在解析查询之前,服务器会先检查查询缓存,如果能够在其中找到对应的查询,服务器就不必再执行查询解析、优化和执行的整个过程,而是直接返回查询缓存中的结果集。
第7章详细讨论了相关内容。
1.2 并发控制
无论何时,只要有多个查询需要在同一时刻修改数据,都会产生并发控制的问题。
本章的目的是讨论MySQL在两个层面的并发控制:服务器层与存储引擎层。
本章只简要地讨论MySQL如何控制并发读写。
以Unix系统的email box为例,典型的mbox文件格式是非常简单的。一个mbox邮箱中的所有邮件都串行在一起,彼此首尾相连。
这种格式对于读取和分析邮件信息非常友好,同时投递邮件也很容易,只要在文件末尾附加新的邮件内容即可。
但如果两个进程在同一时刻对同一个邮箱投递邮件,邮箱的数据会被破坏,两封邮件的内容会交叉地附加在邮箱文件的末尾。
设计良好的邮箱投递系统会通过锁(lock)来防止数据损坏。如果客户试图投递邮件,而邮箱已经被其他客户锁住,那就必须等待,直到锁释放才能进行投递。
这种锁的方案在实际应用环境中虽然工作良好,但并不支持并发处理。因为任意一个时刻,只有一个进程可以修改邮箱的数据,这在大容量的邮箱系统中是个问题。
1.2.1 读写锁
读读不会出现问题,读写、写读、写写都会有问题。需要做同步处理。
一个经典的方法是,通过实现一个由两种类型的锁组成的锁系统来解决问题。
这两种类型的锁通常被称为共享锁和排他锁,也叫读锁和写锁。
读锁是共享的,或者说是相互不阻塞的。多个客户在同一时刻可以同时读取同一个资源,而互不干扰。
写锁则是排他的,也就是说一个写锁会阻塞其他的写锁和读锁。
在实际的数据库系统中,每时每刻都在发生锁定,当某个用户修改某一部分数据时,MySQL会通过锁定防止其他用户读取同一数据。大多数时候,MySQL锁的内部管理都是透明的。
1.2.2 锁粒度
一种提高共享资源并发性的方式就是让锁定对象更有选择性。尽量只锁定需要修改的部分数据,而不是所有的资源。
更理想的方式是,只对会修改的数据片进行精确的锁定。任何时候,在给定的资源上,锁定的数据量越少,则系统的并发程度越高,只要相互之间不发生冲突即可。
问题是加锁也需要消耗资源。锁的各种操作,包括获得锁、检查锁是否已经解除、释放锁等,都会增加系统等开销。如果系统花费大量的时间来管理锁,而不是存取数据,那么系统的性能可能会因此受到影响。
所谓的锁策略,是在锁的开销和数据的安全性之间寻求平衡,这种平衡当然也会影响到性能。
大多数商业数据库系统没有提供更多的选择,一般都是在表上施加行级锁,并以各种复杂的方式来实现,以便在锁比较多的情况下尽可能地提供更好的性能。
而MySQL则提供了多种选择。每种MySQL存储引擎都可以实现自己的锁策略和锁粒度。
MySQL中,对锁的管理灵活,锁粒度可以修改,多存储引擎架构提供对多场景支持。
两种重要的锁策略
表锁
开销最小
在特定的场景中,表锁也可能有良好的性能。例如,READ_LOCAL表锁支持某些类型的并发写操作。
另外,写锁也比读锁有更高的优先级,因此一个写锁请求可能会被插入到读锁队列的前面。
尽管存储引擎可以管理自己的锁,MySQL本身还是会使用各种有效的表锁来实现不同的目的。例如,服务器会为诸如ALTER TABLE之类的语句使用表锁,而忽略存储引擎的锁机制。
行级锁
行级锁可以最大程度地支持并发处理(同时也带来了最大的锁开销)。在InnoDB和XtraDB,以及一些存储引擎中实现了行级锁。
行级锁只在存储引擎层实现,而MySQL服务器层(如有必要,请回顾前问的逻辑架构图)没有实现。服务器层完全不了解存储引擎中的锁实现。在本章的后续内容以及全书中,所有的存储引擎都以自己的方式显现了锁机制。
1.3 事务
事务就是一组原子性的SQL查询,或者说一个独立的工作单元。
事务内的语句,要么全部执行成功,要么全部执行失败。
事务的ACID
一个运行良好的事务处理系统,必须具备这些标准特征:
原子性(atomicity)
一致性(consistency)
隔离性(isolation)
持久性(durability)
实现ACID需要考虑性能、安全等很多复杂问题。
隔离级别
在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。
READ UNCOMMITTED(读未提交)
READ COMMITTED(读已提交)
REPEATABLE READ(可重复度)
SERIALIZABLE(可串行化)
ANSI SQL隔离级别
隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | 加锁读 |
---|---|---|---|---|
READ UNCOMMITTED | Yes | Yes | Yes | No |
READ COMMITTED | No | Yes | Yes | No |
REPEATABLE READ | No | No | Yes | No |
SERIALIZABLE | No | No | No | Yes |
1.3.2 死锁
什么是死锁?
怎么产生死锁?
死锁检测和超时机制
1.3.3 事务日志
采用追加的方式,速度快,顺序IO,不用移动磁头。
预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘,实时修改内存拷贝,持久化事务日志,事务日志持久化后,内存中被修改的数据在后台可以慢慢地刷回到磁盘。
1.3.4 MySQL中的事务
事务支持:InnoDB
自动提交
默认自动提交
在事务中混合使用存储引擎
隐式和显式锁定
1.4 多版本并发控制
可以认为MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制有所不同,但都实现了非阻塞的读操作,写操作也只锁定必要的行。
MVCC的实现,是通过保存数据在某个时间点的快照来实现的。
MVCC的实现,典型的有乐观并发控制和悲观并发控制。下面我们通过InnoDB的简化版行为来说明MVCC是如何工作的。
InnoDB的MVCC,是通过在每行记录后保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间(或删除时间)。当然存储的并不是实际的时间值,而是系统版本号(system version number)。没开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
下面看一下在REPEATABLE READ隔离级别下,MVCC具体是如何操作的。
SELECT
InnoDB会根据以下两个条件检查每行记录:
a. InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
b. 行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。
只有符合上述两个条件的记录,才能返回作为查询结果。
Insert
InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
DELETE
InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
UPDATE
InnoDB为插入一行新纪录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。
保存这两个额外系统版本号,使大多数读操作都可以不用加锁。这样设计使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。
不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。
MVCC只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作。其他两个隔离级别都和MVCC不兼容,因为READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE则会对所有读取的行都加锁。
1.5 MySQL的存储引擎
数据库,或称schema,对应文件系统中数据目录下的一个子目录。
创建表时,MySQL会在数据库子目录下创建一个和表同名的.frm文件保存表的定义。
大小写敏感和平台相关。
可以使用show table status命令(在5.0以后的版本中,也可以查询information_schema中对应的表)显示表的相关信息。
1.5.1 InnoDB存储引擎
MySQL默认的事务引擎。被设计用来处理大量的短期事务,短期事务大部分情况时正常提交的,很少会被回滚。
InnoDB的性能和自动崩溃恢复特性,使得它在非事务型存储的需求中也很流行。
InnoDB的历史
InnoDB概览
InnoDB的数据存储在表空间中。
InnoDB采用MVCC来支持高并发,并且实现了四个标准的隔离级别。
默认级别是REPEATABLE READ(可重复读),并且通过间隙锁(next-key locking
)策略防止幻读的出现。间隙锁使得InnoDB不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定,以防止幻影行的插入。
InnoDB表是基于聚簇索引建立的。
聚簇索引对主键查询有很高的性能。不过它的二级索引(secondary index,非主键索引)中必须包含主键列,所以如果主键列很大的话,其他的索引都会很大。
因此,若表上的索引较多的话,主键应当尽可能的小。
InnoDB的存储格式是平台独立的。数据和索引文件可以在各个平台间复制。
InnoDB内部做了很多优化,包括从磁盘读取数据时采用的可预测性预读,能够自动在内存中创建hash索引以加速读操作的自适应哈希索引(adaptive hash index),以及能够加速插入操作的插入缓冲区(insert buffer)等。后面将详细地讨论这些内容。
如果应用程序基于InnoDB引起,则应该了解一下InnoDB的MVCC架构带来的一些微妙和细节之处时非常有必要的。
存储引擎要为所有用户甚至包括修改数据的用户维持一致性的视图,是非常复杂的工作。要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入也意味着停止读取。
InnoDB支持热备份。
1.5.2 MyISAM存储引擎
特性:
支持全文索引、压缩、空间函数(GIS)等
缺点:
不支持事务和行级锁
崩溃后无法完全恢复
存储
特性
压缩表
性能
1.5.3 MySQL内建等其他存储引擎
Memory引擎
Merge引擎
1.5.4 第三方存储引擎
OLTP类引擎
TokuDB引擎,分形树(Fractal Trees)索引数据结构。缓存无关,是一种大数据存储引擎,因为其拥有很高的压缩比,可以在大的数据量上创建大量索引。
面向列的存储引擎
社区存储引擎
1.5.5 选择合适的引擎
如果应用需要不同的存储引擎,请先考虑以下几个因素。
事务
备份
崩溃恢复
特有的特性
常见的应用场景。
日志型应用
只读或者大部分情况下只读的表
订单处理
电子公告牌和主题讨论论坛
CD-ROM应用
大数据量
1.5.6 转换表的引擎
ALTER TABLE
导出与导入
创建于查询
先创建一个新的存储引擎的表,然后利用insert... select
语法来导数据。
create table innodb_table like muisam_table;
alter table innodb_table engine=Innodb;
insert into innodb_table select * from myisam_table;
数据量大的情况,可以考虑做分批处理,针对每一段数据执行事务提交操作,避免大事务产生过多的undo。
start transaction;
insert into innodb_table select * from myisam_table where id between x and y;
commit;
1.6 MySQL时间线(Timeline)
1.7 MySQL的开发模式
新里程碑开发版
GA(Generally Available)版,通常可用版
实验室预览版,发布一些需要评估的特性
插件付费,插件开发模式
1.8 总结
MySQL拥有分层的架构。
上层是服务器层的服务和查询执行引擎,下层则是存储引擎。
如果能理解MySQL在存储引擎和服务层之间处理查询时如何通过API来回交互,就能抓住MySQL的核心基础架构的精髓。
数据字典保存在.frm文件中,对于InnoDB来说,所有的操作都是事务。
选择多并非好事。InnoDB对于95%以上的用户来说都是最佳选择。

Time waits for no one.