本文系统剖析了MySQL的核心技术架构,重点聚焦于分层逻辑结构、InnoDB存储引擎设计、事务机制与并发控制、主从复制原理及分区策略五大模块。文章旨在帮助开发者深入理解MySQL的运行机制与性能优化要点,内容涵盖B+树索引原理、MVCC实现、两阶段提交等关键技术细节,并解答了单表2000万数据量限制的经典问题。
MySQL是一种开源的关系型数据库管理系统(RDBMS),具备高性能、高可靠、跨平台、社区活跃等特性,是构建各种规模应用程序的首选之一。为了在项目中更好地使用MySQL,我们有必要探索MySQL的核心知识。
首先我们来了解下MySQL的整体架构:

MySQL 采用分层逻辑架构,包括:连接层 (Connection Layer)、服务层 (Server Layer / SQL Layer) 和存储引擎层 (Storage Engine Layer)。这种分层设计使得 MySQL 功能强大、灵活、可扩展。
2.1 连接层
负责处理客户端的网络连接请求和身份验证,高效、安全地建立和维护客户端与服务器之间的通信通道。主要组件与功能:
2.2 服务层
接收来自连接层的 SQL 语句,进行解析、优化,并最终确定如何高效地访问数据。它还负责内置函数的实现、跨存储引擎的功能(如视图、存储过程、触发器等)。主要组件及其功能:
COUNT(), SUM(), MAX(), NOW(), CONCAT())以及一些扩展函数。2.3 引擎层
负责数据的实际读写操作,支持可插拔存储引擎, 不同的存储引擎就是不同的“插件”,可以在启动时或运行时(对某些引擎)加载或更改。常见存储引擎简要对比。

2.4 SQL查询处理流程
在上述逻辑架构下,一条SQL 查询处理流程如下:建立连接、查询缓存(8.0后删除)、SQL 语句解析、查询优化、执行查询和返回结果。

InnoDB存储引擎
MySQL支持可插拔存储引擎,其中最重要也最常见的存储引擎为:InnoDB 。它凭借先进的设计理念,在事务支持、数据安全、并发控制、索引优化等方面都具备显著优势。下面我们来看下其核心知识:
3.1 高性能索引结构:B+树
InnoDB使用B+树作为索引结构,这使其在进行数据检索时效率极高。那么B+树相比于其他数据结构有什么独特之处呢? B+树结构示意如下:

B+树 vs. 二叉搜索树(BST)或平衡二叉搜索树(如 AVL 树、红黑树):B+树的高度更矮,查询效率更高
B+ 树 vs. 哈希表:B+支持范围查找、索引内存占用少,支持更大数量级
B+ 树 vs. B 树
B+ 树其实是 B 树的一种变体,二者都是多路平衡树,但 B+ 树在数据库索引中更具优势,B+ 树通过将数据集中到叶子节点,并建立叶子节点间的链表,极大地优化了范围查询和全表扫描的效率。更多区别如下:

3.2 聚簇索引
为什么常说单表存储数据上限为2000W 条数据?
关于“单表存储数据上限为2000万条数据”的说法,其实并不是一个绝对的技术限制。2000万这个数字,并不是 MySQL 或任何数据库系统强制规定的硬性上限,而是一个在很多实际业务场景中,当单表数据量达到这个级别时,如果不做优化,性能可能会明显下降的经验阈值,尤其是查询性能。
2000W 条数据是如何估算的?
背景知识:

InnoD的每一张表在磁盘上会对应一个.ibd文件,叫作表空间。在表空间中,数据是以数据页的形式存储的,每页的大小默认为16K(可以使用 innodb_page_size 参数在创建实例时指定页面大小,该参数支持4KB、8KB、16KB和32KB。选择合适的页大小需要考虑工作负载和硬件性能,大的页可以减少I/O操作但占用更多空间,小的页则反之。)

2000W 条数据计算

