我所理解的MySQL之一:基础架构

今天MySQL教程栏目为大家介绍我所理解的基础架构。

我所理解的MySQL之一:基础架构

作为一个正经的 crud 工程师,与数据库的交互是日常工作中比重较大的内容,比如日常迭代的增删改查、处理历史数据、优化 sql 性能等等。随着项目数据量的增长,从前为了赶项目进度而埋下的深坑正慢慢显露它们的威力,这也让我不得不全面且深入的学习 mysql,而不仅仅是停留在基础的 crud 上。

MySQL 系列的第一篇,主要介绍 MySQL 的基础架构以及各个组成部分的功能,包括 Server 层的 bin log 和 InnoDB 特有的 redo log 这两种日志模块。

1. MySQL 架构简介

根据 DB-Engines 发布的最受欢迎的数据库管理系统排行榜,MySQL 稳坐第二把交椅。

2020年9月最受欢迎的DBMS排行榜

作为最受欢迎的关系型数据库管理系统之一,MySQL 采用的是C/S架构,即 Client & Server 架构。比如开发者使用 Navicat 连接到 MySQL,那么前者就是客户端,后者就是服务端。

同时,MySQL 也是单进程多线程的数据库。这很好理解,正在运行的 MySQL 实例就是那个“单进程”,而在这个进程中会有很多个线程,比如主线程 Master ThreadIO Thread 等,这些线程被用于处理不同的任务。

2. MySQL 组成部分

前面说到 MySQL 采用的是C/S架构,用户通过客户端连接到 MySQL 服务器,然后提交 SQL 语句到服务器,然后服务器就会把执行结果返回给客服端。

在这一小节的内容中,我们主要关注 MySQL 服务端的逻辑组成,先来看一张图。

MySQL 逻辑架构图

从上图可以看到,与客户端的交互中,MySQL 的服务端分别经过了连接器、查询缓存、分析器、优化器、执行器和存储引擎这几部分。

下面就以一条简单的查询语句来描述 MySQL 服务端的各组成部分及它们所起的作用。

2.1 连接器

在客户端提交查询语句之前,需要与服务端建立连接。所以最先来到的是连接器,连接器的作用就是负责与客户端建立、管理连接,同时查询用户的权限

需要注意的是:

连接器只获取用户的权限,并不做校验,校验是在查询缓存或执行器才进行。一旦建立连接同时获取用户的权限之后,只有建立新的连接才会刷新用户权限。对于长时间没有发送请求的客户端,连接器会自动断开连接。这里的「长时间」是由 wait_timeout 参数来决定的,它的默认值为8小时。

2.2 查询缓存

在经过连接器的建立连接、获取用户权限之后,接下来用户可以提交查询语句了。

最先经过的是查询缓存部分,由它的名字也能够猜到,查询缓存的作用就是查询 MySQL 是否执行过客户端提交的查询语句,如果这条 SQL 之前执行过,并且用户对该表有执行该语句的权限,就会直接返回之前执行的结果。

所以在某些时候,多次执行一句 SQL 并不能得到它的平均执行时间,因为查询缓存的关系,后面的执行时间往往比第一次执行要短。

如果你不想使用缓存,可以在每次查询后都用 update 语句更新表,当然这是非常麻烦并且憨的方法。MySQL也提供了相应的配置项—— query_cache_type,你可以在 my.cnf 文件中将 query_cache_type 设置为0以关闭查询缓存。

需要注意的是:

查询缓存部分是以 key-value 形式进行存储的,key 为查询语句,value 是查询结果。当对数据表进行更新时,关于这张表的所有查询缓存都会失效,所以一般来说查询缓存的命中率是很低的。在 MySQL 8.0 的版本中,查询缓存的功能已经被删除。

2.3 分析器

我使用的 MySQL 版本是5.7.21,所以客户端提交的查询语句会走查询缓存,如果没有命中,那么将继续往下走,来到分析器。

