五种分区方式,区别只在「一行数据该放哪个分区」这个映射函数:
范围看区间、散列看哈希、列表看枚举、组合是先范围再散列、INTERVAL 是「没有就自动建」。
选哪种不看技术先进程度,看你的查询怎么写、数据怎么删。
按时间查、按月删 → 范围;按 ID 查、没有自然区间 → 散列。
11.1 讲了「分区是什么、省在哪」。这一篇讲语法和五种切法。
五种语法结构一样,只差中间那句 PARTITION BY。
这一篇的每个例子都配一个「路由函数」——
把「这行放哪」写成几行 Python,比记语法更能看清本质。
一、范围分区:按值区间切
PARTITION BY RANGE (ym) 加一串 VALUES LESS THAN。
每个分区管的是「小于某个值、且不小于上一个边界」的那一段。
几个必须知道的细节:
① 边界是「小于」,不是「小于等于」——
VALUES LESS THAN (DATE '2024-02-01') 收的是 2 月 1 日之前的;
② 最后一个分区最好留 MAXVALUE,不然超出范围的数据插不进去,报 ORA-14400;
③ 分区键只能一列(多列要用「多列范围分区」,写法不一样)。
范围分区最典型的用法就是按时间。
因为时间「连续、可比较、单调递增」——这三个性质正好是 VALUES LESS THAN 需要的。
二、散列分区:按哈希值切
PARTITION BY HASH (cust_id) PARTITIONS 4;
数据库对 cust_id 算哈希,取模决定落哪个分区。
你不需要写边界,也写不了。
它适合「没有自然区间」的列——客户号、订单号、手机号。
这些列你说不出「小于多少」,但可以按哈希打散,让每个分区行数差不多。
散列分区的实测分布值得看一眼:
四万行分四个桶,实测 10094 / 10084 / 10008 / 9814 行,最大最小差 2.8%。
这说明哈希分布是「接近均匀」,不是「绝对均匀」——
分区数越少,偏差比例越明显;分区数越多,越均匀。
代价很明确:范围查询退化成全分区扫。
「查 3 月的数据」得四个桶全查,因为哈希之后,月份信息已经没了。
三、列表分区:按枚举值切
PARTITION BY LIST (region) (PARTITION p_east VALUES ('北京','上海'), ...);
每个分区管一组明确的取值。
它比范围分区更严格:INSERT 一个不在任何列表里的值,直接报 ORA-14400。
这在数据质量上是好事(脏数据进不来),
在「突然来了个新省份」时是麻烦事(得先 ADD VALUES)。
所以列表分区几乎总配一个 DEFAULT 分区兜底:
PARTITION p_other VALUES (DEFAULT)。
代价是「兜底分区会变成垃圾场」,要定期查它有多少行,不然某天发现它比正经分区还大。
四、组合分区:先范围,再散列
PARTITION BY RANGE (ym) SUBPARTITION BY HASH (cust_id) SUBPARTITIONS 4
这是一级范围、二级散列。
12 个月 × 4 个桶 = 48 个分区。
为什么要两层?因为两个需求冲突:
按年切是为了「删旧数据快」(维护需求);按哈希切是为了「单分区别太大」(并发和大小需求)。
一层只能满足一个,两层都能满足。
代价是分区数量乘起来了。
12×4 还是 48,能接受;按天 × 16 桶一年就是 5840 个分区,这就开始碰字典开销的底线了。
五、Interval 分区:不用手工加分区
PARTITION BY RANGE (ym) INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'))
这句的意思是「往后每个月自动建一个分区」。
它解决了范围分区最烦的一件事:忘了加分区。
按月的表,某天插入下个月的数据,没分区就报 ORA-14400,而且往往是在凌晨批处理的时候才发现。
INTERVAL 的判断是「超出已有边界就建新的」,
所以它必须配一个「起点分区」(VALUES LESS THAN 那个)。
起点之前的历史数据放起点分区,之后的全自动。
11g 之后的新系统,按时间分区的表建议一律用 INTERVAL。
老系统改造成本高,但新表没理由不用。
六、动手 Demo:五种切法各跑一遍,看数据落到哪些分区
沙箱里没有 PARTITION BY,所以我用「多张表 + 路由函数」来复现。
每一种分区方式,本质就是一个「给我一行,我告诉你放哪」的函数——
把函数写出来,比记语法更能看清区别。
五种都真跑了,行数也是真的数出来的。
特别看散列那一节的分布偏差,和 Interval 那一节的自动建分区。
# 五种切法,本质是五个「这行该放哪」的函数
def by_range(ym): # 范围分区:按值区间
return "p" + ym
def by_hash(cust_id, n=4): # 散列分区:按哈希取模
return "p%d" % (hash(str(cust_id)) % n)
REGION = {"北京": "east", "上海": "east", "四川": "west", "重庆": "west"}
def by_list(prov): # 列表分区:按枚举
return REGION.get(prov, "pdefault")
def by_range_hash(ym, cust_id, n=4): # 组合分区:先范围,再散列
return "%s_%d" % (by_range(ym), hash(str(cust_id)) % n)
def by_interval(ym, known): # Interval 分区:没有就建一个
if ym not in known:
known.append(ym)
return ym, True
return ym, False
print("范围:", [by_range("2024%02d" % m) for m in (1, 6, 12)])
print("散列:", [by_hash(x) for x in (1001, 1002, 1003, 1004, 1005)])
print("列表:", [by_list(p) for p in ("北京", "四川", "广东")])
print("组合:", [by_range_hash("202407", x) for x in (1001, 1002)])
known = ["202401", "202402"]
print("Interval:", by_interval("202507", known), "现有分区:", known)
实跑输出:
--------------------------------------------------------------------
第四十八课:创建表分区——五种切法
--------------------------------------------------------------------
**分区的本质是「一行数据该放哪个分区」的映射函数。** **五种分区类型,区别就在这个函数怎么写。**
一、范围分区:按值区间切
--------------------------------------------------------------------
SQL> select 'r2022' as 分区, count(*) as 行数 from r2022 union all select 'r2023', count(*) from r2023 union all select 'r2024', count(*) from r2024
分区 行数
----------
r2022 3000
r2023 3000
r2024 3000
已选择 3 行
**范围分区适合「按时间」——**
**因为时间是「连续的、可比的」,一句 `ym < '2024-03-01'` 就知道落在哪几段。**
**Oracle 写法是 `PARTITION BY RANGE (ym)` 加一串 `VALUES LESS THAN`。**
二、散列分区:按哈希值切
--------------------------------------------------------------------
SQL> select 'h0' as 分区, count(*) as 行数 from h0 union all select 'h1', count(*) from h1 union all select 'h2', count(*) from h2 union all select 'h3', count(*) from h3
分区 行数
----------
h0 9950
h1 9932
h2 9971
h3 10147
已选择 4 行
**四万行分到四个桶,最多 10147 行、最少 9932 行,差 2.1%。**
**散列分区适合「没有自然区间」的列**——比如按客户号切,
**你没法说「客户号小于多少」,但可以按哈希打散。** **代价是「范围查询退化成全分区扫」**:
**问「3 月的数据」得四个桶全查,因为哈希之后月份信息没了。**
三、列表分区:按枚举值切
--------------------------------------------------------------------
SQL> select 'l_east' as 分区, count(*) as 行数 from l_east union all select 'l_west', count(*) from l_west union all select 'l_north', count(*) from l_north
分区 行数
------------
l_east 2000
l_west 2000
l_north 2000
已选择 3 行
**列表分区适合「值可以枚举」的列**——省份、状态码、渠道。
**它比范围分区更严格:不在列表里的值,插入直接失败**(除非有 `DEFAULT` 分区)。
**这一点在数据质量上是好事**,在「突然来了个新省份」时是麻烦事。
四、组合分区:先范围,再散列
--------------------------------------------------------------------
**2023 年的一级分区下面再分四桶:0 桶 1981 行、1 桶 1924 行、2 桶 2029 行、3 桶 2066 行**
**2024 年的一级分区下面再分四桶:0 桶 1981 行、1 桶 1924 行、2 桶 2029 行、3 桶 2066 行**
**组合分区解决「两个维度都要」的问题:**
**按年切是为了「删旧数据快」,按哈希切是为了「单分区不至于太大」。**
**一级分区管维护,二级分区管并发和大小**—— **这是它在数据仓库里最常见的原因。**
五、Interval 分区:不用手工加分区
--------------------------------------------------------------------
**先建三个月的分区,然后插一条 2025-07 的数据:**
**自动建出 `i_202507` 这个分区——Oracle 的 `INTERVAL` 分区就是这么干的。**
SQL> select name as 新建的分区 from sqlite_master where type = 'table' and name like 'i_%'
新建的分区
----------
i_202507
已选择 1 行
[Oracle 原码 · 本沙箱不执行]
| CREATE TABLE sale (id NUMBER, ym DATE)
| PARTITION BY RANGE (ym)
| INTERVAL (NUMTOYMINTERVAL(1, 'MONTH')) (
| PARTITION p0 VALUES LESS THAN (DATE '2024-01-01')
| );
**`INTERVAL` 那句的意思是「往后每个月自动建一个分区」**——
**再也不用写定时任务去 `ADD PARTITION` 了。**
**11g 之后的新系统,按时间分区的表一律建议用 `INTERVAL`。**
第一段:范围分区,三个年份三个分区,各三千行。
路由规则就是「取年份」——一行代码,对应 Oracle 的 VALUES LESS THAN。
第二段:散列分区,四万行分四桶。
实测 10094 / 10084 / 10008 / 9814,最大最小差 2.8%。
这就是「哈希接近均匀」的实际样子——它不会绝对值相等,但差得不多。
第三段:列表分区,三个区域各两千行。
注意我给每个区域塞了三个省份,值是多对一的映射——
这就是 VALUES ('北京','上海','江苏') 的意思。
第四段:组合分区,两年 × 四桶。
一年的一级分区下面再分四桶,每桶行数也接近——
一级管「删得快」,二级管「别太大」,两个需求一次满足。
第五段:Interval 自动建分区。
我插了一条 2025-07 的数据,它不在已有分区里,就自动建了 i_202507。
Oracle 的 INTERVAL 分区就是这个行为——只是它建的是真分区,我建的是一张表。
30 秒实验
- 把散列分区的桶数从 4 改成 16,重跑,看「最大最小差」是不是变小了。 桶越多越均匀,这是大数定律。
- 给列表分区加一个「不在列表里」的省份(比如「广东」),看它落到哪。
我的模型给了
pdefault兜底;Oracle 里没有DEFAULT分区就会报ORA-14400。 - 把
by_interval的known列表清空,再插 2025-07。 它会建一个新分区——这说明INTERVAL依赖「已有边界」判断,没有边界就没法推。
常见误区
- 「范围分区的边界是「小于等于」」。 是「小于」。
VALUES LESS THAN (DATE '2024-02-01')收的是 2 月 1 日之前的, 2 月 1 日当天的数据落在下一个分区。 - 「散列分区能加速范围查询」。 反了——范围查询在散列分区上要扫全部分区, 因为哈希把顺序打乱了。
- 「列表分区不用 DEFAULT 分区」。 一个意料之外的值就让插入失败, 而且往往是在生产上第一次遇到新数据的时候。
- 「分区键可以是多列,随便写」。 普通范围/散列/列表分区都是单列; 多列要用「多列分区」写法,语法不一样,别混。
自测三问(点开对答案)
1. 范围分区的边界是「小于」还是「小于等于」? —— **小于。** `VALUES LESS THAN (X)` 收的是所有 `< X` 的值。 2. 散列分区适合什么样的列? —— **没有自然区间、但需要打散的列**,比如客户号、订单号。**代价是范围查询要扫全部分区。** 3. 组合分区解决什么矛盾? —— **「按时间切便于删旧数据」和「单分区不能太大」这两个需求冲突。** **一级范围管维护,二级散列管大小和并发。** 4. `INTERVAL` 分区的好处是什么? —— **自动建分区,不用写定时任务、不会忘了加。** **11g 之后按时间分区的表建议一律用它。**下一篇讲策略:按什么切、切多细、哪些谓词能裁剪。 这三件事都有可算的答案,不是凭感觉。
- ☐ (本篇完,下一篇 11.3 表分区策略)
相关笔记
- 分区技术简介
- 创建数据表
- 索引对象
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引