设:非叶子结点存放的指向其他数据页的指针数量为 X ;叶子节点存放的行数据数量为 Y ;B+ 树的层数(高)为 Z 。则:B+树能存放的总行数 = (X ^ (Z-1)) * Y。
代入计算:一个数据页 16KB 。假设主键 ID 为 bigint 类型 8 字节,指针大小在 InnoDB 源码中设置为6 字节,一条数据是 14 字节左右,一个数据页中能存放 X = 16*1024/14 ≈ 1170 条数据,指向 1170 个新的数据页。Y 是数据页中能容纳的最大行记录数量,所以与实际存储的行记录的大小有关,假设一条行记录占用的空间大小为1KB,则:Y = 16。
在 InnoDB 中 B+ 树高度一般为1-3层,它就能满足千万级的数据存储。查询数据时,每加载一页(page)代表一次IO,所以通过主键索引查询通常只需要1~3次 I/O 操作即可查找到数据。MySQL 都有缓存, B+ 树高度为 3 时,第 1 层和第 2 层的数据都在缓存中,查询只需要一次 I/O 操作,速度很快,但是当数据超出 2KW 时, B+ 树层高会增加,需要再多一次 I/O 操作,查询效率就急速下降了。所以MySQL数据库单表建议最大2KW数据。
3.3 二级索引
注意:覆盖索引优化二级索引查询效率,例如查询SELECT id, name FROM users WHERE name = 'Alice'; 若 name为二级索引,则无需回表。
3.4 索引优化
定义:当查询所需的所有列都包含在索引中时,数据库可以直接从索引中获取数据而无需回表查询原始数据行,这种索引被称为"覆盖索引"。覆盖索引可以显著提高检索性能。
示例:
-- 假设在users表的(name, age)上建立了联合索引
CREATE INDEX idx_name_age ON users(name, age);
-- 这个查询可以使用索引覆盖,因为只查询了索引包含的列
SELECT name, age FROM users WHERE name = '张三';优势:避免了回表操作,减少I/O操作(不需要读取数据页), 减少CPU消耗(不需要处理数据行)。
定义:MySQL 5.6引入的特性,将WHERE条件中索引相关部分"下推"到存储引擎层进行过滤,而不是在服务器层过滤。
特点:
示例:
sql复制-- 假设在users表的(name, age)上建立了联合索引
CREATE INDEX idx_name_age ON users(name, age);
-- MySQL 5.6之前:存储引擎只根据name='张三'查找索引,然后返回所有匹配的记录到服务器层,由服务器层过滤age>20的记录
-- MySQL 5.6及之后:存储引擎会在索引层面就同时检查name='张三'和age>20,只返回同时满足两个条件的记录
SELECT * FROM users WHERE name = '张三' AND age > 20;优势:减少存储引擎返回给服务器层的数据量, 减少回表操作(对于二级索引),提高查询效率,特别是对于范围查询后的过滤条件。
事务机制与并发控制
4.1 事务概述
事务是InnoDB存储引擎支持的最大的一个特性。事务是指数据库的一组操作,要么全部执行成功,要么全部失败回滚。事务具有四大核心特性ACID:
4.2 Undo Log
每一个事务对数据的修改都会被记录到 undo log ,当执行事务过程中出现错误或者需要执行回滚操作的话,MySQL 可以利用 undo log 将数据恢复到事务开始之前的状态。
undo log 属于逻辑日志,记录的是 SQL 语句,比如说事务执行一条 DELETE 语句,那 undo log 就会记录一条相对应的 INSERT 语句。同时,undo log 的信息也会被记录到 redo log 中,因为 undo log 也要实现持久性保护。并且,undo-log 本身是会被删除清理的,例如 INSERT 操作,在事务提交之后就可以清除掉了;UPDATE/DELETE 操作在事务提交不会立即删除,会加入 history list,由后台线程 purge 进行清理。
undo log 是采用 segment(段)的方式来记录的,每个 undo 操作在记录的时候占用一个 undo log segment(undo 日志段),undo log segment 包含在 rollback segment(回滚段)中。事务开始时,需要为其分配一个 rollback segment。每个 rollback segment 有 1024 个 undo log segment,这有助于管理多个并发事务的回滚需求。
通常情况下, rollback segment header(通常在回滚段的第一个页)负责管理 rollback segment。rollback segment header 是 rollback segment 的一部分,通常在回滚段的第一个页。history list 是 rollback segment header 的一部分,它的主要作用是记录所有已经提交但还没有被清理(purge)的事务的 undo log。这个列表使得 purge 线程能够找到并清理那些不再需要的 undo log 记录。
另外,MVCC 的实现依赖于:隐藏字段、Read View、undo log。在内部实现中,InnoDB 通过数据行的 DB_TRX_ID 和 Read View 来判断数据的可见性,如不可见,则通过数据行的 DB_ROLL_PTR 找到 undo log 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 Read View 之前已经提交的修改和该事务本身做的修改。
场景设定:
逻辑日志
4.3 Redo Log
redo log(重做日志)是 InnoDB 存储引擎独有的,它让 MySQL 拥有了崩溃恢复能力。比如 MySQL 实例挂了或宕机了,重启时,InnoDB 存储引擎会使用 redo log 恢复未落盘的数据。

MySQL 中数据是以页为单位,你查询一条记录,会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到 Buffer Pool 中。后续的查询都是先从 Buffer Pool 中找,没有命中再去硬盘加载,减少硬盘 IO 开销,提升性能。 更新表数据的时候,也是如此,发现 Buffer Pool 里存在要更新的数据,就直接在 Buffer Pool 里更新。然后会把“在某个数据页上做了什么修改”记录到重做日志缓存(redo log buffer)里,接着刷盘到 redo log 文件里。

刷盘时机
InnoDB 将 redo log 刷到磁盘上有几种情况:
innodb_flush_log_at_trx_commit参数控制,后文会提到)。总之,InnoDB 在多种情况下会刷新重做日志,以保证数据的持久性和一致性。
我们要注意设置正确的刷盘策略innodb_flush_log_at_trx_commit 。根据 MySQL 配置的刷盘策略的不同,MySQL 宕机之后可能会存在轻微的数据丢失问题。 innodb_flush_log_at_trx_commit 的值有 3 种,也就是共有 3 种刷盘策略:
刷盘策略innodb_flush_log_at_trx_commit 的默认值为 1,设置为 1 的时候才不会丢失任何数据。为了保证事务的持久性,我们必须将其设置为 1。 另外,InnoDB 存储引擎有一个后台线程,每隔1 秒,就会把 redo log buffer 中的内容写到文件系统缓存(page cache),然后调用 fsync 刷盘。

为什么呢? 因为在事务执行过程 redo log 记录是会写入redo log buffer 中,这些 redo log 记录会被后台线程刷盘。

除了后台线程每秒1次的轮询操作,还有一种情况,当 redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动刷盘。 下面是不同刷盘策略的流程图。

为0时,如果 MySQL 挂了或宕机可能会有1秒数据的丢失。

为1时, 只要事务提交成功,redo log 记录就一定在硬盘里,不会有任何数据丢失。 如果事务执行期间 MySQL 挂了或宕机,这部分日志丢了,但是事务并没有提交,所以日志丢了也不会有损失。

为2时, 只要事务提交成功,redo log buffer中的内容只写入文件系统缓存(page cache)。 如果仅仅只是 MySQL 挂了不会有任何数据丢失,但是宕机可能会有1秒数据的丢失。
硬盘上存储的 redo log 日志文件不只一个,而是以一个日志文件组的形式出现的,每个的redo日志文件大小都是一样的。 比如可以配置为一组4个文件,每个文件的大小是 1GB,整个 redo log 日志文件组可以记录4G的内容。 它采用的是环形数组形式,从头开始写,写到末尾又回到头循环写,如下图所示。

在这个日志文件组中还有两个重要的属性,分别是 write pos、checkpoint
每次刷盘 redo log 记录到日志文件组中,write pos 位置就会后移更新。 每次 MySQL 加载日志文件组恢复数据时,会清空加载过的 redo log 记录,并把 checkpoint 后移更新。 write pos 和 checkpoint 之间的还空着的部分可以用来写入新的 redo log 记录。
如果 write pos 追上 checkpoint ,表示日志文件组满了,这时候不能再写入新的 redo log 记录,MySQL 得停下来,清空一些记录,把 checkpoint 推进一下。

Redo Log使用WAL的策略顺序写,所以刷盘速度很快。
4.4 MVCC
MVCC Multi-version Concurrency Control,即多版本并发控制,用于提高数据库的并发访问性能。核心思想:不加锁,为每次数据修改创建一个“版本”(Version)。
为什么需要 MVCC?解决什么问题?
在没有 MVCC 的数据库中(或使用简单的锁机制),为了保证数据一致性,通常会采用强锁:
这会导致严重的性能瓶颈,尤其是在读多写少的应用场景中。 MVCC 主要解决了以下问题:
MVCC 实现核心组件
1、事务 ID(Transaction ID)
2、数据行的隐藏字段
3、版本链

4、Read View

5、Undo Log(回滚日志)
假设我们有一行数据 name = ‘Alice’,事务 ID 为 100 的事务将其修改为 ’Bob‘。一个 SELECT 查询(事务 ID=200)在事务 100 提交前后执行,它应该看到什么?
1、UPDATE 操作(事务 Trx100):
’Alice‘,而是先将 ’Alice‘ 这行数据拷贝到 Undo Log。’Bob‘,并更新 DB_TRX_ID = 100,DB_ROLL_PTR 指向 Undo Log 中的 ’Alice‘ 版本。当前页 (Bob, Trx100) -> Undo Log (Alice, 某个旧TrxID)。2、SELECT 操作(事务 Trx200):事务 100 还未提交
m_ids 中包含 [100]。DB_TRX_ID=100。DB_TRX_ID < min_trx_id,说明该版本在 Read View 创建前已提交,可见。DB_TRX_ID >= max_trx_id,说明该版本在 Read View 创建后才开启,不可见。DB_TRX_ID 在 m_ids 中,说明创建该版本的事务在生成 Read View 时还活跃(未提交),不可见。DB_TRX_ID 是创建者自己的ID,可见。Trx100 在 m_ids 中,所以最新版本 ’Bob‘ 对 Trx200 不可见。DB_ROLL_PTR 找到旧版本 ’Alice‘,判断其 DB_TRX_ID 小于 min_trx_id(已提交),因此 ’Alice‘ 对 Trx200 可见。所以事务 200 读到的结果是 ’Alice‘。3、SELECT 操作(事务 Trx200):事务 100 提交后:
m_ids 中。DB_TRX_ID=100。Trx100 不在 m_ids 中,且 Trx100 < Trx300,说明该版本已提交,可见。’Bob‘。MVCC 是实现高级别事务隔离(如 读已提交 - RC 和 可重复读 - RR)的基础。
优点:
缺点:
4.5 幻读
什么是幻读?
如何避免幻读?
在 MySQL 的 可重复读(Repeatable Read) 隔离级别下,通过锁定范围(间隙锁 + 记录锁)可以避免幻读。
为什么仅锁定现有行(记录锁)无法避免幻读?
场景复现:仅锁定现有行导致幻读
假设有一张用户表 users,字段如下:
CREATE TABLE users (
id INT PRIMARY KEY,
age INT,
KEY idx_age (age) -- 二级索引
);表中现有数据:

