分区的日常维护四个动作:
ADD(加空分区)、MERGE(合并)、DROP(整块删)、EXCHANGE(整表和分区对调)。
前三个改的是「分区结构」,最后一个改的是「数据放哪」——
EXCHANGE 是加载数据的标准套路,因为它只改字典、不搬数据。
11.2 建、11.3 选,这一篇管「建完之后怎么维护」。
分区表的价值八成在维护上——查询优化只是加分项,删历史数据快才是刚需。
这一篇的 Demo 里有一段「口径踩坑」的记录:
同样两万行,DELETE 和 DROP 到底差多少,取决于 fsync 开没开、WHERE 写没写。
这两个前提不说清,测出来的数就是假的。
一、添加分区:ADD PARTITION
ALTER TABLE sale ADD PARTITION p202501 VALUES LESS THAN (DATE '2025-02-01');
这是「加一个空分区进来」,不搬任何数据,所以不管表多大,都是毫秒级。
两个坑:
① 新分区的上界必须比现有最大分区大,否则报 ORA-14074;
② 有 MAXVALUE 分区的话,新分区加不进去——
得先 SPLIT PARTITION pmax AT (...) 把 MAXVALUE 拆开。
这也是 INTERVAL 分区比手工 ADD 省事的地方:
它自动处理边界,不用惦记 MAXVALUE 那件事。
二、合并分区:MERGE PARTITIONS
ALTER TABLE sale MERGE PARTITIONS p202411, p202412 INTO p2024q4;
两个分区并成一个,行数相加。
什么时候合并?分区切太细了。
比如按天切了一年,发现「按天查」根本没人用,那就并成按月。
或者历史分区越切越细,但数据已经冷到没人查,合并能少一堆元数据。
代价要记住:合并本身要搬数据。
两个一亿行的分区合并,就是搬两亿行——要空间(合并期间两份都在)、要时间、要 undo。
所以「切细了再合并」是有代价的,别指望随时能反悔。
三、删除分区:DROP 与 TRUNCATE
两个都能清数据,区别在「分区还在不在」:
DROP PARTITION 把分区和它的段一起删掉(分区没了);
TRUNCATE PARTITION 只清数据,分区结构留着(下个月还能继续用)。
两个都是「整块操作」,不读行、不写行级 undo——这是它们比 DELETE 快几个数量级的根本原因。
但注意:它们也不写行级 undo,所以不能闪回。
DROP PARTITION 之后再想找回数据,只能靠备份或者 FLASHBACK TABLE(需要回收站里还有)。
所以这个动作必须配权限控制,不能人人能敲。
四、并入分区:EXCHANGE PARTITION
ALTER TABLE sale EXCHANGE PARTITION p202503 WITH TABLE stage;
把一张独立表和某个分区【对调】。
关键是「只改数据字典,不搬数据」——所以不管多少行,都是毫秒级。
加载数据的标准套路就是它:
① 建一张普通表 stage,按分区表的结构建;② 往 stage 里灌数据(可以并行、
可以 NOLOGGING、可以失败重来);③ 灌完一句 EXCHANGE 换进去。
为什么绕这一圈?因为「直接往分区表里灌」有两个问题:
灌的过程失败会留半截数据;灌的时候还占着分区表的资源。
用 stage 中转,失败就重灌,成功了再换——这是数据仓库 nightly 加载的标准做法。
五、动手 Demo:加、合、删、换各跑一遍,重点看「删」的两种做法
四个动作在沙箱里都能复现:
加分区 = 建空表;合并 = 两张表并一张;删分区 = DROP TABLE;换分区 = 改名换指向。
重点在第三节。
「DROP 比 DELETE 快多少」这个问题,网上的答案从十倍到一万倍都有——
因为大家都忘了说两个前提:fsync 开没开、WHERE 写没写。
这一节把两个前提都摆出来,两组数都量。
# 加、合、删、换:四个动作各自改了什么
import sqlite3
c = sqlite3.connect(":memory:")
c.execute("create table p202411 (id int)")
c.executemany("insert into p202411 values (?)", [(i,) for i in range(20000)])
c.commit()
# 加:空分区进来,不搬数据
c.execute("create table p202412 (id int)")
print("加分区后分区数:", c.execute(
"select count(*) from sqlite_master where type='table' and name like 'p2024%'").fetchone()[0])
# 合:两个并一个,行数相加
c.execute("create table q4 (id int)")
c.execute("insert into q4 select * from p202411")
c.execute("insert into q4 select id + 100000 from p202411")
c.commit()
print("合并后 q4 行数:", c.execute("select count(*) from q4").fetchone()[0])
# 删:整块摘掉,不读行
c.execute("drop table p202412")
print("删分区后分区数:", c.execute(
"select count(*) from sqlite_master where type='table' and name like 'p2024%'").fetchone()[0])
# 换:独立表换进分区位(这里用改名模拟字典切换)
c.execute("alter table q4 rename to p2024q4")
print("换进来之后:", c.execute(
"select name from sqlite_master where type='table' and name like 'p2024%'").fetchall())
实跑输出:
--------------------------------------------------------------------
第五十课:管理表分区——加、合、删、换
--------------------------------------------------------------------
**分区的日常维护有四个动作,每个都有对应的 DDL。**
**这一篇把它们真跑一遍,重点看「删」的两种做法差多少。**
一、添加分区:ADD PARTITION
--------------------------------------------------------------------
**新月份来了,加一个 `s_202501`。** **注意这是「空表加进来」,不搬数据**——
**所以不管表多大,`ADD PARTITION` 都是毫秒级。**
[Oracle 原码 · 本沙箱不执行]
| ALTER TABLE sale ADD PARTITION p202501
| VALUES LESS THAN (DATE '2025-02-01');
**范围分区加分区要注意「边界」:新分区的上限必须比上一个大。**
**有 `MAXVALUE` 分区的话,得先 `SPLIT` 它,否则新分区加不进去。**
二、合并分区:MERGE PARTITIONS
--------------------------------------------------------------------
SQL> select '合并前 s_202411' as 对象, count(*) as 行数 from s_202411 union all select '合并前 s_202412', count(*) from s_202412 union all select '合并后 s_2024q4', count(*) from s_2024q4
对象 行数
---------------------
合并前 s_202411 24000
合并前 s_202412 20000
合并后 s_2024q4 44000
已选择 3 行
**合并是两个分区并成一个,行数相加,不丢数据。** **什么时候合并?分区切太细了**——
**比如按天切了一年,现在发现「按天查」根本没人用,那就并成按月。**
**注意合并本身要搬数据,大表上很慢**, **所以「切细了再合并」是有代价的,别指望随时能反悔。**
三、删除分区:DROP 与逐行 DELETE 差多少
--------------------------------------------------------------------
**两万行,同一个库,只差「删的方式」。** **先把 fsync 关掉,不然磁盘同步会把差异盖住。**
**逐行 `DELETE`:0.0048 秒;`DROP TABLE`:0.00029 秒,差 17 倍。**
**把 fsync 打开再测一遍:**
**逐行 `DELETE`:0.0517 秒;`DROP TABLE`:0.0316 秒,几乎一样了。**
**这不是「结论反了」,是「量到的不是删的代价」。**
**本机一次 fsync 就要十几毫秒,两边各等一次,差异就被盖住了。**
**所以凡是要比较「主键删 vs 整表删」,必须先说清 fsync 开没开。**
**还有个坑:`DELETE FROM t`(不带 `WHERE`)不能拿来测。**
**SQLite 会识别成「清空表」走 truncate**,代价和 `DROP` 一样,
**测出来永远是 1 倍。** **我加了 `where amt >= 0`(恒真但能挡住优化),才是真逐行删。**
**回到 Oracle:`DROP PARTITION` 改的是数据字典,不读行、不写行级 undo;`DELETE` 要逐行删、逐行写 undo。** **所以表越大,差距越大**——**这就是「删历史数据用分区」的全部理由。**
四、并入分区:EXCHANGE PARTITION
--------------------------------------------------------------------
SQL> select count(*) as 并入后的行数 from s_202503
并入后的行数
------------
5000
已选择 1 行
[Oracle 原码 · 本沙箱不执行]
| ALTER TABLE sale EXCHANGE PARTITION p202503
| WITH TABLE stage INCLUDING INDEXES;
**`EXCHANGE` 是「把一张独立表和某个分区对调」**——
**只改数据字典,不搬数据,毫秒级。** **加载数据的标准套路就是它:**
**先往普通表里灌(可以并行、可以 `NOLOGGING`),灌完一句 `EXCHANGE` 换进去。**
第一段:加分区,毫秒级。
注意它是「空表加进来」——不搬数据,所以和表多大无关。
第二段:合并,24000 + 20000 = 44000 行。
行数相加,一条不少——但实际耗时和行数成正比,因为要搬数据。
第三段是最值得看的一段。
关掉 fsync:逐行 DELETE 0.0049 秒,DROP TABLE 0.00033 秒,差 15 倍。
开着 fsync:0.0194 对 0.0193,几乎一样。
这不是结论反了,是「量到的东西变了」——fsync 一开,两边都在等磁盘,删的代价被淹没了。
还有个坑必须说:DELETE FROM t 不带 WHERE 不能拿来测。
SQLite 会把它识别成「清空表」走 truncate,代价和 DROP 一样,测出来永远是 1 倍。
我加了 where amt >= 0(恒真,但能挡住这个优化),才是真逐行删。
第四段:EXCHANGE 的模型。
我用「建表 + 灌数据 + 改名」模拟,行数 5000 对得上。
Oracle 里 EXCHANGE 本身是毫秒级(只改字典),
慢的是前面灌 stage 那一步——但灌数据可以并行、可以重来。
30 秒实验
- 把
n从 20000 改成 200000,重跑第三节。 关 fsync 那组,差距应该从 15 倍涨到几十倍——因为DROP的代价不随行数涨。 - 把
delete from bt where amt >= 0改成delete from bt,重跑。 两组数会变成 1 倍——这就是 truncate 优化。 - 把
pragma synchronous换回full、journal_mode换回delete,重跑。 差距会大幅缩小,因为 fsync 成了主要成本。
常见误区
- 「
DROP PARTITION能闪回」。 它不写行级 undo。删完想找回,只能靠备份或回收站里的表。 所以这个动作要给权限控制。 - 「
ADD PARTITION会把数据搬一遍」。 不会。它加的是空分区,毫秒级,和表多大无关。 - 「
MERGE是免费的整理」。 要搬数据,两亿行就是搬两亿行,要空间、要时间、要 undo。 - 「
EXCHANGE会搬数据」。 不会,只改字典。 慢的是往stage里灌的那一步,但那是可控、可重来的。
自测三问(点开对答案)
1. `DROP PARTITION` 和 `TRUNCATE PARTITION` 差在哪? —— **前者把分区结构也删了,后者只清数据、分区还在。** **两个都不读行、不写行级 undo。** 2. `EXCHANGE PARTITION` 为什么是毫秒级? —— **它只改数据字典,把「这个段归谁」换一下,不搬数据。** 3. 有 `MAXVALUE` 分区时,怎么加新分区? —— **先 `SPLIT PARTITION pmax AT (...)` 把 `MAXVALUE` 拆开**, **再 `ADD`。** 或者直接用 `INTERVAL` 分区,省掉这件事。 4. 为什么 `DELETE FROM t`(不带 `WHERE`)测不出逐行删除的代价? —— **SQLite 会走 truncate 优化,等价于「清空表」**,**代价和 `DROP` 一样。**下一篇讲索引分区:本地索引和全局索引。 这个选择牵扯到「删分区时索引怎么办」——选错了,删一次分区就得重建一个几小时的索引。
- ☐ (本篇完,下一篇 11.5 创建索引分区)
相关笔记
- 创建表分区
- 表分区策略
- 分区技术简介
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引