分析器会对提交的语句进行词法分析(解析语句)和语法分析(判断语句是否符合 MySQL 的语法规则),所以分析器的作用就是解析 SQL 语句并检查其合法性

需要注意的是:

MySQL 在检查 SQL 语句合法性时,仅会在最先不符合 MySQL 语法规则的地方提示错误,并不会将 SQL 语句中所有语法错误的地方全部展示。

举个例子:

select * form user_info limit 1;复制代码

上面这句 SQL 有两个错误,第一是 from 拼写错误,第二是不存在 user_info 这张表,在执行之后,MySQL只会提醒一个错误,下面展示了三次执行 SQL 的结果信息。

第一次的执行信息:1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'form user_info limit 1' at line 1, Time: 0.000000s修改为from后第二次的执行信息:1146 - Table 'windfall.user_info' doesn't exist, Time: 0.000000s修改为 user 表后第三次的执行信息:OK, Time: 0.000000s复制代码

2.4 优化器

在校验了 SQL 语句的合法性之后,MySQL 已经知道用户提交的语句是干什么的了,但是在真正执行之前,还需要经过非常“玄学”的优化器。

你相信玄学吗

优化器的作用是为 SQL 语句生成最优的执行计划

之所以说优化器很“玄学”,是因为它在优化 SQL 语句的过程中可能会生成出乎用户意料之外的执行计划(索引选择、多表关联连接顺序、隐式函数转换等)。当然优化器有时候也会“选错”索引,这与数据量、索引统计信息等因素有关。

需要注意的是:

如果你需要优化一条生产环境的 SQL,请尽量在本地还原与生产环境数据量相同的表,然后根据执行计划进行优化。在写查询语句的时候,一定要考虑到索引的最左匹配原则(关于最左匹配原则的整理在索引篇再写)。

关于 MySQL 优化器的工作流程,可以看看这篇博客:MySQL 优化器原来是这样工作的

MySQL 的执行计划也是一项必须要掌握的技能,这篇博客写得非常详细,值得一读:不会看 Explain执行计划,劝你简历别写熟悉 SQL优化

2.5 执行器

在优化器生成了 MySQL 认为最优的执行计划之后,最后来到了执行器,执行器的作用当然就是执行SQL语句了。

但是在执行之前,先要做权限验证,验证用户对表是否有查询权限。然后再根据表定义的引擎类型,去使用相对应引擎提供的接口来对该表进行条件查询,最后将该表所有满足条件的数据行作为结果集返回客户端,这样整个 SQL 的执行就结束了。

需要注意的是:

在执行器执行 SQL 语句前会做校验:判断用户对表是否具有操作权限。

2.6 存储引擎

MySQL 支持的存储引擎有很多种,比如:InnoDB、MyISAM、Memory 等等。

2.6.1 InnoDB

InnoDB 是当下最常用的的 MySQL 存储引擎,同时也是 MySQL 5.5 之后的默认存储引擎。

InnoDB 支持事务、MVCC(多版本并发控制)、外键、行级锁和自增列。但是 InnoDB 不支持全文索引,同时它占用的数据空间更大。

即构数智人 即构数智人

即构数智人是由即构科技推出的AI虚拟数字人视频创作平台,支持数字人形象定制、短视频创作、数字人直播等。

即构数智人 36 查看详情 即构数智人

2.6.2 MyISAM

MyISAM 是 MySQL 5.1 及之前的默认存储引擎,支持全文索引、压缩、空间函数、表级锁。

MyISAM 的数据以紧密格式存储所以占用空间更小,它拥有较高的插入和查询速度,但是 MyISAM 不支持事务,且崩溃后无法安全恢复。

2.6.3 Memory

Memory 的所有数据都保存的内存中,由于不需要磁盘 I/O,所以它的速度比 MyISAM 和 InnoDB 快了一个数量级。但如果数据库关闭或重启,Memory 引擎的数据就会消失。

