MySQL架构

RenXin Lv3

第一层:客户端主要为连接处理、身份验证、确保安全性等

第二层:MySQL的大多数核心功能以及一些跨存储引擎的功能

第三层:存储引擎层,负责数据的存储和提取。存储引擎层还包含几十个底层函数,用于执行诸如“开始一个事务”或者“根据主键提取一行记录”等操作。但存储引擎不会去解析SQL[1],不同存储引擎之间也不会相互通信,而只是简单地响应服务器的请求。

优化与执行

解析查询以创建内部数据结构(解析树),然后对其进行各种优化,包括重写查询、决定表的读取顺序,以及选择合适的索引等。

用户可以通过特殊关键字向优化器传递提示,从而影响优化器的决策过程。也可以请求服务器解释优化过程的各个方面,使用户可以知道服务器是如何进行优化决策的,并提供一个参考点,便于用户重构查询和schema、修改相关配置,使应用尽可能高效地运行

存储引擎对于查询优化是有影响的。优化器会向存储引擎询问一些功能

并发控制

处理并发读/写访问的系统通常实现一个由两种锁类型组成的锁系统。这两种锁通常被称为共享锁(shared lock)和排他锁(exclusive lock),也叫读锁(read lock)和写锁(write lock)

资源上的读锁是共享的,而写锁既会阻塞读锁也会阻塞其他写锁,从而保证在此时间段内只有一个写入操作

锁的粒度:锁定的数据大小,比如说行级锁和全部锁

锁定策略:锁开销和数据安全性的平衡。加锁需要消耗资源。

MySQL存储引擎都可以实现自己的锁策略和锁粒度。MySQL提供了多种存储引擎,而不是单一的通用解决方案。下面让我们来看两种最重要的锁策略。

表锁

最基本也是开销最小的锁策略

同时只有一个人能对表进行写操作,进行写操作时无法进行读操作

行级锁

可以最大程度的支持并发程度,同时也带来了最大的锁开销

事务

一组SQL语句作为一个工作单元以原子方式处理。要么全部执行成功要么全部失败

一个确保数据安全的事务处理系统,必须满足这些密切相关的标准。ACID代表原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。

原子性:不能只执行其中的一部分操作

一致性:若失败了该事务所作的任何修改都不会被保存到数据库中

隔离性:在最终提交前对其他事务不可见

持久性:一旦成功则永久保存

隔离级别

READ UNCOMMITTED(未提交读):在事务中可以查看其他事务中还没有提交的修改。这个隔离级别会导致很多问题,从性能上来说,READ UNCOMMITTED不会比其他级别好太多,却缺乏其他级别的很多好处,在实际应用中一般很少使用。

读取未提交的数据,也称为脏读(dirty read)。

READ COMMITTED(提交读):大多数系统的默认隔离级别是READ COMMITTED(但MySQL不是)。READ COMMITTED满足前面提到的隔离性的简单定义:一个事务可以看到其他事务在它开始之后提交的修改,但在该事务提交之前,其所做的任何修改对其他事务都是不可见的。这个级别仍然允许不可重复读(nonrepeatable read),这意味着同一事务中两次执行相同语句,可能会看到不同的数据结果。

REPEATABLE READ(可重复读):解决了READ COMMITTED级别的不可重复读问题,保证在同一事务中多次读取相同行数据的结果一致。但是理论上,可重复读隔离级别还是无法解决另外一个幻读(phantom read)的问题。

幻读:当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(phantom row)。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题

REPEATABLE READ是MySQL默认的事务隔离级别。

SERIALIZABLE(可串行化):SERIALIZABLE是最高的隔离级别。该级别通过强制事务按序执行,使不同事务之间不可能产生冲突,从而解决了前面说的幻读问题。简单来说,SERIALIZABLE会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中很少用到这个隔离级别

死锁

两个或多个事务相互持有和请求相同资源上的锁,产生了循环依赖。

当多个事务试图以不同的顺序锁定资源时会导致死锁。当多个事务锁定相同的资源时,也可能会发生死锁。

有些时候会出现死锁导致的崩溃,数据库在检测到后会返回错误信息或者直接终止查询

InnoDB目前处理死锁的方式是将持有最少行级排他锁的事务回滚(这是一种最容易回滚的近似算法)

锁的行为和顺序都是和存储引擎有关同样的一系列查询语句有些会产生死锁有一些不会产生死锁。

死锁产生的原因:数据冲突、存储引擎的方式

事务日志

事务日志有助于提高事务的效率。存储引擎只需要更改内存中的数据副本,而不用每次修改磁盘中的表,这会非常快。然后再把更改的记录写入事务日志中,事务日志会被持久化保存在硬盘上。因为事务日志采用的是追加写操作,是在硬盘中一小块区域内的顺序I/O,而不是需要写多个地方的随机I/O,所以写入事务日志是一种相对较快的操作。最后会有一个后台进程在某个时间去更新硬盘中的表。因此,大多数使用这种技术(write-ahead logging,预写式日志)的存储引擎修改数据最终需要写入磁盘两次。

如果修改操作已经写入事务日志,那么即使系统在数据本身写入硬盘之前发生崩溃,存储引擎仍可在重新启动时恢复更改。具体的恢复方法则因存储引擎而异。

MySQL事务(InnoDB引擎)

AUTOCOMMIT

定义:默认情况下,单个INSERT、UPDATE或DELETE语句会被隐式包装在一个事务中并在执行成功后立即提交

通过禁用此模式,可以在事务中执行一系列语句,并在结束时执行COMMIT提交事务或ROLLBACK回滚事务。

使用SET命令设置AUTOCOMMIT变量来设置自动提交模式。启用可以设置为1或者ON,如果设置了AUTOCOMMIT=0,则当前连接总是会处于某个事务中,直到发出COMMIT或者ROLLBACK,然后MySQL会立即启动一个新的事务。

此外,当启用AUTOCOMMIT时,也可以使用关键字BEGIN或者START TRANSACTION来开始一个多语句的事务。修改AUTOCOMMIT的值对非事务型的表不会有任何影响,这些表没有COMMIT或者ROLLBACK的概念。

还有一些命令,当在活动的事务中发出时,会导致MySQL在事务的所有语句执行完毕前提交当前事务。这些通常是进行重大更改的DDL命令,如ALTER TABLE,但LOCK TABLES和其他一些语句也具有同样的效果。有关会导致自动提交事务的完整命令列表,请查看对应版本的官方文档。

MySQL可以通过执行SET TRANSACTION ISOLATION LEVEL命令来设置隔离级别。新的隔离级别会在下一个事务开始的时候生效。可以在配置文件中设置整个服务器的隔离级别,也可以只改变当前会话的隔离级别:

建议最好在服务器级别设置最常用的隔离,并且只在显式情况下修改。MySQL可以识别所有4个ANSI标准的隔离级别,InnoDB也支持这些隔离级别。在事务中混合使用存储引擎MySQL不在服务器层管理事务,事务是由下层的存储引擎实现的。所以在同一个事务中,混合使用多种存储引擎是不可靠的。

在事务中混合使用事务表和非事务表(例如,InnoDB和MyISAM表),如果需要回滚,则无法撤销对非事务表的更改。这会使数据库处于不一致的状态,可能难以恢复,并使整个事务问题变得毫无意义。所以,为每张表选择合适的存储引擎,并不惜一切代价避免在应用中混合使用存储引擎是非常重要的。在非事务表中执行事务相关操作的时候,MySQL通常不会发出提醒,也不会报错。有时候只有回滚的时候才会发出一个警告:某些非事务表中的变更无法回滚。但在大多数情况下,对非事务表的操作都不会有提示。

最好不要在应用程序中混合使用存储引擎。失败的事务可能导致不一致的结果,因为某些部分可以回滚,而其他部分不能回滚。隐式锁定和显式锁定InnoDB使用两阶段锁定协议(two-phase locking protocol)。在事务执行期间,随时都可以获取锁,但锁只有在提交或回滚后才会释放,并且所有的锁会同时释放。前面描述的锁定机制都是隐式的。InnoDB会根据隔离级别自动处理锁。另外,InnoDB还支持通过特定的语句进行显式锁定,这些语句不属于SQL规范:

MySQL还支持LOCK TABLES和UNLOCK TABLES命令,这些命令在服务器级别而不在存储引擎中实现。如果需要事务,应该使用支持事务的存储引擎。因为InnoDB支持行级锁,所以没必要使用LOCK TABLES。

LOCK TABLES命令和事务之间的交互非常复杂,并且在一些服务器版本中存在意想不到的行为。因此建议,除了在禁用AUTOCOMMIT的事务中可以使用,其他任何时候都不要显式地执行LOCK TABLES,不管使用的是什么存储引擎。

多版本并发控制

MySQL的大多数事务型存储引擎使用的都不是简单的行级锁机制。它们会将行级锁和可以提高并发性能的多版本并发控制(MVCC)技术结合使用。

MVCC在很多情况下避免了加锁操作,因此开销更低。根据其实现方式,不仅实现了非阻塞的读操作,写操作也只锁定必要的行。

工作原理:使用数据在某个时间点的快照