事务 A(不加间隙锁)
事务 A(假设隔离级别为读已提交)
BEGIN;
SELECT * FROM users WHERE age = 25 FOR UPDATE; -- 仅加记录锁
-- 第一次查询结果:id=2 和 id=3(age=25 的两条记录)事务 B 插入新数据
-- 事务 B
BEGIN;
INSERT INTO users (id, age) VALUES (5, 25); -- 插入新的 age=25 的记录
COMMIT;事务 A 再次查询
sql复制-- 事务 A 再次执行查询
SELECT * FROM users WHERE age = 25 FOR UPDATE;
-- 第二次查询结果:id=2、id=3、id=5(多了一条记录!)
COMMIT;结果分析
为什么记录锁无法阻止幻读?
对比:如何通过锁定范围避免幻读?如果事务 A 在可重复读(Repeatable Read)隔离级别下执行,InnoDB 会默认使用 Next-Key Lock(记录锁 + 间隙锁):
1、锁定范围:
2、事务 B 的插入操作会被阻塞:
3、事务 A 两次查询结果一致:
间隙锁为什么可以阻止age=25的数据插入,间隙锁是开区间,不包含25,为什么可以阻止数据插入?
在 MySQL 的 InnoDB 引擎中,间隙锁(Gap Lock) 虽然本身是“开区间”,但它通过 Next-Key Lock 机制 和 索引结构的特性(顺序存储) 阻止特定值的插入。以下通过一个具体例子,结合索引结构和锁机制,解释为什么间隙锁可以阻止 age=25 的数据插入。 索引结构与插入逻辑
假设有一个二级索引 idx_age,现有数据如下:
age | 行位置(假设物理存储顺序)
----|-------------------
20 | 行1
25 | 行2
25 | 行3
30 | 行4插入 age=25 的逻辑
当插入一个新的 age=25 时,InnoDB 会根据索引的 有序性,将新数据插入到所有 age=25 的现有记录之后、下一个不同值(30)之前。例如:
新插入的 age=25 会位于 行3 和 行4 之间的位置(物理存储层面)。
锁定范围的真正作用假设事务 A 执行以下操作:
BEGIN;
SELECT * FROM users WHERE age = 25 FOR UPDATE; -- 可重复读隔离级别下触发 Next-Key LockInnoDB 会为 age=25 加 Next-Key Lock,具体锁定范围包括:
记录锁(Row Lock):锁定所有 age=25 的现有行(行2 和 行3)。
间隙锁(Gap Lock):- 左间隙:锁定 (20, 25),阻止插入 20 < age <25 的数据。
右间隙:锁定 (25, 30),阻止插入 25 < age <30 的数据。
为什么插入 age=25 会被阻塞?
新插入的 age=25 虽然值等于 25,但根据索引的有序性,它会被插入到 行3(最后一个 age=25 的行)和 行4(age=30)之间的位置,即 (25, 30) 的间隙。
事务 A 的间隙锁已经锁定了 (25, 30) 的区间,因此插入操作需要获取该区间的插入意向锁(Insert Intention Lock),与间隙锁冲突,导致阻塞。
图示说明
索引值:20 ←[间隙锁(20,25)]→ 25(行2、行3) ←[间隙锁(25,30)]→ 30关键点总结
间隙锁的“开区间”不包含边界值,但通过锁定相邻区间,间接影响新数据的插入位置。
索引的有序性 决定新数据的插入位置,即使值相同(如 age=25),也会被插入到最后一个相同值之后的间隙。
Next-Key Lock 的组合(记录锁 + 间隙锁)确保:- 现有行无法被修改或删除。
对比:如果只有记录锁如果事务 A 仅加记录锁(如 读已提交 隔离级别):
-- 事务 A(读已提交隔离级别)
BEGIN;
SELECT * FROM users WHERE age = 25 FOR UPDATE; -- 仅加记录锁索引有序性
索引有序性其它作用?
5.1 流程

5.2 BinLog
redo log 它是物理日志,记录内容是“在某个数据页上做了什么修改”,属于 InnoDB 存储引擎。 而 binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于MySQL Server 层。 不管用什么存储引擎,只要发生了表数据更新,都会产生 binlog 日志。 那 binlog 到底是用来干嘛的? 可以说 MySQL 数据库的数据备份、主备、主主、主从都离不开 binlog,需要依靠 binlog 来同步数据,保证数据一致性。

binlog 会记录所有涉及更新数据的逻辑操作,并且是顺序写。
Bin Log 日志格式
binlog 日志有三种格式,可以通过binlog_format参数指定。
指定statement,记录的内容是SQL语句原文,比如执行一条update T set update_time=now() where id=1,记录的内容如下。

