专栏地址:
文章目录
1. MySQL的逻辑架构
与其它数据库相比,MySQL最大的优势在于其灵活性,这种灵活性来源其存储与计算分离的架构设计。MySQL将查询处理以及其它系统任务与存储/提取相分离,使得可以根据不同的使用场景,选择合适的数据存储方式。
MySQL的整体架构如下图所示:

从整体上看,MySQL可以分为Sercer层和存储引擎层。
MySQL与客户端的交互
MySQL是”边读边发“的,读取到的每一行都直接放入net_buffer中,当缓存写满后,就发送给客户端。net_buffer的默认大小是16K。
MySQL也不保存完整的结果集,若客户端数据读取不及时,会堵住查询过程,暂停读数据的流程,因此不会把内存打爆。
1.1 Server层
Server层主要包括了:连接器、查询缓存、分析器、优化器、执行器等,同时所有的内置函数、存储过程、视图、触发器等等都在这一层实现,其涵盖了MySQL大多数的核心功能。
连接器
客户端和MySQL服务端之间基于TCP连接,连接器负责与客户端建立并维持连接、获取操作权限等。其通信协议是半双工的,服务端也并不是汇总整个查询结果后一次性返回,而是及时返回。
查询缓存
在解析一个查询语句之前,MySQL会首先查询缓存中是否执行过该语句。之前执行过的语句会以key-value的形式缓存于内存中,key是查询语句、客户端协议版本号等可能影响查询结果的信息的哈希值,value是查询结果。
查询缓存往往弊大于利,原因在于其命中的条件比较苛刻:
- 查询语句任何字符的不同将导致缓存不命中
- 函数等非确定值也会导致缓存不命中
- 更新将导致该表的所有缓存失效
此外,缓存操作还涉及到锁。
分析器
缓存未命中后,MySQL会开始真正的执行语句。分析器的主要职责在于:
- 词法分析:识别出SQL字符串中的字符含义,比如将表名解析成表。
- 语法分析:分析SQL语句的是否满足语法规则,如果发生语法错误,将会返回“You have an error in your SQL syntax”
分析器最终生成了解析树。
优化器
经过分析器后,MySQL已经理解了SQL语句索要执行的操作。在执行之前,还要经过优化器的处理,并生成执行计划。优化器的职责在于最小化查询成本,比如:
- 选择合适的索引
- 选择各个表合适的连接顺序
成本的计量单位是随机读取一个4k数据页的成本,很多信息将导致优化器选择错误的执行计划,比如:
- 统计信息,比如每个表、索引的页面树、索引的基数cardinality等等,统计信息由存储引擎提供
- 优化器不考虑页面是否位于缓存,无法知道到底需要多少次物理IO
- 优化器不会穷尽所有执行计划
执行器
执行器按照执行计划的指令,逐条执行,调用存储引擎接口并将满足条件的行组织成结果集返回给客户端。在慢SQL的日志中,row_examined字段表示执行器总共扫描了多少行数据。
join、order、group等操作也都是在执行器里执行的。
1.2 存储引擎
存储引擎负责MySQL数据的存储与提取,存储引擎被设计成插件式的,InnoDB是MySQL默认的存储引擎,上一个默认引擎是MyISAM。InnoDB是一个事务型引擎,具备故障恢复等特性,也是使用最广泛的存储引擎。MyISAM不支持行级锁和崩溃后的故障恢复,但并不是一无是处,其提供压缩表、GIS空间函数等特性。除非需要使用到InnoDB不具备的特性,否则应优先使用InnoDB引擎。比如,不在乎故障恢复但却对InnoDB占用过多空间比较敏感,则可以使用MyISAM。
InnoDB关键特性有:
- change buffer
- double write
- 自适应哈希索引
- 刷新邻接页
- 异步IO
2. 并发控制
无论何时,只要有多个查询在同一时刻修改数据,就会产生并发控制问题。MySQL在两个层面进行并发控制:Server层和存储引擎层。
2.1 读写锁
在处理并发读或者写时,经典的解决方案是利用一套由两种锁类型组成的锁系统:读锁(read lock)也称共享锁(shared lock)和写锁(write lock)也称排他锁(exclusive lock)。读锁是共享的,多个读操作互不干扰、互相不会阻塞。写锁是排他的,也就是说,一个写锁会阻塞其它的读锁和写锁。
在MySQL中,当用户修改某一部分的数据是,MySQL会通过锁来防止其它用户读取、修改同一数据。
2.2 锁粒度
提高共享资源并发度的基本方式就是降低锁的粒度,让锁定的对象更加具有选择性,尽量只锁定需要修改的部分数据。在给定的资源的前提下,锁定的数据量越小,则系统的并发程度越高。
但是,加锁也需要消耗系统资源。锁的各种操作,包括获得锁、检查锁、释放锁等都会增加系统的开销。如果系统花费大量的时间来管理锁,而不是处理数据,那么系统的性能将会收到很大影响。此时,就需要在锁开销和数据安全之间寻求平衡,选择一种合适的锁策略。
大多数数据库一般都是在表上施加行级锁,以便在资源竞争比较激烈的情况下,提供更好的并发性能。而MySQL则提供了更多的灵活性,存储引擎可以实现自己的锁策略和锁粒度,为可特定的应用场景提供更好的性能。
2.2.1 常见的锁策略
表锁(table lock)
表锁是MySQL最基本的锁,也是开销最小的锁。在进行写操作前,首先需要获得写锁,这会锁定整张表,阻塞其它用户的该表的所有读写操作。在没有写锁的情况下,读锁之间互相步阻塞。
MySQL在Server层实现了表锁,尽管存储引擎可以自己管理锁,但当遇到诸如ALTER TABLE之类的SQL语句时,MySQL会直接使用表锁,同时忽略存储引擎的锁机制。
行级锁(row lock)
行级锁可以最大程度的支持并发处理,同时也带来了最大的锁开销。行级锁只能在存储引擎层实现,Server层完全不了解存储引擎中的锁机制。InnoDB实现了行级锁。
2.3 死锁
死锁是多个线程阻塞等待其它处于死锁状态的线程所持有的锁,即互相持有对方所需要的锁,并请求对方的锁。例如:线程1持有A锁,请求B锁,线程2持有B锁请求A锁。一旦产生死锁,则会陷入死循环,除非有外部因素介入,否则无法能解除死锁。
产生死锁的四个必要条件:
- 互斥:独占性,一个资源同一时间只能被一个线程所拥有。
- 不可抢占:在资源未被使用完毕前,其它申请者不能强行剥夺。
- 请求与保持:进程保持已经获得的资源,去请求其它资源并因此而阻塞。
- 循环等待:若干线程想成了首尾相接的循环等待资源关系。
预防死锁(破坏4个必要条件)
资源一次性分配,一次性获得所有锁——破坏请求与保持
新锁未请求到时释放已占有的锁——破坏不可抢占
一般数据库系统中,均实现了各种死锁检测和死锁超时机制。InnoDB可以检测到死锁的循环依赖,并立即返回一个错误,同时将持有最少行级锁的事务进行回滚。
3. 事务
3.1 事务概念
事务是指满足ACID特性的操作,可以将数据库从一个一致性状态安全的转移到另一个一致性状态。就像锁粒度的升级会增加系统开销一样,事务保障数据安全的同时,也意味着更大的开销。事务由存储引擎实现,Server不管理事务。
事务系统必须支持ACID
- Atomicity原子性:事务被视为不可分割的最小单元,要么全部执行成功,要么全部执行失败。
- Consistency一致性:事务将数据库从一个一致性状态转移到另一个一致性状态,在事务开始前后,数据库的完整性约束没有被破坏。
- Isolation隔离性:事务提交之前对其它事务不可见,事务之间相互隔离。
- Durability持久性:事务提交之后,其结果是永久性的。保证了事务的高可靠性,而不是高可用性(硬盘损坏)。
并发一致性问题
- 丢失更新: 两个事务对同一个数据修改,后一事务覆盖了前者的修改。通过乐观锁或悲观锁解决。
- 脏读: 事务A修改了一个数据,但未提交,随后被事务B读取,若A撤销了修改,那么B读取到的为脏数据。——读取了未提交的数据
- 不可重复读: A读取了一个数据,随后B修改了该数据,此时A再次读取该数据时,结果与第一次不同。——先后读取了修改(update)先后的数据
- 幻读: A读取某个范围的数据,随后B在该范围内添删除了数据,此时A再次读取该范围数据时,结果与第一次不同。——先后读取了某一范围内修改(insert、delete)前后的数据
隔离级别
SQL定义了四种隔离级别:
- 未提交读Read Uncommitted:即使事务没有提交,其修改也是对其它事务可见。
- 提交读Read Committed:一个事务在未提交之前,其所作的修改对其它事务不可见。
- 可重复读Repeatable Read:同一个事务中,多次读取同样数据的结果相同。
- 可串行化Serialable:事务串行、依次逐个执行,事务之间不产生干扰。
隔离级别 | 脏读 | 不可重复读 | 幻影读 |
---|---|---|---|
未提交读 | √ | √ | √ |
提交读 | × | √ | √ |
可重复读 | × | × | √ |
可串行化 | × | × | × |
但是在InnoDB中,默认的事务隔离级别是可重复读,但由于采用了Next-Key Lock,可以解决幻读问题,达到了可串行化的隔离级别。
3.2 事务日志
大多数存储引擎利用事务日志来提高事务的处理效率。存储引擎在修改表数据的时候,首先只修改其缓存页,而不实时得进行刷盘操作。并在修改缓存前,以追加写得方式落盘事务日志。这个方式称之为WAL(Write-Ahead Logging),日志先行。由于顺序IO得速度远快于随机IO,所以事务日志可以大幅度提高系统性能。
InnoDB中的redo log重做日志即是事务日志的一种。
4. MVCC多版本并发控制
MySQL大多数的事务型存储引擎实现的都不是简单的行级锁,一般都利用MVCC来提升并发性能,不仅是MySQL,PostgreSQL、Oracle等其它数据库也都实现了MVCC。
MVCC可以认为是行级锁的一个变种,但是在很多情况下避免了加锁操作,因此开销更小。
MVCC利用视图快照,可以实现整个事务期间看到的数据是一致的,根据不同的开始时间,不同的事务看到的数据可能是不一样的。
参考
《MySQL实战45讲》极客时间
《高性能MySQL》
《MySQL技术内幕(InnoDB存储引擎)》