Memory 支持 Hash 索引,但由于它使用表级锁,因此并发写入的性能比较低。

值得一提的是,MySQL 中的临时表,一般是用 Memory 表保存的,如果中间表数据量过大或含有 BLOB 类型或 TEXT 类型的字段,就会使用 MyISAM 表。

关于存储引擎,由于本人接触的比较少,等看完《MySQL技术内幕:InnoDB存储引擎》之后再整理,这里只是简单地提一下。

3. 日志模块

前面所说的执行流程主要是描述查询语句,如果是更新语句还涉及到 MySQL 的日志模块。

从客户端到执行器的之间的逻辑查询语句和更新语句是相同的,只是在到执行器这一层的时候,更新语句会和 MySQL 的日志模块产生交互,这是查询语句和更新语句不一样的地方。

3.1 物理日志 redo log

3.1.1 redo log 中记录的内容

对于 InnoDB 存储引擎来说,它有一个特有的日志模块——物理日志(重做日志)redo log,它是 InnoDB 存储引擎的日志,它所记录的是数据页的物理修改

举个例子,现在有一张 user 表,有一条主键 id=1,age=18 的数据,然后用户提交了下面这条 SQL,执行器准备执行。

update user set age=age+1 where id=1;复制代码

对于这条 SQL,在 redo log 中记录的内容大致是:将 user 表中主键 id=1 行的 age 字段值修改为19

3.1.2 WAL

MySQL 的更新持久化逻辑运用到了 WAL(Write-Ahead Logging,写前日志记录) 的思想:先写日志,再写磁盘。

需要注意的是这里的写日志也是写到磁盘中,但由于日志是顺序写入的,所以速度很快。而如果没有 redo log,直接更新磁盘中的数据,那么首先需要找到那条记录,然后再把新的值更新进入,由于查询和读写I/O,就相对会慢一些。

最后,当 InnoDB 引擎空闲的时候,它会去执行 redo log 中的逻辑,将数据持久化到磁盘中。

3.1.3 redo log 日志文件

redo log 日志文件大小是固定的,我把它理解为一个循环链表,链表的每个节点都可以存放日志,在这个链表中有两个指针:write(黑) 和 read(白)。

循环链表

最开始这两个指针都指向同一个节点,且节点日志元素都为空,表示此时 redo log 为空。当用户开始提交更新语句,write 节点开始往前移动,假设移动到3的位置。而此时的情况就是 redo log 中有1-3这三个日志元素需要被持久化到磁盘中,当 InnoDB 空闲时,read 指针往前移动,就代表着将 redo log 持久化到磁盘。

但这里有一种特殊情况,就是 InnoDB 一直没有空闲,write 指针一直在写入日志,直到它写到5的位置,再往前写又回到了最开始1的位置(也就是上图的位置,但不同的是链表节点中都存在日志数据)。

此时发现1的位置已经有日志数据了,同时 read 指针也在。那么这时候 write 指针就会暂停写入,InnoDB 引擎开始催动 read 指针移动,把 redo log 清空掉一部分之后再让 write 指针写入日志文件。

3.1.4 redo log 的作用

我们已经知道,redo log 中记录的是数据页的物理修改,所以 redo log 能够保证在数据库发生异常重启时,记录尚未写入磁盘,但是在重启后可以通过 redo log 来“redo”,从而不会发生记录丢失的情况,保证了事务的持久性。

这一能力也被称作 crash-safe

3.2 归档日志 bin log

前面说到 redo log 是 InnoDB 特有的日志,而 bin log 则是属于 MySQL Server 层的日志,在默认的 Statement Level 下它记录的是更新语句的原始逻辑,即 SQL 本身。

另外需要注意的是:

bin log 的日志文件大小并不固定,它是“追加写入”的模式,写完一个文件后会切换到下一个文件写入。bin log 没有 crash-safe 的能力。bin log 是在事务最终提交前写入的,而 redo log 是在事务执行中不断写入的。

3.2.1 bin log 的作用

与 redo log 不同的是,bin log 常用于恢复数据,比如说主从复制,从节点根据父节点的 bin log 来进行数据同步,实现主从同步。

3.3 两阶段提交

为了让 redo log 和 bin log 的状态保持一致,MySQL 使用两阶段提交的方式来写入 redo log 日志。

在执行器调用 InnoDB 引擎的接口将写入更新数据时,InnoDB 引擎会将本次更新记录到 redo log 中,同时将 redo log 的状态标记为 prepare,表示可以提交事务。

随后执行器生成本次操作的 bin log 数据,并写入 bin log 的日志文件中。

最后执行器调用 InnoDB 的提交事务接口,存储引擎把刚写入的 redo log 记录状态修改为 commit,本次更新结束。

在这个过程中有三个步骤 add redo log and mark as prepare -> add bin log -> commit,即:

写入 redo log 日志并标记为 prepare写入 bin log提交事务

如果在第二个步骤,也就是写入 bin log 之前系统崩溃或重启,启动后由于 bin log 中没有记录,会将 redo log 中的记录回滚至执行本次更新语句前。

如果在第三个步骤前,也就是提交之前系统崩溃或重启,即便没有 commit 但是满足 redo log 中记录为 prepare 状态并且 bin log 中也有完整记录,在重启后会自动 commit,并不会回滚。

4. 小结

本文主要介绍 MySQL 的基础架构以及各个组成部分的功能,最后介绍了 MySQL Server 层的 bin log 和 InnoDB 特有的 redo log 这两种日志模块。

5. 温故知新

以下的几个问题是对本文所描述内容的提问,巩固知识,正所谓“温故而知新,可以为师矣”。

如果查询语句中字段不存在、字段有歧义、关键字拼写错误,是由哪个部分报错?如果用户对表没有查询权限,是哪个部分报错?为什么 MySQL 的查询缓存会无效?一条 select 查询语句是如何执行的?MySQL 常用的存储引擎有哪些?MySQL 的日志模块有哪些?分别起到什么作用?redo log 写满了怎么办?如何理解 redo log 的两阶段提交?

redo log 和 bin log 的区别?

更多相关免费学习推荐:mysql教程(视频)

以上就是我所理解的MySQL之一:基础架构的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/355739.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月6日 00:30:38
下一篇 2025年11月6日 00:32:03

相关推荐

  • 如何用Golang实现微服务配置中心_Golang 配置中心开发与实践

    使用Golang基于Etcd可构建轻量级配置中心,支持配置存储、动态更新、多环境管理、版本控制和访问控制;通过HTTP接口实现配置获取与监听,客户端利用goroutine实时感知变更,结合缓存、批量查询与健康检查优化生产可用性。 在微服务架构中,配置中心是核心组件之一。它解决了服务配置分散、变更困难…

    2025年12月16日
    000
  • 如何在Golang中实现云原生配置中心服务

    使用Golang结合Etcd实现云原生配置中心,支持动态更新、多环境隔离与版本控制。通过clientv3操作Etcd存储配置,提供RESTful API供客户端获取和监听配置,利用Watch机制实现实时推送,配合本地缓存与回调函数实现配置热加载,构建轻量高效的服务解耦方案。 在云原生架构中,配置中心…

    2025年12月16日
    000
  • Golang如何实现简单的内容管理后台

    答案:用Golang标准库net/http搭建Web服务,定义Post结构体实现内容模型,通过内存切片存储数据并提供RESTful API接口,使用%ignore_a_1%/template渲染前端页面,配合JavaScript调用API完成增删改查,初期可忽略安全机制,后期需添加认证与防护措施。 …

    2025年12月16日
    000
  • Golang结构体中的指针字段有什么作用_Golang结构体引用类型应用实例

    指针字段用于共享数据、节省内存和实现可变性。通过指向同一地址,多个结构体实例可共享底层数据,避免复制开销并实现同步更新;在大型对象或配置共享场景中提升性能;支持nil状态,适用于延迟初始化,如数据库连接池懒加载;合理使用可优化性能与状态管理。 在Go语言中,结构体的指针字段主要用于共享数据、节省内存…

    2025年12月16日
    000
  • Go语言中内嵌方法访问“父”字段的机制探讨

    在go语言中,内嵌结构体的方法无法直接访问其外部(“父”)结构体的字段,因为方法的接收者明确是内嵌类型,不持有外部结构体的引用。本文将深入探讨这一机制,解释其背后的原理,并提供两种解决思路:通过显式传递“父”引用作为替代方案,以及更符合go惯用法的api设计,即采用外部函数或服务对象来处理数据持久化…

    2025年12月16日
    000
  • Go语言中内嵌结构体方法访问宿主字段的机制与实践

    在go语言中,内嵌结构体的方法无法直接访问其宿主(父级)结构体的字段或方法,因为方法的接收者类型是固定的,不具备宿主上下文。本文将深入探讨这一机制,并通过代码示例验证其局限性,同时提供一种通过接口引用宿主的间接解决方案,并最终建议采用更符合go语言习惯的api设计模式,即分离数据和操作,以实现更清晰…

    2025年12月16日
    000
  • Go语言中嵌入类型方法访问“父”字段的机制与最佳实践

    在go语言中,嵌入类型的方法无法直接访问其宿主(“父”)结构体的非嵌入字段。这是因为嵌入机制是类型提升而非继承,方法的接收器始终是其声明时的类型。本文将深入探讨这一限制的原因,并提供两种解决方案:一种是手动传递“父”引用(不推荐),另一种是重新思考api设计,采用更符合go惯例的显式依赖方式,如db…

    2025年12月16日
    000
  • 深入理解Go语言嵌入:方法与宿主结构体字段的访问机制

    Go语言中,嵌入类型的方法接收者是嵌入类型本身,而非其宿主(embedding)结构体。这意味着嵌入方法无法直接访问宿主结构体的非嵌入字段。若需实现类似功能,可考虑在嵌入类型中引入一个接口字段来引用宿主,但这会增加复杂性。更推荐的设计模式是采用 `db.Save(user)` 形式的函数式API,以…

    2025年12月16日
    000
  • Go语言中将MySQL数据高效转换为JSON的实用指南

    本文旨在提供一个在go语言中将mysql数据库表数据高效转换为json格式的教程。我们将探讨在处理数据库扫描结果时,如何避免所有数据类型都被识别为字节数组(`[]byte`)的问题,并通过动态类型检查和适当的数据转换,确保数值、布尔值等原始类型在json中得到正确表示,从而生成符合预期的json输出…

    2025年12月16日
    000
  • Go database/sql:深度解析预处理语句与直接查询的机制与实践

    go 语言 `database/sql` 包为数据库操作提供了通用接口。本文深入探讨了预处理语句与直接查询在 go 中的实现机制,阐明了驱动在参数化处理中的核心作用。我们将解释为何 `query/queryrow` 也能安全接受参数,并分析预处理语句在防范 sql 注入、提升重复查询性能方面的独特优…

    2025年12月16日
    000
  • Go语言数据库操作:深入理解预处理语句与直接查询

    Go语言的`database/sql`包提供了一个高度抽象的数据库接口,其设计旨在兼容各种SQL数据库系统。无论是使用预处理语句还是直接查询并传递参数,`database/sql`包都通过底层的数据库驱动程序确保了参数的安全性处理,有效防止SQL注入。预处理语句尤其适用于高频重复执行的查询,能通过数…

    2025年12月16日
    000
  • 深入理解 Go database/sql:预处理语句的价值与参数处理机制

    go语言的 `database/sql` 包提供了一个通用的数据库接口,抽象了底层sql数据库的差异。尽管 `query` 和 `queryrow` 等方法允许直接传递参数,看似与预处理语句功能等价,但其内部实现仍依赖数据库驱动进行安全的参数转义和处理。预处理语句通过两步式操作(准备与执行),赋予驱…

    2025年12月16日
    000
  • Golang MySQL连接:正确指定数据库的实践指南

    本教程旨在解决%ignore_a_1%使用`go-sql-driver/mysql`连接mysql时,因尝试通过`use`语句选择数据库而导致的“no database selected”错误。核心内容是指导开发者应在dsn(data source name)中直接指定目标数据库,而非在连接建立后执…

    2025年12月16日
    000
  • Go语言中将IP地址等二进制数据存储到MySQL的BINARY类型字段

    在Go语言中,将IP地址等二进制数据存储到MySQL的`BINARY(N)`类型字段时,直接传递`[]byte`或`[N]byte`会遇到类型转换错误。本文将详细介绍如何通过将`[]byte`数据类型显式转换为`string`类型,利用`go-sql-driver/mysql`驱动的特性,有效解决这…

    2025年12月16日
    000
  • 如何在Go语言中高效地将二进制数据存储到MySQL的BINARY列

    本文详细阐述了在Go语言中使用`database/sql`驱动向MySQL的`BINARY(X)`列存储二进制数据(如IP地址)的有效方法。针对直接传递`[]byte`或`[4]byte`导致的类型转换错误,文章提出了将`net.IP`切片显式转换为`string`的解决方案,并提供了示例代码,确保…

    2025年12月16日
    000
  • Go语言与MySQL:高效存储二进制IP地址到BINARY字段

    在使用go语言的`go-sql-driver/mysql`驱动程序将二进制ip地址(如`net.ip`或`[]byte`)存储到mysql的`binary(4)`字段时,直接传递这些类型常会导致错误。本教程将详细介绍如何通过将`[]byte`类型的ip地址显式转换为`string`类型来解决这一问题…

    2025年12月16日
    000
  • Go语言与MySQL:正确存储二进制IP地址数据

    在go语言中,将二进制ip地址(如`net.ip.to4()`返回的`[]byte`)存储到mysql的`binary(4)`类型字段时,直接传递`[4]byte`数组或`net.ip`切片会导致类型转换错误。本文将详细探讨常见的存储误区,并提供一种简洁有效的解决方案:通过将`[]byte`切片显式…

    2025年12月16日
    000
  • Go语言中利用构建约束实现App Engine与标准SQL环境的条件编译

    本文将指导如何在go语言项目中,通过使用构建约束(`// +build` directives)优雅地解决google app engine (gae) 特定包(如`appengine/cloudsql`)与标准sql库在不同环境下的兼容性问题。我们将探讨如何利用`appengine`和`!appe…

    2025年12月16日
    000
  • Golang如何使用工厂模式创建对象

    Go语言通过接口和结构体实现工厂模式,封装对象创建过程。定义Database接口及MySQL、PostgreSQL实现,工厂函数NewDatabase根据类型返回对应实例,支持扩展与配置,提升代码可维护性。 在Go语言中,工厂模式通过函数或方法封装对象的创建过程,避免重复代码,提升可维护性。虽然Go…

    2025年12月16日
    000
  • Go语言库的跨环境兼容:利用构建约束处理App Engine与标准SQL

    本文将探讨Go语言库如何在Google App Engine (GAE) 和标准运行环境中实现代码的条件编译,尤其针对appengine/cloudsql包的兼容性问题。通过利用Go的构建约束(Build Constraints),开发者可以优雅地隔离特定于GAE的代码逻辑,如数据库连接,从而在不修…

    2025年12月16日
    000

发表回复

登录后才能评论
关注微信