同步数据时,会执行记录的SQL语句,但是有个问题,update_time=now()这里会获取当前系统时间,直接执行会导致与原库的数据不一致。 为了解决这种问题,我们需要指定为row,记录的内容不再是简单的SQL语句了,还包含操作的具体数据,记录内容如下。

row格式记录的内容看不到详细信息,要通过mysqlbinlog工具解析出来。 update_time=now()变成了具体的时间update_time=1627112756247,条件后面的@1、@2、@3 都是该行数据第 1 个~3 个字段的原始值(假设这张表只有 3 个字段)。 这样就能保证同步数据的一致性,通常情况下都是指定为row,这样可以为数据库的恢复与同步带来更好的可靠性。 但是这种格式,需要更大的容量来记录,比较占用空间,恢复与同步时会更消耗 IO 资源,影响执行速度。 所以就有了一种折中的方案,指定为mixed,记录的内容是前两者的混合。 MySQL 会判断这条SQL语句是否可能引起数据不一致,如果是,就用row格式,否则就用statement格式。
binlog 的写入时机也非常简单,事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到 binlog 文件中。 因为一个事务的 binlog 不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache。 我们可以通过binlog_cache_size参数控制单个线程 binlog cache 大小,如果存储内容超过了这个参数,就要暂存到磁盘(Swap)。 binlog 日志刷盘流程如下

write和fsync的时机,可以由参数sync_binlog控制,默认是1。 为0的时候,表示每次提交事务都只write,由系统自行判断什么时候执行fsync。

虽然性能得到提升,但是机器宕机,page cache里面的 binlog 会丢失。 为了安全起见,可以设置为1,表示每次提交事务都会执行fsync,就如同 redo log 日志刷盘流程 一样。 最后还有一种折中方式,可以设置为N(N>1),表示每次提交事务都write,但累积N个事务后才fsync。

在出现 IO 瓶颈的场景里,将sync_binlog设置成一个比较大的值,可以提升性能。 同样的,如果机器宕机,会丢失最近N个事务的 binlog 日志。
两阶段提交
redo log(重做日志)让 InnoDB 存储引擎拥有了崩溃恢复能力。 binlog(归档日志)保证了 MySQL 集群架构的数据一致性。 虽然它们都属于持久化的保证,但是侧重点不同。 在执行更新语句过程,会记录 redo log 与 binlog 两块日志,以基本的事务为单位,redo log 在事务执行过程中可以不断写入,而 binlog 只有在提交事务时才写入,所以 redo log 与 binlog 的写入时机不一样。

回到正题,redo log 与 binlog 两份日志之间的逻辑不一致,会出现什么问题? 我们以update语句为例,假设id=2的记录,字段c值是0,把字段c值更新成1,SQL语句为update T set c=1 where id=2。 假设执行过程中写完 redo log 日志后,binlog 日志写期间发生了异常,会出现什么情况呢?

由于 binlog 没写完就异常,这时候 binlog 里面没有对应的修改记录。因此,之后用 binlog 日志恢复数据时,就会少这一次更新,恢复出来的这一行c值是0,而原库因为 redo log 日志恢复,这一行c值是1,最终数据不一致。

为了解决两份日志之间的逻辑一致问题,InnoDB 存储引擎使用两阶段提交方案。 原理很简单,将 redo log 的写入拆成了两个步骤prepare和commit,这就是两阶段提交。

使用两阶段提交后,写入 binlog 时发生异常也不会有影响,因为 MySQL 根据 redo log 日志恢复数据时,发现 redo log 还处于prepare阶段,并且没有对应 binlog 日志,就会回滚该事务。

再看一个场景,redo log 设置commit阶段发生异常,那会不会回滚事务呢?

并不会回滚事务,它会执行上图框住的逻辑,虽然 redo log 是处于prepare阶段,但是能通过事务id找到对应的 binlog 日志,所以 MySQL 认为是完整的,就会提交事务恢复数据。
5.3 同步机制分类
1、异步复制(Asynchronous Replication)
2、半同步复制(Semisynchronous Replication)
sql复制INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
SET GLOBAL rpl_semi_sync_master_enabled = 1;3、组复制(Group Replication)
5.4 拓扑结构
一主一从