意味着,无论事务运行多长时间,都可以看到数据的一致视图,也意味着不同的事务可以在同一时间看到同一张表中的不同数据

实现:

InnoDB在每个事务启动时分配一个事务ID实现mvcc,在该事务中修改记录时,将向Undo日志写入一条说明如何恢复该更改的Undo记录,并且事务的回滚指针指向该Undo日志记录。这就是事务如何在需要时执行回滚的方法。

当不同的会话读取聚簇主键索引记录时,InnoDB会将该记录的事务ID与该会话的读取视图进行比较。如果当前状态下的记录不应可见(更改它的事务尚未提交),那么Undo日志记录将被跟踪并应用,直到会话达到一个符合可见条件的事务ID。这个过程可以一直循环到完全删除这一行的Undo记录,然后向读取视图发出这一行不存在的信号。

聚簇主键索引: InnoDB是MySQL数据库的一个存储引擎,它支持使用聚簇主键索引。聚簇主键索引是按照表的主键来组织数据存储的一种方式,表中的数据行按照主键的顺序存储,这有助于提高查询性能

所有Undo日志写入也都会写入Redo日志,因为Undo日志写入是服务器崩溃恢复过程的一部分,并且是事务性的。

在记录中保留这些额外信息带来的结果是,大多数读取查询都不再需要获取锁。它们只是尽可能快地读取数据,确保仅查询符合条件的行即可。缺点是存储引擎必须在每一行中存储更多的数据,在检查行时需要做更多的工作,并处理一些额外的内部操作。MVCC仅适用于REPEATABLE READ和READ COMMITTED隔离级别。READ UNCOMMITTED与MVCC不兼容[13],是因为查询不会读取适合其事务版本的行版本,而是不管怎样都读最新版本。SERIALIZABLE与MVCC也不兼容,是因为读取会锁定它们返回的每一行。

MVCC仅适用于REPEATABLE READ和READ COMMITTED隔离级别。READ UNCOMMITTED与MVCC不兼容,是因为查询不会读取适合其事务版本的行版本,而是不管怎样都读最新版本。SERIALIZABLE与MVCC也不兼容,是因为读取会锁定它们返回的每一行

用于在任何给定时间只在一个节点上接受写操作。这在管理一致性方面具有优势,但在需要将数据写入多台服务器或多个地区时,会导致需要做出取舍。MySQL提供了一种原生方式来将一个节点执行的写操作分发到其他节点,这被称为复制。

源节点为每个副本节点提供一个线程,该线程作为复制客户端登录,当写入发生时会被唤醒,发送新数据。

一主多副的多个MySQL服务器拓扑树:

对于在生产环境中运行的任何数据,都应该使用复制并至少有三个以上的副本,理想情况下应该分布在不同的地区(在云托管环境中,称为region)用于灾难恢复计划。

MySQL将表的元数据重新设计为一种数据字典,包含在表的.ibd文件中。这使得表结构上的信息支持事务和原子级数据定义更改。在操作期间,我们不再仅仅依赖information_schema来检索表定义和元数据,而是引入了字典对象缓存,这是一种基于最近最少使用(LRU)的内存缓存,包括分区定义、表定义、存储程序定义、字符集和排序信息。服务器访问表的元数据的方式的这一重大变化减少了I/O,非常高效。特别是当前访问最活跃的那些表,在缓存中最常出现。每个表的.ibd和.frm文件被替换为已经被序列化的字典信息(.sdi)。

InnoDB是MySQL的默认事务型存储引擎,也是最重要、使用最广泛的引擎。它是为处理大量短期事务而设计的,这些事务通常是正常提交的,很少会被回滚。InnoDB的性能和自动崩溃恢复特性,使得它在非事务型存储需求中也很流行。

InnoDB将数据存储在一系列的数据文件中,这些文件统被称为表空间(tablespace)。表空间本质上是一个由InnoDB自己管理的黑盒。

并且通过间隙锁(next-key locking)策略来防止在这个隔离级别上的幻读:InnoDB不只锁定在查询中涉及的行,还会对索引结构中的间隙进行锁定,以防止幻行被插入。

MySQL 8.0的另一个主要变化是删除了基于文件的表元数据存储,并将其转移到使用InnoDB表存储的数据字典中。

MySQL 8.0引入了原子数据定义更改。

MySQL通过API来与存储引擎交互需要处理的数据行,

  • Title: MySQL架构
  • Author: RenXin
  • Created at : 2024-01-19 17:54:24
  • Updated at : 2024-05-27 17:12:18
  • Link: https://blog.renxin.space/middleware/database/mysql_structure/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments