Update MySQL.md

重新绘制配图,补充一条 SQL 的执行过程以及 B 树与 B+ 树的比较,更新数据类型等描述。
This commit is contained in:
CyC2018
2026-10-26 22:03:56 +08:00
parent 52536facd5
commit b4647213f8
11 changed files with 435 additions and 11 deletions
+34 -11
View File
@@ -9,6 +9,7 @@
* [索引的优点](#索引的优点)
* [索引的使用条件](#索引的使用条件)
* [二、查询性能优化](#二查询性能优化)
* [一条 SQL 的执行过程](#一条-sql-的执行过程)
* [使用 Explain 进行分析](#使用-explain-进行分析)
* [优化数据访问](#优化数据访问)
* [重构查询方式](#重构查询方式)
@@ -44,7 +45,7 @@
#### 1. 数据结构
B Tree 指的是 Balance Tree,也就是平衡树。平衡树是一棵查找树,并且所有叶子节点位于同一层。
B Tree 是一种多路平衡查找树,每个节点可以有多个子节点,并且所有叶子节点位于同一层。
B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。
@@ -56,7 +57,7 @@ B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具
进行查找操作时,首先在根节点进行二分查找,找到一个 key 所在的指针,然后递归地在指针所指向的节点进行查找。直到查找到叶子节点,然后在叶子节点上进行二分查找,找出 key 所对应的 data。
插入删除操作会破坏平衡树的平衡性,因此在进行插入删除操作之后,需要对树进行分裂、合并、旋转等操作来维护平衡性。
插入删除操作会破坏平衡树的平衡性,因此在进行插入删除操作之后,需要对节点进行分裂、合并或者从相邻节点借用 key 来维护平衡性。
#### 3. 与红黑树的比较
@@ -66,6 +67,8 @@ B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具
平衡树的树高 O(h)=O(log<sub>d</sub>N),其中 d 为每个节点的出度。红黑树的出度为 2,而 B+ Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多。
例如本机 MySQL 8.0 中一张 200 万行的 orders 表(平均每行约 200 字节),从 .ibd 文件中读取主键索引根页的 PAGE_LEVEL,值为 2,也就是树高为 3:根页中有 27 条记录,指向 27 个中间页,叶子页约 2.5 万个,每页约 80 行。查找任意一行只需要读 3 个页,而且根页和中间页通常常驻在缓冲池中,实际最多 1 次磁盘 I/O;换成二叉树,200 万个节点的高度约为 21。
(二)磁盘访问原理
操作系统一般将内存和磁盘分割成固定大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点。
@@ -76,6 +79,12 @@ B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具
为了减少磁盘 I/O 操作,磁盘往往不是严格按需读取,而是每次都会预读。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的磁盘旋转时间,速度会非常快。并且可以利用预读特性,相邻的节点也能够被预先载入。
#### 4. 与 B Tree 的比较
- B Tree 的每个节点都存放数据,B+ Tree 只有叶子节点存放数据,内部节点只存放 key 和指针,同样大小的页能放下更多的 key,树更矮;
- B+ Tree 的叶子节点按 key 的顺序用指针连接,范围查询找到起点后顺序扫描叶子节点即可,B Tree 则要在不同层的节点之间来回遍历;
- B+ Tree 的每次查找都要走到叶子节点,查询的磁盘 I/O 次数稳定。
### MySQL 索引
索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。
@@ -214,6 +223,8 @@ WHERE user_id = 5 AND status > 0 AND create_time > '2025-01-05' -- key_len = 5
不过第二条语句的 Extra 为 Using index condition,也就是索引下推:create_time 虽然不能缩小扫描范围,但仍然在索引上过滤,不满足条件的行不需要回表,所以范围条件之后的列并不是完全没有作用。
例如 `user_id = 1001 AND create_time >= '2025-01-20'` 跳过了 status,只能用 user_id 定位(key_len = 4),user_id 为 1001 的 20 行中只有 3 行满足时间条件。开启索引下推时 Handler_read_next 为 3,用 `SET optimizer_switch='index_condition_pushdown=off'` 关闭后为 20,也就是多回表了 17 次。
需要注意的是,是否使用索引是优化器按成本估算决定的。走二级索引的每一行都要回表,匹配的行越多回表越多,超过一定比例后全表扫描反而更快:`create_time > '2025-01-23'` 匹配 9.9 万行,走 idx_create_time;`create_time > '2025-01-02'` 匹配 191 万行,就选择了全表扫描。IN 也一样,`user_id IN` 5 个值时走索引,3 万多个值时全表扫描。因此同一种写法有时走索引有时不走,可能是因为匹配的数据量不同。
### 索引的优点
@@ -238,6 +249,18 @@ WHERE user_id = 5 AND status > 0 AND create_time > '2025-01-05' -- key_len = 5
## 二、查询性能优化
### 一条 SQL 的执行过程
MySQL 分为 Server 层和存储引擎层:Server 层负责连接、解析、优化和执行,存储引擎负责数据的存储和读取,例如 InnoDB。
<div align="center"> <img src="pics/sql-flow.png" width="760px"/> </div><br>
图中的数据来自本机 MySQL 8.0 上 200 万行的 orders 表。优化器并不是“有索引就用”:idx_create_time 也能用于这个查询,但估算要扫描 98 万行、再回表 98 万次,代价比全表扫描还高;联合索引 idx_user_status_time 的第一列就是 user_id,只需要扫描 20 行。用 EXPLAIN 可以看到最终的选择(key: idx_user_status_time,rows: 20),开启 optimizer_trace 可以看到每种方案的估算代价。
需要注意的是,MySQL 5.7 及以前在解析之前还会查询缓存,表一有修改相关的缓存就全部失效,命中率很低,8.0 已经移除了查询缓存。
更新语句的执行过程与查询相同,只是在执行器调用 InnoDB 修改数据时,还要写 undo log、redo log 和 binlog,见下文的 [日志](#七日志)。
### 使用 Explain 进行分析
Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explain 结果来优化查询语句。
@@ -395,7 +418,7 @@ LIMIT 10;
- 崩溃恢复:MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
- 其它特性:MyISAM 支持压缩表和空间数据索引。
- 其它特性:MyISAM 支持压缩表,空间数据索引 InnoDB 从 MySQL 5.7 起也支持。
## 四、数据类型
@@ -403,13 +426,13 @@ LIMIT 10;
TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT 分别使用 8, 16, 24, 32, 64 位存储空间,一般情况下越小的列越好。
INT(11) 中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。
INT(11) 中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。MySQL 8.0.17 起这种写法已被废弃,建表时会给出警告 Integer display width is deprecated,SHOW CREATE TABLE 中也只显示 int。
### 浮点数
FLOAT 和 DOUBLE 为浮点类型,DECIMAL 为高精度小数类型。CPU 原生支持浮点运算,但是不支持 DECIMAL 类型的计算,因此 DECIMAL 的计算比浮点类型需要更高的代价。
FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽,例如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。
DECIMAL 可以指定精度,例如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。FLOAT 和 DOUBLE 也能写成 FLOAT(7, 4) 这种形式,但 MySQL 8.0.17 起已被废弃。金额等需要精确计算的数据应该使用 DECIMAL。
### 字符串
@@ -425,7 +448,7 @@ MySQL 提供了两种相似的日期时间类型:DATETIME 和 TIMESTAMP。
#### 1. DATETIME
能够保存从 1000 年到 9999 年的日期和时间,精度为秒,使用 5 字节的存储空间(MySQL 5.6.4 之前为 8 字节)。
能够保存从 1000 年到 9999 年的日期和时间,精度默认为秒,可以用 DATETIME(6) 精确到微秒,使用 5 字节的存储空间(MySQL 5.6.4 之前为 8 字节),小数部分另外占用 0~3 字节。
它与时区无关。
@@ -451,7 +474,7 @@ TIMESTAMP 比 DATETIME 少占用 1 字节,但是只能表示到 2038 年,需
当一个表的数据不断增多时,Sharding 是必然的选择,它可以将数据分布到集群的不同节点上,从而缓解单个数据库的压力。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/63c2909f-0c5f-496f-9fe5-ee9176b31aba.jpg" width=""> </div><br>
<div align="center"> <img src="pics/shard-horizontal.png" width="760px"/> </div><br>
### 垂直切分
@@ -459,7 +482,7 @@ TIMESTAMP 比 DATETIME 少占用 1 字节,但是只能表示到 2038 年,需
在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库、用户数据库等。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/e130e5b8-b19a-4f1e-b860-223040525cf6.jpg" width=""> </div><br>
<div align="center"> <img src="pics/shard-vertical.png" width="760px"/> </div><br>
### Sharding 策略
@@ -493,7 +516,7 @@ TIMESTAMP 比 DATETIME 少占用 1 字节,但是只能表示到 2038 年,需
- **I/O 线程** :负责从主服务器上读取二进制日志,并写入从服务器的中继日志(Relay log)。
- **SQL 线程** :负责读取中继日志,解析出主服务器已经执行的数据更改并在从服务器中重放(Replay)。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/master-slave.png" width=""> </div><br>
<div align="center"> <img src="pics/mysql-replication.png" width="760px"/> </div><br>
### 读写分离
@@ -502,12 +525,11 @@ TIMESTAMP 比 DATETIME 少占用 1 字节,但是只能表示到 2038 年,需
读写分离能提高性能的原因在于:
- 主从服务器负责各自的读和写,极大程度缓解了锁的争用;
- 从服务器可以使用 MyISAM,提升查询性能以及节约系统开销;
- 增加冗余,提高可用性。
读写分离常用代理方式来实现,代理服务器接收应用层传来的读写请求,然后决定转发到哪个服务器。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/master-slave-proxy.png" width=""> </div><br>
<div align="center"> <img src="pics/mysql-rw-split.png" width="760px"/> </div><br>
## 七、日志
@@ -588,3 +610,4 @@ innodb_flush_log_at_trx_commit 和 sync_binlog 的默认值都是 1,也就是
- [MySQL 8.0: Type Conversion in Expression Evaluation](https://dev.mysql.com/doc/refman/8.0/en/type-conversion.html)
- [MySQL 8.0: Redo Log](https://dev.mysql.com/doc/refman/8.0/en/innodb-redo-log.html)
- [MySQL 8.0: The Binary Log](https://dev.mysql.com/doc/refman/8.0/en/binary-log.html)
- [Replication Implementation | MySQL 8.0 Reference Manual](https://dev.mysql.com/doc/refman/8.0/en/replication-implementation.html)
Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

+73
View File
@@ -0,0 +1,73 @@
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="300" viewBox="0 0 760 300">
<style>
text { font-family: "PingFang SC", "Microsoft YaHei", Helvetica, Arial, sans-serif; fill: #3F3F3F; font-size: 12px; }
.title { font-weight: bold; font-size: 15px; fill: #333; text-anchor: middle; }
.b { font-weight: bold; }
.c { text-anchor: middle; }
.e { text-anchor: end; }
.sub { font-size: 11px; fill: #6E6E6E; }
.note { fill: #C0605A; }
.ok { fill: #6A9F68; }
.mono { font-family: Menlo, Consolas, monospace; }
.arrow { stroke: #555; stroke-width: 1.1; fill: none; marker-end: url(#a); }
.red { stroke: #C0605A; marker-end: url(#r); }
.green { stroke: #6A9F68; marker-end: url(#g); }
.dash { stroke-dasharray: 5 4; }
.line { stroke: #555; stroke-width: 1.1; fill: none; }
.frame { fill: none; stroke: #999; stroke-width: 1; stroke-dasharray: 4 3; }
.shadow { fill: #DADADA; }
</style>
<defs>
<marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#555"/>
</marker>
<marker id="r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#C0605A"/>
</marker>
<marker id="g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#6A9F68"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#FFFFFF"/>
<text x="380" y="28" class="title">主从复制(MySQL 8.0 本机实测的线程)</text>
<rect x="20" y="46" width="250" height="200" rx="8" class="frame"/>
<text x="30" y="64" class="b">主库 :3307</text>
<rect x="400" y="46" width="340" height="200" rx="8" class="frame"/>
<text x="410" y="64" class="b">从库 :3308</text>
<path d="M47,86 v34 a23,6 0 0 0 46,0 v-34" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1.2"/>
<ellipse cx="70" cy="86" rx="23" ry="6" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1.2"/>
<path d="M47,103 a23,6 0 0 0 46,0" fill="none" stroke="#7FB2D6" stroke-width="1"/>
<text x="70" y="144" class="c sub">数据</text>
<path d="M160,82 h29 l9,9 v37 h-38 z" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1.2"/>
<path d="M189,82 v9 h9" fill="none" stroke="#BFBEBA" stroke-width="1"/>
<line x1="166" y1="99" x2="191" y2="99" stroke="#BFBEBA" stroke-width="1"/>
<line x1="166" y1="106" x2="191" y2="106" stroke="#BFBEBA" stroke-width="1"/>
<line x1="166" y1="113" x2="191" y2="113" stroke="#BFBEBA" stroke-width="1"/>
<text x="214" y="110" class="sub mono">binlog</text>
<line x1="96" y1="104" x2="156" y2="104" class="arrow "/>
<text x="126" y="96" class="c sub">提交</text>
<rect x="110" y="180" width="140" height="36" rx="6" fill="#C6ECE9" stroke="#7DBCB7"/>
<text x="180" y="202.5" class="c sub">Binlog Dump 线程</text>
<line x1="179" y1="130" x2="179" y2="178" class="arrow "/>
<rect x="420" y="180" width="120" height="36" rx="6" fill="#C6ECE9" stroke="#7DBCB7"/>
<text x="480" y="202.5" class="c sub">I/O 线程</text>
<line x1="252" y1="198" x2="418" y2="198" class="arrow "/>
<text x="335" y="190" class="c sub">发送 binlog 事件</text>
<path d="M461,82 h29 l9,9 v37 h-38 z" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1.2"/>
<path d="M490,82 v9 h9" fill="none" stroke="#BFBEBA" stroke-width="1"/>
<line x1="467" y1="99" x2="492" y2="99" stroke="#BFBEBA" stroke-width="1"/>
<line x1="467" y1="106" x2="492" y2="106" stroke="#BFBEBA" stroke-width="1"/>
<line x1="467" y1="113" x2="492" y2="113" stroke="#BFBEBA" stroke-width="1"/>
<text x="454" y="110" class="sub mono e">relay log</text>
<line x1="480" y1="178" x2="480" y2="132" class="arrow "/>
<rect x="580" y="180" width="140" height="36" rx="6" fill="#C6ECE9" stroke="#7DBCB7"/>
<text x="650" y="202.5" class="c sub">SQL 线程 + 4 个 worker</text>
<path d="M502,104 C560,104 610,140 630,178" class="arrow"/>
<path d="M647,86 v34 a23,6 0 0 0 46,0 v-34" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1.2"/>
<ellipse cx="670" cy="86" rx="23" ry="6" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1.2"/>
<path d="M647,103 a23,6 0 0 0 46,0" fill="none" stroke="#7FB2D6" stroke-width="1"/>
<text x="700" y="110" class="sub">数据</text>
<line x1="670" y1="178" x2="670" y2="130" class="arrow "/>
<text x="30" y="272" class="sub">主库提交时不等从库,从库落后的时间就是复制延迟。8.0.27 起默认 4 个 worker 并行重放(replica_parallel_workers = 4)。</text>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

+70
View File
@@ -0,0 +1,70 @@
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="300" viewBox="0 0 760 300">
<style>
text { font-family: "PingFang SC", "Microsoft YaHei", Helvetica, Arial, sans-serif; fill: #3F3F3F; font-size: 12px; }
.title { font-weight: bold; font-size: 15px; fill: #333; text-anchor: middle; }
.b { font-weight: bold; }
.c { text-anchor: middle; }
.e { text-anchor: end; }
.sub { font-size: 11px; fill: #6E6E6E; }
.note { fill: #C0605A; }
.ok { fill: #6A9F68; }
.mono { font-family: Menlo, Consolas, monospace; }
.arrow { stroke: #555; stroke-width: 1.1; fill: none; marker-end: url(#a); }
.red { stroke: #C0605A; marker-end: url(#r); }
.green { stroke: #6A9F68; marker-end: url(#g); }
.dash { stroke-dasharray: 5 4; }
.line { stroke: #555; stroke-width: 1.1; fill: none; }
.frame { fill: none; stroke: #999; stroke-width: 1; stroke-dasharray: 4 3; }
.shadow { fill: #DADADA; }
</style>
<defs>
<marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#555"/>
</marker>
<marker id="r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#C0605A"/>
</marker>
<marker id="g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#6A9F68"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#FFFFFF"/>
<text x="380" y="28" class="title">读写分离与复制延迟</text>
<rect x="47" y="110" width="46" height="13" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1.2"/>
<circle cx="54" cy="116.5" r="2" fill="#7FB2D6"/>
<line x1="61" y1="116.5" x2="86" y2="116.5" stroke="#7FB2D6" stroke-width="1"/>
<rect x="47" y="126" width="46" height="13" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1.2"/>
<circle cx="54" cy="132.5" r="2" fill="#7FB2D6"/>
<line x1="61" y1="132.5" x2="86" y2="132.5" stroke="#7FB2D6" stroke-width="1"/>
<rect x="47" y="142" width="46" height="13" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1.2"/>
<circle cx="54" cy="148.5" r="2" fill="#7FB2D6"/>
<line x1="61" y1="148.5" x2="86" y2="148.5" stroke="#7FB2D6" stroke-width="1"/>
<text x="70" y="174" class="c b">应用</text>
<rect x="170" y="108" width="140" height="46" rx="6" fill="#EAE9E7" stroke="#BFBEBA"/>
<text x="240" y="128" class="c sub b" style="fill:#444">代理或数据源路由</text>
<text x="240" y="144" class="c sub">按 SQL 类型转发</text>
<line x1="96" y1="130" x2="166" y2="130" class="arrow "/>
<path d="M445,66 v36 a25,6 0 0 0 50,0 v-36" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1.2"/>
<ellipse cx="470" cy="66" rx="25" ry="6" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1.2"/>
<path d="M445,84 a25,6 0 0 0 50,0" fill="none" stroke="#7FB2D6" stroke-width="1"/>
<text x="510" y="82" class="sub">主库:写和实时读</text>
<path d="M448,156 v30 a22,6 0 0 0 44,0 v-30" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1.2"/>
<ellipse cx="470" cy="156" rx="22" ry="6" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1.2"/>
<path d="M448,171 a22,6 0 0 0 44,0" fill="none" stroke="#7DBCB7" stroke-width="1"/>
<text x="500" y="176" class="sub">从库 1:读</text>
<path d="M448,226 v30 a22,6 0 0 0 44,0 v-30" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1.2"/>
<ellipse cx="470" cy="226" rx="22" ry="6" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1.2"/>
<path d="M448,241 a22,6 0 0 0 44,0" fill="none" stroke="#7DBCB7" stroke-width="1"/>
<text x="500" y="246" class="sub">从库 2:读</text>
<path d="M496,96 C620,110 620,160 588,170" class="arrow dash"/>
<path d="M496,100 C660,120 660,230 588,240" class="arrow dash"/>
<text x="640" y="150" class="sub">复制</text>
<line x1="312" y1="120" x2="444" y2="84" class="arrow "/>
<text x="370" y="92" class="c sub mono">INSERT、UPDATE</text>
<line x1="312" y1="140" x2="446" y2="170" class="arrow "/>
<line x1="312" y1="146" x2="446" y2="238" class="arrow "/>
<text x="330" y="196" class="sub mono">SELECT</text>
<text x="30" y="272" class="sub">本机实测:主库一个事务插入 50 万行,从库在 1.4 秒后才一次性看到这些行。刚写完就去从库读,可能读不到自己写的数据。</text>
<text x="30" y="290" class="sub">常用做法:写入后的一段时间内这个用户的读请求走主库,或者把要求实时的读请求固定发到主库。</text>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

+87
View File
@@ -0,0 +1,87 @@
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="280" viewBox="0 0 760 280">
<style>
text { font-family: "PingFang SC", "Microsoft YaHei", Helvetica, Arial, sans-serif; fill: #3F3F3F; font-size: 12px; }
.title { font-weight: bold; font-size: 15px; fill: #333; text-anchor: middle; }
.b { font-weight: bold; }
.c { text-anchor: middle; }
.e { text-anchor: end; }
.sub { font-size: 11px; fill: #6E6E6E; }
.note { fill: #C0605A; }
.ok { fill: #6A9F68; }
.mono { font-family: Menlo, Consolas, monospace; }
.arrow { stroke: #555; stroke-width: 1.1; fill: none; marker-end: url(#a); }
.red { stroke: #C0605A; marker-end: url(#r); }
.green { stroke: #6A9F68; marker-end: url(#g); }
.dash { stroke-dasharray: 5 4; }
.line { stroke: #555; stroke-width: 1.1; fill: none; }
.frame { fill: none; stroke: #999; stroke-width: 1; stroke-dasharray: 4 3; }
.shadow { fill: #DADADA; }
</style>
<defs>
<marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#555"/>
</marker>
<marker id="r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#C0605A"/>
</marker>
<marker id="g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#6A9F68"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#FFFFFF"/>
<text x="380" y="28" class="title">水平切分:按 user_id % 2 把行分到两个库</text>
<text x="120" y="82" class="c sub b" style="fill:#444">orders(切分前)</text>
<rect x="30" y="90" width="90" height="24" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="75" y="106" class="c sub" style="fill:#444">order_id</text>
<rect x="120" y="90" width="90" height="24" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="165" y="106" class="c sub" style="fill:#444">user_id</text>
<rect x="30" y="114" width="90" height="24" rx="3" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1"/>
<text x="75" y="130" class="c sub mono" style="fill:#444">1024</text>
<rect x="120" y="114" width="90" height="24" rx="3" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1"/>
<text x="165" y="130" class="c sub mono" style="fill:#444">7</text>
<rect x="30" y="138" width="90" height="24" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="75" y="154" class="c sub mono" style="fill:#444">1025</text>
<rect x="120" y="138" width="90" height="24" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="165" y="154" class="c sub mono" style="fill:#444">8</text>
<rect x="30" y="162" width="90" height="24" rx="3" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1"/>
<text x="75" y="178" class="c sub mono" style="fill:#444">1026</text>
<rect x="120" y="162" width="90" height="24" rx="3" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1"/>
<text x="165" y="178" class="c sub mono" style="fill:#444">9</text>
<rect x="30" y="186" width="90" height="24" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="75" y="202" class="c sub mono" style="fill:#444">1027</text>
<rect x="120" y="186" width="90" height="24" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="165" y="202" class="c sub mono" style="fill:#444">10</text>
<text x="410" y="58" class="c sub b" style="fill:#444">库 0 的 orders</text>
<rect x="320" y="66" width="90" height="24" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="365" y="82" class="c sub" style="fill:#444">order_id</text>
<rect x="410" y="66" width="90" height="24" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="455" y="82" class="c sub" style="fill:#444">user_id</text>
<rect x="320" y="90" width="90" height="24" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="365" y="106" class="c sub mono" style="fill:#444">1025</text>
<rect x="410" y="90" width="90" height="24" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="455" y="106" class="c sub mono" style="fill:#444">8</text>
<rect x="320" y="114" width="90" height="24" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="365" y="130" class="c sub mono" style="fill:#444">1027</text>
<rect x="410" y="114" width="90" height="24" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="455" y="130" class="c sub mono" style="fill:#444">10</text>
<text x="410" y="168" class="c sub b" style="fill:#444">库 1 的 orders</text>
<rect x="320" y="176" width="90" height="24" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="365" y="192" class="c sub" style="fill:#444">order_id</text>
<rect x="410" y="176" width="90" height="24" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="455" y="192" class="c sub" style="fill:#444">user_id</text>
<rect x="320" y="200" width="90" height="24" rx="3" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1"/>
<text x="365" y="216" class="c sub mono" style="fill:#444">1024</text>
<rect x="410" y="200" width="90" height="24" rx="3" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1"/>
<text x="455" y="216" class="c sub mono" style="fill:#444">7</text>
<rect x="320" y="224" width="90" height="24" rx="3" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1"/>
<text x="365" y="240" class="c sub mono" style="fill:#444">1026</text>
<rect x="410" y="224" width="90" height="24" rx="3" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1"/>
<text x="455" y="240" class="c sub mono" style="fill:#444">9</text>
<line x1="214" y1="130" x2="316" y2="106" class="arrow "/>
<line x1="214" y1="160" x2="316" y2="212" class="arrow "/>
<text x="530" y="106" class="sub">表结构相同,每个库只存一部分行</text>
<text x="530" y="216" class="sub">按 user_id 查询只访问一个库;</text>
<text x="530" y="234" class="sub">按其它条件查询要访问所有库</text>
<text x="30" y="268" class="sub">同一个用户的订单落在同一个库,查询“某用户的订单”不需要跨库。</text>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

+76
View File
@@ -0,0 +1,76 @@
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="280" viewBox="0 0 760 280">
<style>
text { font-family: "PingFang SC", "Microsoft YaHei", Helvetica, Arial, sans-serif; fill: #3F3F3F; font-size: 12px; }
.title { font-weight: bold; font-size: 15px; fill: #333; text-anchor: middle; }
.b { font-weight: bold; }
.c { text-anchor: middle; }
.e { text-anchor: end; }
.sub { font-size: 11px; fill: #6E6E6E; }
.note { fill: #C0605A; }
.ok { fill: #6A9F68; }
.mono { font-family: Menlo, Consolas, monospace; }
.arrow { stroke: #555; stroke-width: 1.1; fill: none; marker-end: url(#a); }
.red { stroke: #C0605A; marker-end: url(#r); }
.green { stroke: #6A9F68; marker-end: url(#g); }
.dash { stroke-dasharray: 5 4; }
.line { stroke: #555; stroke-width: 1.1; fill: none; }
.frame { fill: none; stroke: #999; stroke-width: 1; stroke-dasharray: 4 3; }
.shadow { fill: #DADADA; }
</style>
<defs>
<marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#555"/>
</marker>
<marker id="r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#C0605A"/>
</marker>
<marker id="g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#6A9F68"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#FFFFFF"/>
<text x="380" y="28" class="title">垂直切分:按列拆成两张表</text>
<text x="230" y="58" class="c sub b" style="fill:#444">product(切分前)</text>
<rect x="30" y="66" width="80" height="26" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="70" y="83" class="c sub mono" style="fill:#444">id</text>
<rect x="30" y="92" width="80" height="26" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="70" y="109" class="c sub" style="fill:#444">1024</text>
<rect x="110" y="66" width="80" height="26" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="150" y="83" class="c sub mono" style="fill:#444">name</text>
<rect x="110" y="92" width="80" height="26" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="150" y="109" class="c sub" style="fill:#444">键盘</text>
<rect x="190" y="66" width="80" height="26" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="230" y="83" class="c sub mono" style="fill:#444">price</text>
<rect x="190" y="92" width="80" height="26" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="230" y="109" class="c sub" style="fill:#444">199</text>
<rect x="270" y="66" width="160" height="26" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="350" y="83" class="c sub mono" style="fill:#444">detail_html</text>
<rect x="270" y="92" width="160" height="26" rx="3" fill="#E6E3FA" stroke="#B8B0DE" stroke-width="1"/>
<text x="350" y="109" class="c sub" style="fill:#444">&lt;div&gt;…20 KB…&lt;/div&gt;</text>
<line x1="150" y1="124" x2="150" y2="146" class="arrow "/>
<line x1="400" y1="124" x2="500" y2="146" class="arrow "/>
<rect x="30" y="168" width="80" height="26" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="70" y="185" class="c sub mono" style="fill:#444">id</text>
<rect x="30" y="194" width="80" height="26" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="70" y="211" class="c sub" style="fill:#444">1024</text>
<rect x="110" y="168" width="80" height="26" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="150" y="185" class="c sub mono" style="fill:#444">name</text>
<rect x="110" y="194" width="80" height="26" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="150" y="211" class="c sub" style="fill:#444">键盘</text>
<rect x="190" y="168" width="80" height="26" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="230" y="185" class="c sub mono" style="fill:#444">price</text>
<rect x="190" y="194" width="80" height="26" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="230" y="211" class="c sub" style="fill:#444">199</text>
<text x="150" y="240" class="c sub">product:常用的列</text>
<rect x="420" y="168" width="80" height="26" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="460" y="185" class="c sub mono" style="fill:#444">id</text>
<rect x="420" y="194" width="80" height="26" rx="3" fill="#C6E4FB" stroke="#7FB2D6" stroke-width="1"/>
<text x="460" y="211" class="c sub" style="fill:#444">1024</text>
<rect x="500" y="168" width="160" height="26" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="580" y="185" class="c sub mono" style="fill:#444">detail_html</text>
<rect x="500" y="194" width="160" height="26" rx="3" fill="#E6E3FA" stroke="#B8B0DE" stroke-width="1"/>
<text x="580" y="211" class="c sub" style="fill:#444">&lt;div&gt;…20 KB…&lt;/div&gt;</text>
<text x="540" y="240" class="c sub">product_detail:大字段</text>
<text x="30" y="264" class="sub">列表页只查 product,一页能放下更多行;打开详情时再按 id 查 product_detail。</text>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

+95
View File
@@ -0,0 +1,95 @@
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="470" viewBox="0 0 760 470">
<style>
text { font-family: "PingFang SC", "Microsoft YaHei", Helvetica, Arial, sans-serif; fill: #3F3F3F; font-size: 12px; }
.title { font-weight: bold; font-size: 15px; fill: #333; text-anchor: middle; }
.b { font-weight: bold; }
.c { text-anchor: middle; }
.e { text-anchor: end; }
.sub { font-size: 11px; fill: #6E6E6E; }
.note { fill: #C0605A; }
.ok { fill: #6A9F68; }
.mono { font-family: Menlo, Consolas, monospace; }
.arrow { stroke: #555; stroke-width: 1.1; fill: none; marker-end: url(#a); }
.red { stroke: #C0605A; marker-end: url(#r); }
.green { stroke: #6A9F68; marker-end: url(#g); }
.dash { stroke-dasharray: 5 4; }
.line { stroke: #555; stroke-width: 1.1; fill: none; }
.frame { fill: none; stroke: #999; stroke-width: 1; stroke-dasharray: 4 3; }
.shadow { fill: #DADADA; }
</style>
<defs>
<marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#555"/>
</marker>
<marker id="r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#C0605A"/>
</marker>
<marker id="g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1.5 L9.5,5 L1,8.5 z" fill="#6A9F68"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#FFFFFF"/>
<text x="380" y="28" class="title">一条 SELECT 在 MySQL 中的执行过程</text>
<rect x="30" y="46" width="420" height="56" rx="5" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1.2"/>
<line x1="30" y1="60" x2="450" y2="60" stroke="#BBBBBB" stroke-width="1"/>
<circle cx="39" cy="53" r="2.2" fill="#BBBBBB"/>
<circle cx="47" cy="53" r="2.2" fill="#BBBBBB"/>
<circle cx="55" cy="53" r="2.2" fill="#BBBBBB"/>
<text x="39" y="76" class="sub mono">SELECT * FROM orders WHERE user_id = 1024</text>
<text x="39" y="92" class="sub mono">  AND create_time &gt; '2025-01-10'</text>
<rect x="20" y="126" width="720" height="268" rx="8" class="frame"/>
<text x="30" y="144" class="b">Server 层</text>
<rect x="40" y="150" width="90" height="34" rx="6" fill="#EAE9E7" stroke="#BFBEBA"/>
<text x="85" y="171.5" class="c sub b">连接器</text>
<text x="142" y="165" class="sub" style="fill:#444">校验用户名、密码和权限,维持连接</text>
<rect x="40" y="196" width="90" height="34" rx="6" fill="#EAE9E7" stroke="#BFBEBA"/>
<text x="85" y="217.5" class="c sub b">解析器</text>
<text x="142" y="211" class="sub" style="fill:#444">词法、语法分析,生成语法树</text>
<text x="142" y="226" class="sub note">写成 selec 时报 ERROR 1064</text>
<line x1="85" y1="184" x2="85" y2="194" class="arrow "/>
<rect x="40" y="242" width="90" height="34" rx="6" fill="#EAE9E7" stroke="#BFBEBA"/>
<text x="85" y="263.5" class="c sub b">预处理</text>
<text x="142" y="257" class="sub" style="fill:#444">检查表和列是否存在</text>
<text x="142" y="272" class="sub note">写成 amout 时报 ERROR 1054 Unknown column</text>
<line x1="85" y1="230" x2="85" y2="240" class="arrow "/>
<rect x="40" y="288" width="90" height="34" rx="6" fill="#E6E3FA" stroke="#B8B0DE"/>
<text x="85" y="309.5" class="c sub b">优化器</text>
<text x="142" y="303" class="sub" style="fill:#444">估算各种执行方式的代价,</text>
<text x="142" y="318" class="sub" style="fill:#444">选择代价最小的(右表)</text>
<line x1="85" y1="276" x2="85" y2="286" class="arrow "/>
<rect x="40" y="334" width="90" height="34" rx="6" fill="#C6E4FB" stroke="#7FB2D6"/>
<text x="85" y="355.5" class="c sub b">执行器</text>
<text x="142" y="349" class="sub" style="fill:#444">按执行计划调用存储引擎的接口,过滤、返回结果</text>
<line x1="85" y1="322" x2="85" y2="332" class="arrow "/>
<line x1="85" y1="104" x2="85" y2="148" class="arrow "/>
<text x="460" y="154" class="sub b" style="fill:#444">优化器估算的代价(optimizer trace)</text>
<rect x="460" y="162" width="140" height="22" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="530" y="177" class="c sub" style="fill:#444">方案</text>
<rect x="600" y="162" width="80" height="22" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="640" y="177" class="c sub" style="fill:#444">估算行数</text>
<rect x="680" y="162" width="50" height="22" rx="3" fill="#EAE9E7" stroke="#BFBEBA" stroke-width="1"/>
<text x="705" y="177" class="c sub" style="fill:#444">代价</text>
<rect x="460" y="184" width="140" height="24" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="530" y="200" class="c sub" style="fill:#444">全表扫描</text>
<rect x="600" y="184" width="80" height="24" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="640" y="200" class="c sub" style="fill:#444">1978736 行</text>
<rect x="680" y="184" width="50" height="24" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="705" y="200" class="c sub mono" style="fill:#444">222512</text>
<rect x="460" y="208" width="140" height="24" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="530" y="224" class="c sub mono" style="fill:#444">idx_create_time</text>
<rect x="600" y="208" width="80" height="24" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="640" y="224" class="c sub" style="fill:#444">989368 行</text>
<rect x="680" y="208" width="50" height="24" rx="3" fill="#FFFFFF" stroke="#BBBBBB" stroke-width="1"/>
<text x="705" y="224" class="c sub mono" style="fill:#444">934036</text>
<rect x="460" y="232" width="140" height="24" rx="3" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1"/>
<text x="530" y="248" class="c sub mono" style="fill:#444">idx_user_status_time</text>
<rect x="600" y="232" width="80" height="24" rx="3" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1"/>
<text x="640" y="248" class="c sub" style="fill:#444">20 行</text>
<rect x="680" y="232" width="50" height="24" rx="3" fill="#C6ECE9" stroke="#7DBCB7" stroke-width="1"/>
<text x="705" y="248" class="c sub mono" style="fill:#444">2.27</text>
<rect x="40" y="410" width="680" height="40" rx="6" fill="#C6ECE9" stroke="#7DBCB7"/>
<text x="380" y="428" class="c sub b" style="fill:#444">InnoDB:先在 Buffer Pool 中找数据页,不在内存中才从磁盘读入</text>
<text x="380" y="444" class="c sub">按 idx_user_status_time 找到 20 条索引记录,用索引条件下推过滤 create_time,再回表取整行</text>
<line x1="85" y1="380" x2="85" y2="408" class="arrow "/>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB