一文流系列是作者苦于技术知识学了-忘了,背了-忘了的苦恼,决心把技术知识的要点一笔笔✍️出来,一图图画出来,一句句讲出来,以求刻在🧠里。
该系列文章会把核心要点提炼出来,以求掌握精髓,至于其他细节,写在文章里,留待后续回忆。
目前进度请查看:
:::info
https://www.yuque.com/u1949579/vtk1e4/fuq6986htl8yy9bg?singleDoc# 《我的技术栈-思维导图》
:::
Mysql 由来和简介
The world’s most popular open source database(世界上最受欢迎的开源数据库)
官网链接:https://www.mysql.com/
MySQL 作为一款开源的关系型数据库管理系统,是目前市场上最主流的关系型数据库之一。其受欢迎程度源于其强大的功能、出色的性能、高可靠性以及开源的特性,这些优势使得 MySQL 成为众多开发者和企业的首选数据库解决方案。
Mysql的诞生
MySQL 的诞生源于瑞典的 TcX 公司,创始人 Monty Widenius 在开发公司的一个报表工具时,需要一个高效的数据库管理系统来存储和处理数据。由于当时现有的数据库解决方案要么成本高昂,要么无法满足其特定需求,Monty Widenius 决定自己开发一个数据库系统。最初的开发工作主要由 Monty Widenius 和他的团队在 TcX 公司内部进行,他们致力于打造一个简单、快速且易于使用的数据库管理系统,以满足公司内部的业务需求。这个早期版本的数据库系统逐渐发展起来,并在 TcX 公司内部得到了广泛应用,为 MySQL 的后续发展奠定了基础。
版本演进和主要特性
1979年
Monty Widenius发明了Mysql前身:Unireg
1985年
Mysql AB公司前身成立,Mysql实现了第一款存储引擎:ISAM
1996年 V1.0
MySQL 1.0 正式发布,具备了基本的数据库管理功能,如数据的存储、查询和更新等。
1999-2000年
MySQL AB 公司成立。期间开发了Berkeley DB引擎,支持事务处理。
2000年
MySQL 开源,采用了 GPL(通用公共许可证)许可协议。同年4月ISAM引擎更名MyISAM。
2001年 V3.23
MySQL 集成了 InnoDB 存储引擎。后在V4.0版本正式发布。
2004年 V4.1
MySQL 4.1 版本发布,该版本引入了子查询、字符集支持等重要特性。
2005年 V5.0
MySQL 5.0 版本发布,加入了存储过程、视图、触发器等一系列企业级特性,并且将 InnoDB 存储引擎作为默认存储引擎。
2008年-2010年 V5.5 LTS
2008年MySQL 被 Sun 公司收购,随后在2010年Oracle收购Sun 公司。同年MySQL 5.5 版本发布。
2013年 V5.6
MySQL 5.6 发布,引入了多项优化,改进InnoDB,引入了 JSON 数据类型、窗口函数、在线 DDL 操作等新特性;提供了更强大的安全特性,如角色基于访问控制和增强的身份验证插件。
2015年 V5.7
MySQL 5.7 发布,引入了 MySQL Group Replication、GIS 数据类型、NoSQL 接口等先进特性;提供了默认的 SSL 连接和更多的安全选项;增强了对 JSON 数据类型的支持,提供了丰富的 JSON 函数和操作符。
2018年 V8.0
MySQL 8.0 发布,引入了更多的 JSON 支持、事务数据字典、窗口函数和更好的查询优化器等;默认的字符集为 UTF-8mb4,更好地支持全球语言文字;提供了默认的加密连接和新的用户账户管理;采用了新的并行复制技术,提升了复制性能。
2024年 V8.4 LTS
Mysql 8.4 LTS 发布,InnoDB 引擎优化,支持版本混合运行,降低了新版本的引入风险。同年10月15日发布 Mysql 9.1,支持 IF NOT EXISTS 选项创建视图;支持原子操作的增删表语句,增加了对 Java 存储程序和 VECTOR 数据类型的支持。
Mysql 体系结构
MySQL 的体系结构大致可分为如下层级:
- 网络连接层:负责与客户端建立连接、获取权限以及维持和管理连接;
- 数据库服务层:包括系统管理和控制工具、连接池、NoSql & SQL 接口、解析器、预处理器、查询优化器、执行器和缓存。
- 存储引擎层:负责数据的存储和提取,不同的存储引擎通过通用 API 与数据库服务层对接。
- 系统文件层:包含了 MySQL 中存储数据的底层文件,如日志文件、数据文件、配置文件
Mysql 运行机制
MySQL 的运行机制主要包括以下几个关键阶段:
- 当客户端发起连接请求时,MySQL 首先进行连接管理,验证用户身份和权限。
- 接着,查询缓存会尝试查找是否存在与该查询语句匹配的缓存结果,如果命中,则直接返回缓存数据。
- 若未命中,则进入语法解析阶段,将 SQL 语句解析为抽象语法树。
- 然后,预处理器会基于语法树进行语法和语义的检查,确保语句的正确性,并进一步优化语法树。
- 随后,查询优化器会对解析后的语句进行优化,生成多个执行计划,并从中选择最优的执行计划,考虑索引的使用、表的连接顺序等因素,以降低查询成本。
- 最后,执行器根据优化后的执行计划,与存储引擎进行交互,执行实际的数据读取、写入或修改操作,并将结果返回给客户端。
连接建立阶段
通信方式与协议
MySQL 客户端与服务端通过 TCP 协议采用**半双工通信**。与全双工(如网卡可同时收发数据)和单工(如电视遥控器、电脑主机与显示器)通信不同,半双工虽允许双向传输,但同一时刻仅一方可传数据。
客户端发 SQL 语句给服务端时是一次性发送(数据包过大可调整 max_allowed_packet
参数,默认 4M
,用 show variables like'max_allowed_packet';
查看),服务端回数据也是一次性发送。这导致数据传输中双方不能同时发数据,也无法控制已发送数据流程。比如客户端发 SQL 后只能等服务端发完结果,即便已得所需数据。
连接过程与线程管理
- 认证流程:客户端连接 MySQL 服务端时,服务端校验用户名、密码等,启用时还包括 SSL/TLS 证书,全部通过才能成功连接。
- 线程管理:连接建立后 MySQL 为其绑定线程,线程池预先创建并管理线程,新请求分配空闲线程处理,处理完返回池中等待复用。
线程状态查看:使用
show processlist
或show full processlist
命令查看线程状态,常见状态如 login(初始连接未认证)、executing(执行语句)、cleaning up(处理完命令准备释放内存和重置变量)等,不同状态反映线程工作阶段,助于排查问题和优化性能。
查询缓存阶段
缓存原理与机制
MySQL 的查询缓存是一项用于提高查询性能的重要机制,它主要对 SELECT 查询的结果以及对应的 SQL 语句进行缓存。
- 缓存原理:基于哈希表结构,以 SQL 语句为键、结果集为值缓存 SELECT 查询结果。新查询先权限验证后查缓存,若与缓存中 SQL 完全一致(包括多种细节)则直接返回缓存结果。
- 有效期管理:缓存结果有有效期,有效期内相同查询复用缓存。但数据库表有数据修改操作(如增删改表结构等)时,相关缓存结果立即失效清除,以保证数据一致性。
缓存配置与优化
MySQL 提供了一系列与查询缓存相关的配置参数:
参数名称 | 作用描述 | 取值及含义 | 优化建议 |
---|---|---|---|
query_cache_type | 设置查询缓存开启模式 | OFF:关闭查询缓存,不缓存也不检索结果; ON:开启查询缓存; DEMAND:按需开启,有SQL_CACHE关键字的SELECT语句才缓存 | 读多写少设为ON,写多设为OFF |
query_cache_size | 指定查询缓存总内存空间大小 | 单位为字节,如设置具体数值则启动时分配对应内存空间用于缓存 | 依数据库实际使用情况和服务器内存配置调整,小型应用且内存有限可先设16MB再优化 |
query_cache_limit | 限制单个查询结果能被缓存的最大字节数 | 默认值为1MB | 大结果集查询频率低或对实时性要求高时可增大;内存有限且多小结果集查询可减小 |
query_cache_min_res_unit | 缓存内存存储块最小大小 | 默认值是4096字节 | /(主要说明对内存分配操作与碎片的影响) |
query_cache_wlock_invalidate | 表被写锁时是否返回缓存结果 | 默认值为OFF,表示可以返回 | / |
特殊的缓存失效情况**:**
含 NOW () 等不确定函数、用临时表、查特定数据库表、存储函数等内的查询不缓存。
优化建议:
SQL 语句保持一致,对稳定查询用 SQL_CACHE 强制缓存,写频繁表少缓存或分区,定期用 SHOW STATUS LIKE ‘Qcache%’ 监控缓存状态(如命中、插入、低内存清理、未缓存次数)来优化配置与策略提升性能。
解析与优化阶段
语法解析与预处理
当查询缓存未命中时,SQL 语句就会进入语法解析与预处理阶段。
- 词法分析:解析器将 SQL 语句按分词规则分解成 Token,如 “SELECT name FROM user WHERE id = 1;” 会被分成多个 Token。
- 语法分析与解析树生成:语法分析器依据 MySQL 语法规则对 Token 检查分析,判断语句是否合规并生成解析树,如 “SELECT * FROM users WHERE age> 20;” 能正确识别各部分语法结构构建解析树。
- 预处理器合法性检查:预处理器基于 MySQL 规则进一步检查解析树,包括确认表和列的存在性,如查询不存在的表会报错;检查名字和别名歧义;处理数据类型兼容性等语义问题,若语句有语法或语义问题,此阶段会检测出并返回错误信息阻止后续查询执行。
查询优化策略
查询优化器的核心任务是为输入的 SQL 查询语句生成最优的执行计划,以提高查询效率和性能。它会综合考虑多种因素,运用一系列复杂的优化策略来实现这一目标。
- 静态优化:在查询编译阶段,依据 SQL 语句语法和语义,运用等价变换等策略化简条件。如对 “SELECT * FROM products WHERE (price> 10 AND price < 20) OR (price = 15);” 化简为 “SELECT * FROM products WHERE price > 10 AND price <= 20;”,降低查询条件复杂度。
- 动态优化:查询执行中,依数据库实际数据分布和统计信息调整计划。如据索引统计信息,对 “SELECT * FROM users WHERE age = 25;” 选择合适索引避免全表扫描;在多表连接查询中,分析连接顺序成本,如 “SELECT * FROM orders o JOIN customers c ON o.customer_id = c.id JOIN products p ON o.product_id = p.id;” 依表数据量和统计信息选最优连接顺序,减少中间结果集。
执行引擎阶段
当查询执行器接收到由查询优化器生成的执行计划后,它会根据 SQL 语句中涉及的表的存储引擎类型,调用相应的应用程序编程接口(API),与底层的存储引擎进行交互,以完成数据的读取、筛选、聚合等操作,最终获取到查询结果并返回给客户端。
Mysql 存储引擎
存储引擎 | 存储限制 | 事务支持 | 索引支持 | 锁颗粒 | 数据压缩支持 | 外键支持 |
---|---|---|---|---|---|---|
InnoDB | 受限于操作系统和硬件 | 支持事务 | 支持索引 | 行级锁 | 支持压缩 | 支持 |
MyISAM | 受限于操作系统和硬件 | 不支持事务 | 支持索引 | 表级锁 | 支持压缩 | 不支持 |
Memory | 受限于内存大小 | 不支持事务 | 支持索引 | 表级锁 | 不支持 | 不支持 |
MySQL 存储引擎是数据库底层软件组件,它负责处理数据的存储、查询、更新以及删除等操作。从本质上讲,它是数据库管理系统中用于存储和管理数据的一种机制。存储引擎就像是一个 “黑盒子”,它接收来自数据库上层的请求,然后按照特定的规则和算法来执行相应的操作。存储引擎对数据处理和性能有着关键影响。它不仅决定了数据存储的方式、索引的构建以及查询的执行效率,还直接影响着数据库的整体稳定性和可靠性。例如,不同存储引擎在处理高并发事务时,表现出不同的性能和特点。像 InnoDB 引擎支持事务安全表,能确保数据的完整性和一致性,在处理大量并发事务时表现出色;而 MyISAM 引擎虽然不支持事务,但在读取和插入操作上具有较高的速度,适用于一些对事务要求不高的场景。
InnoDB
InnoDB 是 MySQL 的数据库引擎之一,由 Innobase Oy 公司开发,后被甲骨文公司并购。它是 MySQL 默认的存储引擎,为 MySQL AB 发布 binary 的标准之一。
InnoDB 提供了事务安全(ACID 兼容)的特性,以及对并发控制和恢复能力的支持。它被设计用来最有效地利用以及使用内存和 CPU,在处理巨大数据量时具有良好的性能表现。
InnoDB主要特性:
- **事务支持: **InnoDB 支持事务,使用事务日记来记录数据库的所有更改操作,事务日志先于数据文件写入磁盘,通过先写日志再写数据的方式保证数据的持久性和完整性。
- **行级锁: **InnoDB 支持行级锁,能够对数据行进行精确锁定,而不是锁定整个表。在高并发场景下,多个事务可以同时操作不同行的数据,大大提高了数据库的并发处理能力。同时InnoDB支持多种锁类型,包括共享锁(S 锁)和排他锁(X 锁)。共享锁允许多个事务同时读取同一行数据,但不允许其他事务对该行进行修改;排他锁则在事务修改某行数据时使用,阻止其他事务对该行进行读或写操作,确保数据在修改过程中的一致性和完整性。
- MVCC(多版本并发控制): MVCC 通过为每行数据维护多个版本,使得不同事务在读取数据时可以看到不同时间点的版本,从而避免了锁的争用,提高了并发性能。事务在执行一致性读(如普通的
SELECT
语句)时,InnoDB 会根据事务的隔离级别和事务启动时间,选择合适的行版本提供给事务。这使得每个事务都能看到在事务开始时数据库的一致性快照,保证了数据的一致性和可重复读特性。理论上,InnoDB在默认的事务隔离级别(可重复读)上就可以达到序列化读的效果。 - **外键约束:**保证数据的完整性和正确性。
InnoDB 数据文件
在InnoDB中,主要包括 ibdata1、ibtmp1、{库名}/{表名}.ibd、{库名}/{表名}.frm 四类数据文件。这些文件都存储在 my.cnf 配置文件中 datadir 指向的文件目录中。
ibdata1 数据库基础文件以及共享的数据\索引文件
ibdata1通常是一个较大的文件,其初始大小由配置参数innodb_data_file_path
决定,并且在数据库运行过程中会根据存储的数据量和操作需求而增长。
在**共享表空间模式下,主要以B+树的结构存储所有的表数据和索引数据**。此外还存储了数据库的数据字典,双写缓冲区,存储更改缓冲区。
- 数据字典:包含了数据库中所有表的结构定义、存储过程和函数的信息、视图的定义等元数据
- 双写缓冲区:数据页从内存刷到磁盘时先写入该区域进行备份,用于数据落盘的故障恢复
- 存储更改缓冲区:缓存对辅助索引的修改操作。当对辅助索引进行插入、删除或更新操作时,如果相应的索引页不在缓冲池中,这些操作会被记录到更改缓冲区,之后在合适的时机合并到索引页中,以此减少磁盘 I/O 操作。
-- 数据文件,可以看到ibdata1文件默认大小12m,会自动扩展
MariaDB [(none)]> show variables like 'innodb_data_file_path';
+-----------------------+------------------------+
| Variable_name | Value |
+-----------------------+------------------------+
| innodb_data_file_path | ibdata1:12M:autoextend |
+-----------------------+------------------------+
1 row in set (0.006 sec)
ibtmp1 临时表数据文件
ibtmp1 文件存储基于磁盘的临时表数据,在复杂查询(如含子查询、分组操作等)产生大量数据需创建临时表且无法全存于内存时会被使用,创建或重建索引时,若数据排序操作不能在内存完成,它也会存储部分排序中间结果。
其大小随实际操作的数据量和类型变化,频繁读写会增加磁盘 I/O 操作,影响数据库性能。数据库性能优化时要关注其大小和使用情况,可通过调整系统参数或优化查询操作减少影响,比如发现它过大时,可适当增大缓冲池大小,在 MySQL 配置文件(my.cnf 或 my.ini)中设置 <font style="color:rgba(0, 0, 0, 0.85);">innodb_buffer_pool_size</font>
参数,若服务器内存充足,将该参数从默认值(如 8M)增大到合适值(如 4G),能显著减少索引创建或临时表操作中数据写入ibtmp1 文件的情况。
.ibd 表独立的数据\索引文件
在开启了**独立表空间模式(innodb_file_per_table=1,Mysql5.6之后默认
)后,每张表的表数据和索引数据**会被存储到对应的.ibd文件中。
-- 表空间配置,独立表空间配置默认开启
MariaDB [(none)]> show variables like 'innodb_file_per_table';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_file_per_table | ON |
+-----------------------+-------+
1 row in set (0.002 sec)
独立表空间文件的创建与表的创建同步进行,表中的行数据以页为单位存储于<font style="color:rgba(0, 0, 0, 0.85);">.ibd</font>
文件。在索引构建方面,InnoDB主要采用B+树索引 。若表设有主键,其主键索引即为聚簇索引,聚簇索引的叶子节点会直接存储行数据。而辅助索引的叶子节点,则存储着主键值与索引列的值,通过主键值便能进一步查找完整的行数据。这也就是回表的过程。回表查询在.ibd文件和ibdata1文件中是一样的。
好文推荐:Innodb表空间、段、区描述页分析与磁盘存储空间管理 - dev_song - 博客园
.frm 表定义文件
.frm
文件用于存储表的定义信息。它是一种与存储引擎相对独立的文件格式,无论是 InnoDB 还是 MyISAM 等存储引擎都会有.frm
文件来保存表的基本结构信息。包含表中列的详细信息,如列名、数据类型、列长度(对于变长类型)。以及记录了表所使用的字符集和排序规则。
InnoDB 逻辑存储结构
InnoDB的存储结构分为 5 级,从大到小依次为
- 表空间 Tablespace
- 段 Segment
- 区 Extent
- 页 Page
- 行 Row
- 页 Page
- 区 Extent
- 段 Segment
表空间(Tablespace)
表空间可以看做是 InnoDB 存储引擎逻辑结构的最高层 ,所有的数据都是存放在表空间中。默认情况下,InnoDB 会在数据目录下创建一个名为 ibdata1 的文件,大小为 12MB,这个文件就是对应的共享表空间(系统表空间)在文件系统上的表示。这个文件是自扩展文件,当不够用的时候它会自己增加文件大小。在一个 MySQL 服务器中,系统表空间只有一份。表空间(Tablespace)是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但是一个段只能属于一个表空间。数据库由一个或多个表空间组成,表空间从管理上可以划分为系统表空间、用户表空间、撤销表空间、临时表空间。
共享表空间(系统表空间)
System Tablespace,系统表空间可以对应文件系统上一个或多个实际的文件,默认情况下,InnoDB 会在数据目录下创建一个名为 ibdata1 的文件,大小为 12MB,这个文件就是对应的系统表空间在文件系统上的表示。这个文件是自扩展文件,当不够用的时候它会自己增加文件大小。在一个 MySQL 服务器中,系统表空间只有一份。系统表空间与独立表空间的一个非常明显的不同之处就是在表空间开头有许多记录整个系统属性的页面。
InnoDB 有四个最基本的系统表:
- SYS_TABLES:记录了表的名称、每个表对应唯一的 ID、表拥有列的个数、表的类型(包括一些文件格式,行格式等)、MIX_ID、表所属表空间的 ID 等;
- SYS_COLUMNS:记录了列所属表对应的 ID、列是表中的第几列、列名称、主数据类型、精确数据类型(比如是否允许 NULL 等)、列最多占用存储空间的字节数、列的精度【默认都是0】;
- SYS_INDEXES:记录了索引所属表对应的 ID、索引的唯一 ID、索引名称、索引包含列的个数、索引类型、索引根页面所在表空间 ID、索引根页面所在的页面号等;
- SYS_FIELDS:记录了索引列所属索引的 ID、索引列在某个索引中是第几列、索引列的名称等。
独立表空间
Mysql 5.6以前,默认是共享表空间模式,数据存放在ibdata1 文件。Mysql 5.6 以后默认为独立表空间模式(innodb_file_per_table = 1
),每张表都有其对应的以.ibd 后缀命名的表空间文件,这些文件独立存储该表的数据、索引等相关信息。
其他表空间
- 撤销表空间(Undo Tablespaces):专门用来存放 undolog 的日志文件。
- 临时表空间(Temporary Tablespaces):存放用户创建的临时表和磁盘内部临时表。
- 通用表空间(General Tablespaces):类似于系统表空间,通用表空间是共享表空间,可以存储多个表的数据。
共享表空间与独立表空间的选择
在 InnoDB 存储引擎中,独立表空间和共享表空间各有特点。
- 独立表空间下,每张表都有专属空间用于存放数据和索引,这使得数据相互隔离,便于管理,且可通过
optimize table
命令回收空间,面对大量删除操作时性能稳定,同时方便单表在不同数据库间迁移;但缺点是单表数据量过大时,表空间文件会膨胀,增加管理难度且影响读写性能。 - 共享表空间则能灵活分布在多个磁盘,适合小数据库集中管理;然而,其存在文件易臃肿、空间分配后无法自动回缩、高并发读写时易出现 I/O 瓶颈等问题,不适合数据变动频繁及频繁写入的场景。
综合来看,独立表空间在数据管理灵活性、空间利用率及应对高并发等方面优势突出,自 MySQL 5.6 起已成为默认选项,若无特殊需求,实际项目中优先选择它能有效提升数据库性能与管理效率。
段(Segment)
段(Segment)由一个或多个区组成,区在文件系统是一个连续分配的空间(在InnoDB中是连续的64个页),不过在段中不要求区与区之间是相邻的。段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存。当我们创建数据表、索引的时候,就会相应创建对应的段,比如创建一张表时会创建一个表段,创建一个索引时会创建一个索引段。
段的意义:
对于范围查询,其实是对B+树叶子节点中的记录进行顺序扫描,而如果不区分叶子节点和非叶子节点,统统把节点代表的页面放到申请到的区中的话,进行范围扫描的效果就大打折扣了。所以InnoDB对B+树的叶子节点和非叶子节点进行了区别对待,也就是说叶子节点有自己独有的区,非叶子节点也有自己独有的区。
存放叶子节点的区的集合就算是一个段(segment),存放非叶子节点的区的集合也算是一个段。也就是说一个索引会生成2个段,一个叶子节点段,一个非叶子节点段。
除了索引的叶子节点段和非叶子节点段之外,InnoDB中还有为存储一些特殊的数据而定义的段,比如回滚段。所以常见的段有数据段、索引段、回滚段
。数据段即为B+树的叶子节点,索引段即为B+树的非叶子节点
。
段其实不对应表空间中某一个连续的物理区域,而是一个逻辑上的概念,由若干个零散的页面以及一些完整的区组成。
区(Extent)
区(Extent)是比页大一级的存储结构,在InnoDB存储引擎中,一个区会分配64个连续的页。因为InnoDB中的页大小默认是16KB,所以一个区的大小是64*16KB=1MB。
对于小表而言,区的概念相对不那么突出,表基本由段以及离散的数据 page 页构成。但随着表空间数据量的不断增长,系统对磁盘空间的分配策略转变为按区进行。区的存在主要是为了提高 page 分配效率,批量分配相较于单一 page 分配,不仅在速度上更快,而且在数据连续性(在磁盘上的物理位置)方面表现更优,这对于提升磁盘 I/O 性能具有重要意义 。
页(Page)
页是 MySQL 存储的基础单元,InnoDB 中一个页的大小通常为 16k。InnoDB 会将 xxx.ibd 表空间文件按 16k 大小的 page 进行切分。数据在存储时,以页为单位进行读写操作。不同类型的数据,如数据行、索引信息等,都会按照一定的规则存储在各个页中。
行(Row)
行是数据库中数据存储的最小逻辑单元。在 InnoDB 中,数据按行存储在数据页中。每一行数据包含了表中定义的各个字段的值。行格式决定了数据在行中的存储方式,常见的行格式有 Compact、Redundant 等。除了用户定义的字段数据,行还包含一些隐藏字段,例如事务 ID、回滚指针等。这些隐藏字段在事务处理、数据恢复等方面起着关键作用。例如,事务 ID 用于标识对该行数据进行修改的事务,回滚指针则用于在事务回滚时恢复数据的原有状态。行数据在页中的存储顺序和排列方式,也会影响数据的查询和更新效率。
InnoDB 事务实现原理
好文推荐:
万字解析 mysql innodb 事务实现原理
InnoDB中MVCC的实现原理_innodb的mvcc实现原理-CSDN博客
MySQL三大日志(binlog、redo log和undo log)详解_mysql挂了用什么log-CSDN博客
详解MySQL的MVCC(ReadView部分解析C++源码)_mysql mvcc-CSDN博客
建议看看好文推荐中的两篇文章,写得非常好了,我这里就简单介绍。
InnoDB 严格遵循 ACID 特性。其事务基于redo log(重做日志)和undo log(撤销日志)以及(mvcc)多版本并行控制机制以及锁机制进行实现。
Redo Log 重做日志
InnoDB 存储引擎中有 redo log buffer 和 redo log file。redo log buffer 是内存中的缓冲区,用于临时存放事务执行过程中对数据页的修改操作信息。redo log file 是磁盘上的文件,用于持久化存储这些修改记录。
它的主要作用是确保事务的持久性。当事务提交时,即使数据库系统崩溃,没有来得及将修改后的数据页写入磁盘,也可以通过 redo log 来恢复这些修改,保证事务的修改不会丢失。
在事务执行过程中,每一个对数据页的修改操作都会先记录在 redo log buffer 中。当事务提交时,会将 redo log buffer 中的内容刷盘到 redo log file。在将 redo log buffer 写入 redo log file 时,会使用 fsync 操作来确保日志真正地持久化到磁盘。
此外,InnoDB支持设置不同的刷盘时机,同股票配置 innodb_flush_log_at_trx_commit
参数进行修改:
- 0 :表示每次事务提交时不进行刷盘操作
- 1 :表示每次事务提交时都将进行刷盘操作 (默认值)
- 2 :表示每次事务提交时都只把 redo log buffer 内容写入 page cache
另外,InnoDB 存储引擎有一个后台线程,每隔1 秒,就会把 redo log buffer 中的内容写到文件系统缓存(page cache),然后调用 fsync 刷盘。
Undo Log 回滚日志
undo log 是一种逻辑日志,它记录的是数据的逻辑变化。其主要功能是为了实现事务的原子性。如果事务在执行过程中需要回滚,InnoDB 就可以利用 undo log 来撤销已经执行的操作,将数据恢复到事务开始之前的状态。
另外,undo log 还用于 MVCC(多版本并发控制)。它可以为每个事务提供一个数据在某个时间点的快照,通过保存数据的旧版本,使得不同事务可以在不同的版本上进行读取操作,从而提高数据库的并发性能。
当事务需要回滚时,InnoDB 会按照 undo log 中记录的逆序操作来恢复数据。例如,如果事务插入了一条记录,undo log 中会记录删除这条记录的操作;如果事务更新了一个数据,undo log 会记录如何将数据恢复到更新前的值。在 MVCC 中,每个数据行都可能存在多个版本,通过 undo log 来维护这些版本之间的关联。当一个事务读取数据时,InnoDB 会根据事务的隔离级别和 undo log 中的版本信息来判断应该返回哪个版本的数据。
3. 多版本并发控制(MVCC)
MVCC 为每个事务构建独立的数据库视图(Read View),事务读取数据时,只能看到事务启动时已提交的版本,以此避免读写冲突。
InnoDB 为数据行维护版本链,每次数据修改,旧版本及对应的事务 ID 都会被保存。事务读取数据时生成 readView,它包含活跃事务 ID 等信息。通过对比事务 ID,判断数据版本对当前事务是否可见。比如,若数据行版本的事务 ID 早于 readView 中最早活跃事务 ID,当前事务就能访问该版本。
undo log 在其中至关重要,它存储旧版本数据,为 MVCC 提供数据来源。
InnoDB 适用场景以及调优建议
适用场景
InnoDB 适用于对事务完整性要求高、需保障数据一致性的场景,像联机事务处理系统,如银行转账、电商订单处理,能保证事务 ACID 特性;也适用于高并发读写场景,例如多人协作的文档编辑系统,其行级锁机制可减少锁冲突。
调优建议
- 配置参数优化:
- innodb_buffer_pool_size:设为服务器物理内存 70% - 80%,用于缓存数据和索引。
- innodb_log_buffer_size:写入频繁系统可适当增大,减少日志写盘次数。
- innodb_io_capacity:依磁盘 I/O 性能设值,SSD 可设几千,机械硬盘设几百。
- innodb_flush_method:使用直接 I/O 的系统,可设为 O_DIRECT。
- 索引优化:
- 合理创建索引:为查询条件、连接条件列建索引,避免过多。
- 索引类型选择:多数情况用 B-Tree 索引,等值查询可适当考虑哈希索引。
- 事务管理优化:
- 缩短事务长度:将大事务拆成小事务,独立提交或回滚。
- 合理设置隔离级别:多选用 “读已提交” 或 “可重复读” 级别。
MyISAM
MyISAM 是 MySQL 的一种存储引擎,在早期版本的 MySQL 中被广泛使用。它不支持事务处理,但在简单的查询和存储场景下有较好的性能表现。适用于例如日志服务器,小型的字典服务或配置服务等对于数据一致性要求不高,同时对于读取速度有较高要求的服务。
MyISAM按照行存储数据,数据文件中的每一行对应表中的一条记录。索引文件通过特定的索引算法(如 B-Tree 索引)建立索引项与数据行的映射关系。当执行查询操作时,首先检查是否有可用的索引。如果有,利用索引在索引文件中快速定位数据行的位置,然后从数据文件中读取相应的数据。如果没有索引,则需要进行全表扫描来查找满足条件的数据。
主要特性:
- 表级锁定:采用表级锁机制。当对表进行写操作时,会锁住整个表。这在高并发写操作场景下可能会导致性能瓶颈,但对于读操作较多的情况影响较小。
- 简单高效的索引结构:索引结构相对简单,在基于索引的查询操作中效率较高,特别是对于非事务性的读取操作,能够快速定位数据。
- 不支持事务:不具备 ACID 事务特性,无法保证复杂业务操作下的数据一致性和完整性。例如,在多步骤数据修改场景中,不能像支持事务的引擎一样保证操作要么全部成功要么全部失败。
数据文件及存储结构:
- **表定义文件(.frm) **:存储表的定义信息。
- **数据文件(.myd) **:用于存储表中的实际数据,以行的形式排列。
- 索引文件(.myi):存储索引信息,与数据文件分离。这种分离的结构使得在查询过程中可以根据索引快速定位数据在数据文件中的位置。例如,在执行一个基于索引列的查询时,先在索引文件中找到对应的索引项,然后根据索引项中的指针定位到数据文件中的数据行。
Memory
Memory 存储引擎是将数据存储在内存中的存储引擎,数据读写速度极快,但数据持久性差,主要用于对读写性能要求极高的特定场景。
主要特性:
- 内存存储:数据全部存储在内存中,这使得读写操作速度非常快,能够快速响应数据请求。
- 哈希索引为主:默认使用哈希索引,对于等值查询性能卓越。例如,在快速查找键 - 值对的场景中,能够迅速定位到目标数据。
- 数据易失性:数据依赖内存,一旦服务器断电或者数据库服务崩溃,数据会丢失,因此数据安全性较低。
Mysql 索引与SQL调优
索引概述
在数据库中,索引就如同书籍的目录,能帮助我们快速定位到所需的数据。想象一下,在一本数百万字的巨著里查找特定的段落,如果没有目录,那将是一场噩梦般的逐页翻阅;而有了清晰的目录,我们可以迅速定位到目标章节。
索引的重要性主要体现在两个方面:
- 它能显著提升查询性能,减少系统响应时间,这对于高并发的应用系统至关重要。
- 合适的索引有助于维护数据的完整性和一致性,例如唯一索引可确保特定列的值不会重复。
索引方法
B+树
B+树是InnoDB存储引擎默认使用的索引方法 。在B+树结构中,所有的数据记录都存储在叶子节点,非叶子节点仅用于存储索引值和指向子节点的指针,这使得B+树的结构更加紧凑,磁盘I/O效率更高。叶子节点之间通过双向链表相连,这种有序的结构使得范围查询变得极为高效。
B+树与B树的区别
B+树是B树的一种优化
- B树的每个结点都存储了key和data,B+树的data存储在叶子节点上。非叶子节点不存储data,这样一个节点就可以存储更多的key。可以使得树更矮,磁盘IO操作次数更少。查询效率更高。
- B+树查询路径都是从非叶子结点, 到叶子节点。 效率比较稳定。
- B+树叶子结点是一个链表, 扫描全表数据速度更快(只需要遍历叶子节点,并且范围查询也有优化)。
因为B+树的这些好处,在 MySQL 中,MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,
哈希
哈希索引基于哈希表实现,它通过对索引列的值进行哈希运算,生成一个唯一的哈希值,然后将数据存储在该哈希值对应的位置。哈希索引的最大优势在于等值查询速度极快,理论上只需一次哈希计算和一次磁盘I/O操作(假设没有缓存命中),就能定位到目标数据。但哈希索引也有明显的局限性,由于哈希值的无序性,它不支持范围查询。
RTree
RTree(R树)是一种用于处理多维空间数据的索引结构,在MySQL中主要用于地理空间数据类型,如POINT、LINESTRING、POLYGON等。优势在于范围查找,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
索引类型
普通索引
普通索引是最基本的索引类型,对索引列的值没有特殊限制,允许重复。在创建普通索引时,语法如下:
CREATE INDEX index_name ON table_name (column_name);
假设在一个博客文章表中,经常需要根据文章分类来查询文章,此时就可以在分类列上创建普通索引,如:
CREATE INDEX idx_category ON articles (category);
这样在执行SELECT * FROM articles WHERE category = ‘技术文章’;这类查询时,数据库就能利用索引快速定位到相关文章,提升查询效率。
唯一索引
唯一索引要求索引列中的值必须唯一,但允许有空值(前提是该列允许为空)。创建唯一索引的语法为:
CREATE UNIQUE INDEX index_name ON table_name (column_name);
在用户表中,为了确保每个用户的邮箱地址唯一,避免重复注册,可以在邮箱列上创建唯一索引:
CREATE UNIQUE INDEX idx_email ON users (email);
当有新用户注册时,数据库会自动检查邮箱列的值是否唯一,若存在重复则拒绝插入,保证了数据的唯一性。
主键索引
主键索引是一种特殊的唯一索引,它不仅要求列值唯一,还不能为空。在MySQL中,每个表只能有一个主键。创建主键索引有两种常见方式,一种是在创建表时直接指定主键列:
CREATE TABLE table_name (
column_name data_type PRIMARY KEY
);
另一种是在已有表上添加主键:
ALTER TABLE table_name ADD PRIMARY KEY (column_name);
以订单表为例,通常将订单ID设置为主键,因为每个订单都有唯一标识,通过主键索引可以快速定位和管理订单数据,方便进行查询、更新和删除操作。
组合索引
组合索引是在多个列上创建的索引,语法如下:
CREATE INDEX index_name ON table_name (column1, column2, column3);
在电商系统的订单详情表中,经常需要根据订单日期、客户ID和订单状态进行查询,此时可以创建如下组合索引:
CREATE INDEX idx_order_info ON order_details (order_date, customer_id, order_status);
使用组合索引时要遵循“最左前缀原则”,即查询条件必须从组合索引的最左边列开始,依次使用列才能有效利用索引。例如,查询WHERE order_date = '2023 - 01 - 01' AND customer_id = 123 AND order_status = '已完成'
,索引可以被充分利用;但如果查询WHERE customer_id = 123 AND order_status = '已完成'
,由于没有从最左列order_date开始,索引的效率会大打折扣。
全文索引
全文索引主要用于在文本类型的列中进行全文搜索。在MySQL中,MyISAM存储引擎原生支持全文索引,InnoDB存储引擎从5.6版本开始支持。创建全文索引的语法如下:
CREATE FULLTEXT INDEX index_name ON table_name (column_name);
在新闻文章表中,若要实现对文章内容的快速搜索功能,可以在文章内容列上创建全文索引:
CREATE FULLTEXT INDEX idx_article_content ON news_articles (content);
使用全文索引进行查询时,需使用MATCH AGAINST语法:
SELECT * FROM news_articles WHERE MATCH(content) AGAINST ('关键词' IN NATURAL LANGUAGE MODE);
聚簇索引和非聚簇索引
从底层存储方式维度划分,索引又可以分为聚簇索引(聚集索引)和非聚簇索引(非聚集索引)。
聚簇索引:索引结构和数据一起存放的索引,InnoDB 中的主键索引就属于聚簇索引。
非聚簇索引:索引结构和数据分开存放的索引,二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
聚簇索引(密集索引)
行数据和主键索引存储在一起,辅助键索引只存储辅助键和主键,而不保存数据
InnoDB使用的是聚簇索引,行数据保存在叶子节点上。如果通过where id = 5
,直接通过主键索引找到对应的关键字,然后返回行数据。
如果通过where name = tom
,首先通过辅助键索引找到对应id = 5
,然后再通过主键索引找到行数据
聚簇索引的创建规则为:
- 如果存在主键,主键就是聚簇索引
- 如果没有主键,表中第一个唯一非空索引为聚簇索引
- 如果以上都没有,InnoDB生成一个隐藏主键作为聚簇索引,是一个6字节的列,随着数据的插入自增
所以,InnoDB必须有个聚簇索引,这是因为非主键索引叶子节点不保存行数据,而是保存着主键值。
非聚簇索引(稀疏索引)
B+树叶子节点存储的是指向数据的指针
MyISAM使用的为非聚簇索引,主键索引保存了主键,非主键索引保存了非主键,而数据行保存在其他位置,检索的过程都是通过叶子节点内保存的地址找到对应的数据行。
聚簇索引的优势
InnoDB为什么使用聚簇索引呢?从上面索引过程我们可以看到,对于非主键查询来说,聚簇索引需要经过两次检索,好像效率更低了,那么聚簇索引的优势在哪?
- 行数据和叶子节点保存在一起,会一起被加载到内存,找到叶子节点就可以将数据库返回。
- 辅助索引的叶子节点保存主键的指针,而不使用地址值作为指针,减少了当出现行移动或者数据页分裂时辅助索引的维护工作,使用主键值当作指针会让辅助索引占用更多的空间,InnoDB在移动行时无须更新辅助索引中的这个"指针"。也就是说行的位置(实现中通过16K的Page来定位)会随着数据库里数据的修改而发生变化(前面的B+树节点分裂以及Page的分裂),使用聚簇索引就可以保证不管这个主键B+树的节点如何变化,辅助索引树都不受影响。
索引设计
索引失效场景
- 列类型不匹配:如果索引列和查询条件的数据类型不匹配,例如在一个字符串类型的索引列上执行了数值比较,那么索引就会失效。
- 函数操作:如果查询条件中使用了函数操作,例如在索引列上使用函数操作或者使用了自定义函数,那么索引也会失效。
- 索引列值为空:如果查询条件中使用了IS NULL或者IS NOT NULL操作,那么如果索引列上存在NULL值,那么索引就会失效。
- 表达式操作:如果查询条件中使用了表达式操作,例如对索引列进行加减乘除等操作,那么索引也会失效。
- 隐式类型转换:如果查询条件中使用了隐式类型转换,例如在一个字符串类型的索引列上执行了数值比较,并且数据库自动将字符串转换为数字类型,那么索引也会失效。
- 数据量太大:如果表中的数据量太大,那么对于一些非唯一索引列,索引的查询优化器可能会认为扫描整个表比使用索引更加高效,从而导致索引失效。
- 索引列上存在函数:如果索引列上使用了函数,例如在索引列上使用了UPPER()函数,那么索引也会失效。
- 违反最左匹配原则:联合索引要正确使用需满足最左匹配原则,即:符合第一列才会继续判断后面的字段。
- 使用OR查询时其中一列不是索引
索引设计原则
- 选择合适列
- 频繁查询列:在经常出现在WHERE子句中的列上创建索引,能极大提高查询效率。例如,在用户表中,经常根据用户年龄查询特定年龄段的用户,那么在年龄列上创建索引是明智之举。
- 连接条件列:当进行多表连接查询时,连接条件中的列应创建索引。如在订单表和用户表进行连接查询时,连接条件通常是两个表中的用户ID,这两个用户ID列都应创建索引。
- 排序列:如果某个列经常用于排序操作,在该列创建索引可以加快排序速度。例如,在商品表中,经常根据商品销量进行排序,那么在销量列上创建索引有助于提升排序效率。
- **非频繁更新列:**频繁更新的列设计索引会增加索引表的维护成本。
- **索引选择性高的列:**索引的选择性是指索引列中不同值的数量与表中记录总数的比值。选择性越高,索引的效率越高。例如,在性别列中,只有“男”和“女”两个值,选择性较低,在该列创建索引意义不大。而在用户ID列中,每个用户的ID都是唯一的,选择性为1,在该列创建索引能极大提高查询效率
- 避免过多索引,建议单表不超过5个索引
- 注意索引长度,对于字符串类型的列,创建索引时可指定索引长度。例如:
CREATE INDEX idx_name ON users (name(10));
这表示只对name列的前10个字符创建索引。这样做可以减少索引占用的空间,提高查询效率。但要注意,索引长度的选择需谨慎,过短可能无法有效区分不同值,降低索引的选择性。
SQL优化方法
EXPLAIN 分析查询语句
在进行SQL调优前,了解查询语句的执行情况至关重要。MySQL的EXPLAIN关键字能帮助我们分析查询语句的执行计划。例如:
EXPLAIN SELECT * FROM users WHERE age > 30;
执行后,MySQL返回的结果集包含诸多执行信息:
列名 | 说明 |
---|---|
id | SELECT语句的唯一标识符,标识查询顺序。子查询或联合查询中有多个id,值越大优先级越高,越先执行。 |
select_type | 查询类型,常见的有: + SIMPLE(简单查询,无子查询和联合查询)、 + PRIMARY(主查询,包含子查询时最外层的查询)、 + SUBQUERY(子查询)、 + DERIVED(派生表,FROM子句中的子查询)等。 |
table | 查询涉及的表名。 |
partitions | 查询将访问的分区,表未分区时显示NULL。 |
type | 表的连接类型,是关键字段。常见连接类型有: + ALL(全表扫描),性能最差的。 + index(索引扫描,遍历整个索引树) + range(范围扫描,用于检索指定范围的值,如>、<、BETWEEN等运算符) + ref(使用非唯一索引进行等值查询,找到匹配的所有行) + eq_ref(用于唯一索引或主键的等值查询,连接中使用,每个表最多返回一行匹配记录) + const(常量查询,通过主键或唯一索引等值查询,结果为常量)等。 连接类型从优到差为const > eq_ref > ref > range > index > ALL。 |
possible_keys | 可能用于查询的索引列表,为空表示无可用索引。 |
key | 实际使用的索引,为空表示未使用索引。 |
key_len | 使用的索引长度,可判断索引使用是否合理。 |
ref | 与索引进行比较的列或常量。 |
rows | MySQL估计要扫描的行数,是估计值。 |
filtered | 存储引擎返回的数据经过滤后,满足查询条件记录所占百分比。 |
Extra | 额外信息,如Using index(使用覆盖索引,查询所需数据都在索引中,无需回表)、Using where(使用WHERE子句过滤)、Using temporary(使用临时表,常见于排序或分组操作)、Using filesort(需文件排序,性能较差)等。 |
通过分析这些信息,能找出查询语句的问题。
示例:假设有用户表users,含id(主键)、name、age、email字段。执行:
EXPLAIN SELECT * FROM user WHERE age > 30 AND email = 'test@example.com';
若EXPLAIN结果为:
可知type为ALL,全表扫描,possible_keys和key为空,未用索引。可能是age和email列未创建合适索引。可创建组合索引:
CREATE INDEX idx_age_email ON users (age, email);
再次执行EXPLAIN:
EXPLAIN SELECT * FROM users WHERE age > 30 AND email = 'test@example.com';
新结果为:
此时type变为range,key为idx_age_email,使用了组合索引,查询性能提升。
优化查询语句
- 避免全表扫描:全表扫描是查询效率低的主因之一。通过创建合适索引可避免。如在大量记录的products表中,查询
SELECT * FROM products WHERE category = '电子产品';
,category列无索引会全表扫描。在category列创建索引可提高效率。 - 优化子查询:子查询有时影响查询性能。可将子查询转换为连接查询或用临时表优化。例如原查询:
SELECT * FROM orders WHERE customer_id
IN (SELECT customer_id FROM customers WHERE region = '亚洲');
转换为连接查询:
SELECT orders.* FROM orders
JOIN customers ON orders.customer_id = customers.customer_id
WHERE customers.region = '亚洲';
- 避免不必要的函数:查询条件中用函数会致索引无法使用。如
SELECT * FROM users WHERE UPPER(name) = 'JOHN';
对name列用UPPER函数,无法用name列索引。应避免,改为
SELECT * FROM users WHERE name = 'john';
优化表结构
- 合理选择数据类型:合适的数据类型可减少存储开销,提高查询效率。如表示年龄用TINYINT比INT节省空间;表示状态用ENUM类型限制取值范围,比VARCHAR节省空间。
- 示例:订单状态列status,取值’已支付’、‘未支付’、‘已取消’,原用VARCHAR(20),可改为ENUM(‘已支付’, ‘未支付’, ‘已取消’),减少存储开销,提高查询效率。
- 拆分大表:表数据量过大可拆分。如将含用户基本信息和详细信息的大表拆分为两个表,查询基本信息时避免读取大量不必要的详细信息,提高查询效率。
- 示例:user_info表含用户ID、姓名、年龄、地址、联系方式、兴趣爱好等字段,可拆分为user_basic_info(用户ID、姓名、年龄)和user_detail_info(用户ID、地址、联系方式、兴趣爱好)。查询用户基本信息时避免读取user_detail_info中的大量数据。
SELECT * FROM user_basic_info WHERE user_id = 123;
配置优化
- 调整MySQL配置参数:MySQL配置参数对性能影响大。
innodb_buffer_pool_size
设置InnoDB存储引擎的缓冲池大小,增大可提高数据缓存命中率,减少磁盘I/O。建议根据服务器内存调整,一般可用服务器物理内存的60% - 80% 。例如服务器有32GB内存,可设置innodb_buffer_pool_size
= 20G。 - 使用合适的存储引擎:MySQL支持InnoDB、MyISAM等存储引擎。InnoDB支持事务、行级锁,适用于事务一致性要求高的场景;MyISAM不支持事务、表级锁,适用于读操作频繁的场景。如电商订单系统,对事务一致性要求高,选InnoDB;博客文章表,读操作多,可选MyISAM(需注意MyISAM在高并发写入场景下性能较差)。