1. MySQL的基础架构

专栏地址:

MySQL系列文章专栏



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是查询结果。

查询缓存往往弊大于利,原因在于其命中的条件比较苛刻:

  1. 查询语句任何字符的不同将导致缓存不命中
  2. 函数等非确定值也会导致缓存不命中
  3. 更新将导致该表的所有缓存失效

此外,缓存操作还涉及到锁。

分析器

缓存未命中后,MySQL会开始真正的执行语句。分析器的主要职责在于:

  1. 词法分析:识别出SQL字符串中的字符含义,比如将表名解析成表。
  2. 语法分析:分析SQL语句的是否满足语法规则,如果发生语法错误,将会返回“You have an error in your SQL syntax”

分析器最终生成了解析树。

优化器

经过分析器后,MySQL已经理解了SQL语句索要执行的操作。在执行之前,还要经过优化器的处理,并生成执行计划。优化器的职责在于最小化查询成本,比如:

  1. 选择合适的索引
  2. 选择各个表合适的连接顺序

成本的计量单位是随机读取一个4k数据页的成本,很多信息将导致优化器选择错误的执行计划,比如:

  1. 统计信息,比如每个表、索引的页面树、索引的基数cardinality等等,统计信息由存储引擎提供
  2. 优化器不考虑页面是否位于缓存,无法知道到底需要多少次物理IO
  3. 优化器不会穷尽所有执行计划

执行器

执行器按照执行计划的指令,逐条执行,调用存储引擎接口并将满足条件的行组织成结果集返回给客户端。在慢SQL的日志中,row_examined字段表示执行器总共扫描了多少行数据。
join、order、group等操作也都是在执行器里执行的。

1.2 存储引擎

存储引擎负责MySQL数据的存储与提取,存储引擎被设计成插件式的,InnoDB是MySQL默认的存储引擎,上一个默认引擎是MyISAM。InnoDB是一个事务型引擎,具备故障恢复等特性,也是使用最广泛的存储引擎。MyISAM不支持行级锁和崩溃后的故障恢复,但并不是一无是处,其提供压缩表、GIS空间函数等特性。除非需要使用到InnoDB不具备的特性,否则应优先使用InnoDB引擎。比如,不在乎故障恢复但却对InnoDB占用过多空间比较敏感,则可以使用MyISAM。

InnoDB关键特性有:

  1. change buffer
  2. double write
  3. 自适应哈希索引
  4. 刷新邻接页
  5. 异步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

  1. Atomicity原子性:事务被视为不可分割的最小单元,要么全部执行成功,要么全部执行失败。
  2. Consistency一致性:事务将数据库从一个一致性状态转移到另一个一致性状态,在事务开始前后,数据库的完整性约束没有被破坏。
  3. Isolation隔离性:事务提交之前对其它事务不可见,事务之间相互隔离。
  4. Durability持久性:事务提交之后,其结果是永久性的。保证了事务的高可靠性,而不是高可用性(硬盘损坏)。

并发一致性问题

  1. 丢失更新: 两个事务对同一个数据修改,后一事务覆盖了前者的修改。通过乐观锁或悲观锁解决。
  2. 脏读: 事务A修改了一个数据,但未提交,随后被事务B读取,若A撤销了修改,那么B读取到的为脏数据。——读取了未提交的数据
  3. 不可重复读: A读取了一个数据,随后B修改了该数据,此时A再次读取该数据时,结果与第一次不同。——先后读取了修改(update)先后的数据
  4. 幻读: A读取某个范围的数据,随后B在该范围内添删除了数据,此时A再次读取该范围数据时,结果与第一次不同。——先后读取了某一范围内修改(insert、delete)前后的数据

隔离级别

SQL定义了四种隔离级别:

  1. 未提交读Read Uncommitted:即使事务没有提交,其修改也是对其它事务可见。
  2. 提交读Read Committed:一个事务在未提交之前,其所作的修改对其它事务不可见。
  3. 可重复读Repeatable Read:同一个事务中,多次读取同样数据的结果相同。
  4. 可串行化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存储引擎)》

思维导图

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/cooper20/article/details/108632599