<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>MySQL on Wimi's Space</title><link>https://wimi.space/tags/mysql/</link><description>Recent content in MySQL on Wimi's Space</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Fri, 06 Jun 2025 09:48:39 +0800</lastBuildDate><atom:link href="https://wimi.space/tags/mysql/index.xml" rel="self" type="application/rss+xml"/><item><title>MySQL 锁相关</title><link>https://wimi.space/posts/2025/06/mysql-lock-desc/</link><pubDate>Fri, 06 Jun 2025 09:48:39 +0800</pubDate><guid>https://wimi.space/posts/2025/06/mysql-lock-desc/</guid><description>&lt;p>数据库锁设计的初衷是处理并发问题。作为多用户共享的资源，当出现并发访问的时候，数据库需要合理地控制资源的访问规则。而锁就是用来实现这些访问规则的重要数据结构。&lt;/p>
&lt;h1 id="mysql-锁的分类">MySQL 锁的分类&lt;/h1>
&lt;h2 id="根据锁的范围分类">根据锁的范围分类&lt;/h2>
&lt;p>根据加锁的范围，MySQL 里面的锁大致可以分成全局锁、表级锁和行级锁三类。&lt;/p>
&lt;h3 id="全局锁">全局锁&lt;/h3>
&lt;p>全局锁就是对整个数据库实例加锁。可以通过下面命令操作全局锁：&lt;/p>
&lt;pre>&lt;code class="language-SQL">-- 全局读锁
FLUSH TABLES WITH READ LOCK;

-- 释放锁
UNLOCK TABLES;
&lt;/code>&lt;/pre>
&lt;p>加全局读锁后，整个库就处于只读状态了，之后其他线程的以下语句会被阻塞：&lt;/p>
&lt;ul>
&lt;li>数据更新语句（数据的增删改）。&lt;/li>
&lt;li>数据定义语句（包括建表、修改表结构等）。&lt;/li>
&lt;li>更新类事务的提交语句。&lt;/li>
&lt;/ul>
&lt;p>全局锁主要应用于做全库逻辑备份，这样在备份数据库期间，不会因为数据或表结构的更新，而出现备份文件的数据与预期的不一样。&lt;/p>
&lt;p>加上全局锁后，整个数据库都是只读状态，这期间业务只能读取而不能更新数据，会导致业务停滞。如果数据库引擎支持事务的『可重复读隔离级别』，在备份数据库之前开启事务，会先创建读视图，整个事务执行期间都在用这个读视图，由于 MVCC 的支持，备份期间业务依然可以对数据进行更新操作。&lt;/p>
&lt;p>备份数据库的工具是 mysqldump，在使用 mysqldump 时加上 -single-transaction 参数的时候，就会在备份数据库之前先开启事务。这种方法只适用于支持『可重复读隔离级别的事务』的存储引擎。InnoDB 存储引擎默认就是可重复读隔离级别，可以采用这种方式来备份数据库。对于不支持事务的存储引擎如 MyISAM，在备份时只能使用全局锁的方式。&lt;/p>
&lt;h3 id="表级锁">表级锁&lt;/h3>
&lt;p>MySQL 里面表级别的锁有两种：一种是表锁，一种是元数据锁（METADATA LOCK）。&lt;/p>
&lt;h4 id="表锁">表锁&lt;/h4>
&lt;p>可以通过下面命令操作表锁：&lt;/p>
&lt;pre>&lt;code class="language-SQL">-- 表级别的共享锁，也就是读锁；允许当前会话读取被锁定的表，但阻止其他会话对这些表进行写操作。
LOCK TABLES table_name READ;

-- 表级别的独占锁，也就是写锁；允许当前会话对表进行读写操作，但阻止其他会话对这些表进行任何操作（读或写）。
LOCK TABLES table_name WRITE;