作用:读写分离、冷备容灾。
一主多从

作用:读密集型业务负载均衡(如电商大促)
级联复制

双主复制(Master-Master)

多源复制

5.5 如何选择复制模式?
场景需求 | 推荐模式 |
|---|---|
读扩展 | 一主多从(异步复制) |
数据高一致 | 半同步复制 或 组复制 |
跨地域容灾 | 级联复制 + 半同步 |
多数据中心双向同步 | 双主复制(业务层防冲突) |
聚合多个数据源 | 多源复制 |
7x24 高可用 | 组复制(InnoDB Cluster) |
6.1 类型详解
RANGE 分区
原理:根据列的取值范围将数据分布到不同分区。语法示例:
CREATE TABLE sales (
id INT,
sale_date DATE,
amount DECIMAL(10,2)
) PARTITION BY RANGE (YEAR(sale_date)) (
PARTITION p0 VALUES LESS THAN (2020),
PARTITION p1 VALUES LESS THAN (2021),
PARTITION p2 VALUES LESS THAN (2022),
PARTITION p3 VALUES LESS THAN (2023),
PARTITION p_max VALUES LESS THAN MAXVALUE
);适用场景:
优点:
LIST 分区
原理:根据离散的值列表将数据分布到分区。
CREATE TABLE users (
id INT,
region_id INT,
username VARCHAR(50)
) PARTITION BY LIST (region_id) (
PARTITION p_east VALUES IN (1, 2, 3),
PARTITION p_west VALUES IN (4, 5, 6),
PARTITION p_south VALUES IN (7, 8, 9),
PARTITION p_other VALUES IN (DEFAULT)
);适用场景:
HASH 分区
原理:根据哈希函数将数据均匀分布到指定数量的分区。
-- 基于列值的HASH分区
CREATE TABLE orders (
id INT,
order_date DATE,
customer_id INT
) PARTITION BY HASH(customer_id)
PARTITIONS 4;
-- 基于表达式的HASH分区(线性哈希)
CREATE TABLE logs (
id INT,
log_time DATETIME
) PARTITION BY LINEAR HASH(YEAR(log_time))
PARTITIONS 6;适用场景:
优点:
KEY 分区
原理:类似 HASH 分区,但使用 MySQL 内置的哈希函数,支持多列分区键。
CREATE TABLE events (
id INT AUTO_INCREMENT,
event_type VARCHAR(20),
created_at TIMESTAMP,
PRIMARY KEY (id, event_type)
) PARTITION BY KEY(event_type)
PARTITIONS 5;适用场景:
复合分区(子分区)
CREATE TABLE sales_detail (
id INT,
sale_date DATE,
region VARCHAR(10),
amount DECIMAL(10,2)
) PARTITION BY RANGE (YEAR(sale_date))
SUBPARTITION BY HASH(region)
SUBPARTITIONS 4 (
PARTITION p2020 VALUES LESS THAN (2021),
PARTITION p2021 VALUES LESS THAN (2022),
PARTITION p2022 VALUES LESS THAN (2023)
);6.2 分区策略选择指南
业务场景 | 推荐分区策略 | 理由 |
|---|---|---|
时间序列数据(日志、监控) | RANGE + 按时间分区 | 便于按时间范围管理和清理数据 |
用户数据分片 | HASH/KEY + 用户ID | 均匀分布,避免热点 |
多租户SaaS系统 | LIST + 租户ID | 按租户隔离数据 |
地理分布数据 | LIST + 地域编码 | 按地域管理查询 |
6.3 分区限制与注意事项
限制条件:
使用建议:

SQL机读顺序的鱼骨图,首先从from开始读取,然后是 on,从图上可以看出,这个是一前一后,后面的join 和 where 是同时解析,group by 和 having 是一前一后,后面才是 select 和 order by 解析,最后是 limit 限制显示多少条记录。
-End-
原创作者|孙少卡