-- 释放当前会话的所有表锁。
UNLOCK TABLES;
&lt;/code>&lt;/pre>
&lt;p>在还没有出现更细粒度的锁的时候，表锁是最常用的处理并发的方式，不过尽量避免在使用 InnoDB 引擎的表使用表锁，因为表锁的颗粒度太大，会影响并发性能。InnoDB 有更细粒度的行级锁支持。&lt;/p>
&lt;h4 id="auto-inc-自增锁">AUTO-INC 自增锁&lt;/h4>
&lt;p>当一个表有 AUTO_INCREMENT 列时，MySQL 会在插入新记录时自动为该表加上 AUTO-INC 锁，这是一种特殊的表级锁，目的是为了保证在高并发插入时，AUTO_INCREMENT 列的值能够正确地递增，不会出现重复值。&lt;/p></description></item><item><title>MySQL 索引相关</title><link>https://wimi.space/posts/2025/06/mysql-index-desc/</link><pubDate>Thu, 05 Jun 2025 15:31:54 +0800</pubDate><guid>https://wimi.space/posts/2025/06/mysql-index-desc/</guid><description>&lt;h2 id="innodb-的索引模型">InnoDB 的索引模型&lt;/h2>
&lt;p>&lt;img src="https://static.wimi.space/blog/mysql-index-model.png" alt="">&lt;/p>
&lt;p>根据叶子节点的内容，索引类型分为主键索引和非主键索引。&lt;/p>
&lt;ul>
&lt;li>主键索引的叶子节点存的是整行数据。在 InnoDB 里，主键索引也被称为聚簇索引（clustered index）。&lt;/li>
&lt;li>非主键索引的叶子节点内容是主键的值。在 InnoDB 里，非主键索引也被称为二级索引（secondary index）。&lt;/li>
&lt;/ul>
&lt;h3 id="b-树和数据页">B+ 树和数据页&lt;/h3>
&lt;p>InnoDB 的数据是按数据页为单位来读写的，默认数据页大小为16 KB。每个数据页之间通过双向链表的形式组织起来，物理上不连续，但是逻辑上连续。&lt;/p>
&lt;p>InnoDB 使用 B+ 树作为索引，B+ 树中的每个节点都是一个数据页，结构示意图如下：&lt;/p>
&lt;p>&lt;img src="https://cdn.xiaolincoding.com//mysql/other/7c635d682bd3cdc421bb9eea33a5a413.png" alt="">
「图片来自 &lt;a href="https://cdn.xiaolincoding.com//mysql/other/7c635d682bd3cdc421bb9eea33a5a413.png">小林 Coding&lt;/a>」&lt;/p>
&lt;h3 id="回表和覆盖索引">回表和覆盖索引&lt;/h3>
&lt;p>用表 T(id, k)，id 为主键索引，k 为非主键索引，来举例分析：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>通过主键索引查询方式，如 &lt;code>SELECT * FROM T WHERE id = ?&lt;/code> 只需要搜索 id 这棵 B+ 树；&lt;/p>
&lt;/li>
&lt;li>
&lt;p>通过非主键索引查询方式，如 &lt;code>SELECT * FROM T WHERE k = ?&lt;/code> 则需要先搜索 k 索引树，得到 id 的值，再到 id 索引树搜索一次。这个过程称为『回表』。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果执行的语句是 &lt;code>SELECT id FROM T WHERE k = ?&lt;/code>，这时只需要查 id 的值，而 id 的值已经在 k 索引树上了，因此可以直接提供查询结果，不需要回表，这个过程称为『覆盖索引』。&lt;/p></description></item><item><title>MySQL 的事务隔离级别</title><link>https://wimi.space/posts/2025/06/mysql-transaction-isolation/</link><pubDate>Wed, 04 Jun 2025 16:14:24 +0800</pubDate><guid>https://wimi.space/posts/2025/06/mysql-transaction-isolation/</guid><description>&lt;p>MySQL的事务隔离作用是确保事务之间的操作相互隔离，避免数据不一致问题，从而保证数据库的一致性和并发事务的正确执行。&lt;/p>
&lt;p>它定义了多个并发事务如何看到彼此的数据变更。其核心作用在于解决并发事务操作同一数据时可能引发的数据一致性问题，确保每个事务都像是在独立运行的环境中操作数据。&lt;/p>
&lt;h2 id="并发事务会导致的问题">并发事务会导致的问题&lt;/h2>
&lt;h3 id="脏读">脏读&lt;/h3>
&lt;p>事务读取到其他事务尚未提交的无效数据。例如，事务 A 修改数据但未提交，事务 B 读取该数据后若A回滚，则 B 读到的是脏数据。&lt;/p>
&lt;h3 id="不可重复读">不可重复读&lt;/h3>
&lt;p>同一事务内多次读取同一数据，因其他事务提交了修改导致结果不一致。例如，事务 A 读取某行数据后，事务 B 更新并提交该行，A 再次读取时数据变化。&lt;/p>
&lt;h3 id="幻读">幻读&lt;/h3>
&lt;p>事务按条件查询数据时，其他事务插入符合条件的新数据并提交，导致当前事务再次查询时出现“幻影”记录。例如，事务 A 查询年龄 =20 的记录，事务 B 插入一条年龄 =20 的新记录并提交，A 再次查询时会多出一条数据。&lt;/p>
&lt;h2 id="事务隔离级别">事务隔离级别&lt;/h2>
&lt;p>多个事务并发执行时，可能会导致上述的问题，对事务的一致性产生不同程度的影响，SQL 标准提出了四种隔离级别来规避这些问题，隔离级别越高性能效率越低，隔离级别由低到高分别为：&lt;/p>
&lt;h3 id="读未提交read-uncommitted">读未提交（READ UNCOMMITTED）&lt;/h3>
&lt;p>最低的隔离级别，允许事务读取其他事务尚未提交的数据变更。&lt;/p>
&lt;p>这可能导致脏读、不可重复读或幻读问题。例如，事务A修改数据但未提交，事务B读取该数据后若A回滚，则B读到的是无效数据（脏读）。&lt;/p>
&lt;h3 id="读已提交read-committed">读已提交（READ COMMITTED）&lt;/h3>
&lt;p>此级别解决了脏读问题，但可能出现不可重复读和幻读。&lt;/p>
&lt;p>例如，同一事务内多次读取同一数据，结果因其他事务提交而不同。&lt;/p>
&lt;h3 id="可重复读repeatable-read">可重复读（REPEATABLE READ）&lt;/h3>
&lt;p>保证同一事务内多次读取相同数据的结果保持一致，避免脏读和不可重复读，但理论上仍可能遇到幻读。&lt;/p>
&lt;p>例如，事务A读取符合条件的数据，事务B插入新数据并提交后，A再次查询发现多出数据。解决的方案有两种：&lt;/p>
&lt;ul>
&lt;li>针对快照读（普通 SELECT 语句），是通过 MVCC 方式解决了幻读，因为可重复读隔离级别下，事务执行过程中看到的数据，一直跟这个事务启动时看到的数据是一致的，即使中途有其他事务插入了一条数据，是查询不出来这条数据的，所以就很好了避免幻读问题。&lt;/li>
&lt;li>针对当前读（SELECT &amp;hellip; FOR UPDATE 等语句），是通过 Next-key Lock（记录锁+间隙锁）方式解决了幻读，因为当执行该语句的时候，会加上 Next-key Lock，如果有其他事务在锁范围内插入了一条记录，那么这个插入语句就会被阻塞，无法成功插入，所以就很好了避免幻读问题。&lt;/li>
&lt;/ul>
&lt;p>这两个解决方案是很大程度上解决了幻读现象，但是还是有个别的情况造成的幻读现象是无法解决的。&lt;/p>
&lt;h3 id="串行化serializable">串行化（SERIALIZABLE）&lt;/h3>
&lt;p>最高的隔离级别，强制事务串行执行，完全隔离并发操作，避免脏读、不可重复读和幻读。&lt;/p>
&lt;p>但会显著降低数据库性能，仅在严格要求数据一致性时使用。&lt;/p>
&lt;h2 id="场景举例">场景举例&lt;/h2>
&lt;pre>&lt;code class="language-SQL">CREATE TABLE `T` (`value` int) ENGINE=InnoDB;
INSERT INTO `T` (`value`) VALUES (1);
&lt;/code>&lt;/pre>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th style="text-align: center">操作步骤&lt;/th>
 &lt;th style="text-align: center">事务 A&lt;/th>
 &lt;th style="text-align: center">事务 B&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td style="text-align: center">1&lt;/td>
 &lt;td style="text-align: center">启动事务，得到值 1&lt;/td>
 &lt;td style="text-align: center">开启事务&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td style="text-align: center">2&lt;/td>
 &lt;td style="text-align: center">&lt;/td>
 &lt;td style="text-align: center">查询得到值 1&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td style="text-align: center">3&lt;/td>
 &lt;td style="text-align: center">&lt;/td>
 &lt;td style="text-align: center">将 1 改成 2&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td style="text-align: center">4&lt;/td>
 &lt;td style="text-align: center">查询得到值 V1&lt;/td>
 &lt;td style="text-align: center">&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td style="text-align: center">5&lt;/td>
 &lt;td style="text-align: center">&lt;/td>
 &lt;td style="text-align: center">提交事务 B&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td style="text-align: center">6&lt;/td>
 &lt;td style="text-align: center">查询得到值 V2&lt;/td>
 &lt;td style="text-align: center">&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td style="text-align: center">7&lt;/td>
 &lt;td style="text-align: center">提交事务 A&lt;/td>
 &lt;td style="text-align: center">&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td style="text-align: center">8&lt;/td>
 &lt;td style="text-align: center">查询得到值 V3&lt;/td>
 &lt;td style="text-align: center">&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>在不同的隔离级别下，事务 A 中的 V1、V2、V3 返回值分别是什么。&lt;/p></description></item><item><title>MySQL 三大日志和两阶段提交</title><link>https://wimi.space/posts/2025/06/mysql-undo-redo-binlog/</link><pubDate>Wed, 04 Jun 2025 10:39:54 +0800</pubDate><guid>https://wimi.space/posts/2025/06/mysql-undo-redo-binlog/</guid><description>&lt;h2 id="walwrite-ahead-logging">WAL（Write-Ahead Logging）&lt;/h2>
&lt;p>WAL 是 MySQL 数据库的核心日志机制，其核心原则是在将数据变更应用到数据库之前，先将这些变更写入日志。这种方式保证了即使系统发生崩溃，数据库也能通过回放日志恢复到一致的状态。&lt;/p>
&lt;p>以 InnoDB 存储引擎为例，当事务修改数据时，MySQL 会先将变更记录到 Redo log 中，再将数据写入磁盘。这样可以避免每次数据修改都要直接写入到数据文件，因为日志文件的写入操作通常比直接写入数据文件更高效。这种机制减少了直接磁盘 IO 的开销，并确保在崩溃恢复时能通过日志重放恢复未落盘的数据。&lt;/p>
&lt;h2 id="mysql-三大日志">MySQL 三大日志&lt;/h2>
&lt;p>MySQL 使用多种日志来实现不同的功能，不同的日志类型解决了数据库运行中不同层面的关键问题，最常见的是 Redo log、Undo log 和 Binlog。它们都遵循 WAL 机制，但用途和实现方式不同。&lt;/p>
&lt;ul>
&lt;li>Redo log 是 InnoDB 存储引擎级别的日志，主要服务于崩溃恢复。&lt;/li>
&lt;li>Undo log 是 InnoDB 存储引擎级别的日志，主要用于事务回滚和多版本并发控制（MVCC）的实现。&lt;/li>
&lt;li>Binlog 是 MySQL 服务级别的日志，主要服务于数据备份、主从复制和数据恢复。&lt;/li>
&lt;/ul>
&lt;h3 id="redo-log">Redo Log&lt;/h3>
&lt;p>Redo Log 是 MySQL InnoDB 存储引擎的重要组成部分，用于记录数据页的物理变更，保证事务的持久性和数据恢复，确保了数据库的稳定性和数据的一致性。&lt;/p>
&lt;p>Redo Log 首先存储在内存中的日志缓冲区中，在合适的时间从缓冲区刷写到磁盘上的日志文件中，默认有 ib_logfile1 和 ib_logfile2 两个日志文件，大小固定，循环使用。Redo Log 中记录的数据页变更信息通常包括表空间 ID、页号、偏移量和新值等。有了 Redo log，InnoDB 就可以保证即使数据库发生异常重启，之前提交的记录都不会丢失，这个能力称为 crash-safe。&lt;/p>
&lt;ul>
&lt;li>作用：用于崩溃恢复（Crash recovery）。&lt;/li>
&lt;li>特点：Redo log 是物理日志，记录的是数据库 Page 的低级别变更。当事务提交时，Redo log 确保这些变更可以在系统崩溃后被重新应用到数据库中，从而保证已提交事务的持久性。&lt;/li>
&lt;/ul>
&lt;h3 id="undo-log">Undo Log&lt;/h3>
&lt;p>Undo Log 是 MySQL InnoDB 存储引擎中的一种核心日志机制，它主要解决了事务的回滚和实现多版本并发控制 (MVCC)。它是保证数据库 ACID 特性 中 原子性和隔离性的重要基础。&lt;/p></description></item><item><title>MySQL 的基本架构</title><link>https://wimi.space/posts/2025/06/mysql-architecture-desc/</link><pubDate>Tue, 03 Jun 2025 11:26:52 +0800</pubDate><guid>https://wimi.space/posts/2025/06/mysql-architecture-desc/</guid><description>&lt;blockquote>
&lt;p>⚠️️ 整理自互联网，用于概念了解。&lt;/p>&lt;/blockquote>
&lt;p>MySQL 的架构采用经典的『客户端/服务器模型』和『分层设计』，核心组件协同工作以处理查询、管理数据、保证事务和提供高可用性等。其核心架构通常分为以下几个主要层次和组件，&lt;a href="https://dev.mysql.com/doc/refman/8.4/en/pluggable-storage-overview.html">详见官方文档&lt;/a>。&lt;/p>
&lt;p>&lt;img src="https://dev.mysql.com/doc/refman/8.4/en/images/mysql-architecture.png" alt="">&lt;/p>
&lt;h2 id="连接层">连接层&lt;/h2>
&lt;h3 id="connectors">Connectors&lt;/h3>
&lt;p>连接器 &lt;a href="https://www.mysql.com/products/connector/">Connectors&lt;/a> 是 MySQL 官方提供的驱动程序，用于让不同编程语言编写的应用程序（客户端）能够与 MySQL 服务器通信。它实现了 MySQL 的通信协议，封装了底层连接细节，使开发者无需处理网络传输、数据封包等复杂操作。严格来说连接器是客户端的组件，而非服务端。&lt;/p>
&lt;h2 id="服务层">服务层&lt;/h2>
&lt;h3 id="sql-interface">SQL Interface&lt;/h3>
&lt;p>是 MySQL 服务层的核心组件之一，负责接收客户端发送的 SQL 命令，并将执行结果返回给客户端。是连接客户端请求与数据库内部处理的桥梁。&lt;/p>
&lt;p>主要的核心功能有：&lt;/p>
&lt;ol>
&lt;li>接收 SQL 命令：客户端通过连接器发送的 SQL 语句首先到达 SQL 接口。支持多种交互协议。&lt;/li>
&lt;li>分发 SQL 到内部组件：将 SQL 语句交给解析器进行语法和语义分析，生成解析树。解析后的指令交给优化器生成最优执行计划。&lt;/li>
&lt;li>处理执行结果：接收存储引擎返回的数据（如查询结果集），将结果转换为客户端兼容的格式（如二进制流、表格形式）。通过连接层将结果返回给客户端。&lt;/li>
&lt;/ol>
&lt;h3 id="parser">Parser&lt;/h3>
&lt;p>解析 SQL 语句，检查语法正确性，分解 SQL 为可执行的逻辑单元。&lt;/p>
&lt;h3 id="optimizer">Optimizer&lt;/h3>
&lt;p>优化 SQL 查询，生成高效的执行计划。基于索引、表统计信息选择最佳执行路径（如选择 JOIN 顺序、索引使用）。&lt;/p>
&lt;h3 id="executor">Executor&lt;/h3>
&lt;p>根据优化器生成的执行计划，调用存储引擎提供的接口来执行操作。如果启用了查询缓存（在 MySQL 5.7 及更早版本中），会先检查查询缓存。&lt;/p>
&lt;h3 id="caches">Caches&lt;/h3>
&lt;p>作用：缓存查询结果以提高性能。功能：命中相同查询直接返回结果（8.0 已移除，推荐外部缓存如 Redis）。&lt;/p>
&lt;h2 id="存储引擎层">存储引擎层&lt;/h2>
&lt;p>负责数据的物理存储、检索和管理。MySQL 的核心优势之一是其可插拔的存储引擎架构。存储引擎是表级别的，同一个数据库的不同表可以使用不同的存储引擎。存储引擎通过定义良好的 API 与服务层交互，这些 API 屏蔽了不同引擎实现的差异。&lt;/p>
&lt;p>常见引擎：&lt;/p>
&lt;h3 id="innodb">InnoDB&lt;/h3>
&lt;p>MySQL 的默认引擎&lt;/p></description></item></channel></rss>