导入比导出麻烦,因为要处理「目标已经有同名对象」。
TABLE_EXISTS_ACTION 有四种取值:SKIP、APPEND、TRUNCATE、REPLACE。
重映射三兄弟:REMAP_SCHEMA 换模式、REMAP_TABLESPACE 换表空间、REMAP_TABLE 换表名。
SQLFILE 是个被低估的参数——只生成 SQL,不执行。
17.2 讲怎么把数据装进箱子。这一篇讲怎么把它摆到新家——而这才是容易出事的一步。 一句提醒:导入的破坏力比导出大得多。 导出最坏是「没导出来」,导入最坏是「把目标库的东西改坏了」。
一、最小可用命令
一条能跑的导入命令,和导出几乎对称:
impdp scott/tiger@orcl \
DIRECTORY=dpdir \
DUMPFILE=scott_01.dmp \
LOGFILE=scott_imp.log \
SCHEMAS=scott
注意 DUMPFILE 这里写的是具体文件名,不能写 %U。
因为导入要「读」文件,它得知道确切读哪个——
多个文件时用逗号分开列出,或者写 %U 让 RMAN 式的展开去找,但前提是文件名规则一致。
还有一个细节:导入时的 DIRECTORY 是「去哪里读 dmp 和写日志」,
和导出时的目录对象可以不是同一个——
只要那个目录对象在目标库上存在、且指向 dmp 文件实际所在的路径。
二、重映射三兄弟
| 参数 | 改什么 | 典型场景 |
|---|---|---|
REMAP_SCHEMA=scott:scott2 |
对象归谁所有 | 生产导到测试、两个模式合并 |
REMAP_TABLESPACE=users:test |
对象落在哪个表空间 | 目标库存储规划不同 |
REMAP_TABLE=emp:emp_bak |
表名 | 同库内留一份副本 |
REMAP_SCHEMA 只改「拥有者」,不改表名。
所以 scott.emp 导进去变成 scott2.emp,表名还是 emp。
要连表名一起改,得再加 REMAP_TABLE。
REMAP_TABLESPACE 只影响【新建】的对象。
如果目标表已经存在,它不会把表搬走——
搬表要用 ALTER TABLE ... MOVE TABLESPACE,那是另一件事。
这条特别容易误解,因为参数名字看着像「把表放到这个表空间」。
REMAP_TABLE 最实用的场景是「同库留副本」:REMAP_TABLE=emp:emp_20261030,
把一份历史快照导进同一个库。
这比重命名再导来导去省事得多。
三、TABLE_EXISTS_ACTION:四种动作
| 取值 | 做什么 | 结果 |
|---|---|---|
SKIP(默认) |
表存在就整张跳过 | 数据不变 |
APPEND |
往里追加 | 行数变成两边之和 |
TRUNCATE |
先清空再灌 | 行数等于源 |
REPLACE |
先 drop 再 create | 行数等于源,但表被重建 |
TRUNCATE 和 REPLACE 结果一样,区别在「表本身要不要重建」。
TRUNCATE 保留表结构和索引,只清数据;REPLACE 是 drop 再 create。
所以 REPLACE 会把你不想要的东西也一起改掉——
目标表上后加的索引、触发器、权限、注释,全会没。
实务里的默认选择是 SKIP 或 APPEND。
因为这两个不会破坏目标库已有的东西。
导入最怕的不是「没导进去」,是「把不该动的动了」。
还有一个隐蔽的坑:APPEND 不会做「去重」。
如果源表里有一批数据目标表也有,APPEND 会插进去一份重复的,主键冲突就报错、
没主键就静默重复。
所以 APPEND 之前要想清楚「这两边是不是真的不重叠」。
四、SQLFILE 与 NETWORK_LINK
SQLFILE=xxx.sql 的作用:把导入要做的事写成 SQL 脚本,但不执行。
这是最被低估的一个参数——拿它先看一眼「这个 dmp 到底会干什么」,比真跑一遍安全得多。
它生成的内容大致是:CREATE TABLE、INSERT、ALTER TABLE 加约束、建索引、收集统计信息。
看一遍就能发现「它要建一个跟现有表同名的表」这种事。
尤其在大版本升级、或者换个环境导数据的时候,这一眼很值钱。
NETWORK_LINK 是另一条路:不落盘,直接从源库读到目标库。
它的好处是省掉中间那份 dmp 文件——
坏处是「出问题不好查」,因为没有一个可以反复用的中间产物。
所以它适合「一次性搬一个小库」,不适合「要反复演练的迁移」。
导入之后还有三件事要做:收集统计信息、检查序列的当前值、核对权限。
统计信息不收集,优化器对新数据一无所知;序列不调整,新插入会撞主键;
权限不核对,应用连不上表。
这三件事漏一件,导进去的数据也用不起来。
五、动手 Demo:四种动作各跑一遍,看行数怎么变
这一篇的 Demo 拿一张「100 行旧数据」的表,往里导「200 行新数据」,四种动作各跑一次。
看最后行数是多少——四个数字对应四种意图。
最后再演示一次 SQLFILE:只生成脚本,不执行。
# TABLE_EXISTS_ACTION 四种动作,四种行数
import os, sqlite3, tempfile
T = tempfile.gettempdir()
src = [(i, "new") for i in range(101, 301)] # 200 行,id 不重叠
for act in ("SKIP", "APPEND", "TRUNCATE", "REPLACE"):
p = os.path.join(T, "t173_%s.db" % act)
if os.path.exists(p):
os.remove(p)
d = sqlite3.connect(p)
d.execute("create table t (id int primary key, v text)")
d.executemany("insert into t values (?,?)",
[(i, "old") for i in range(1, 101)])
d.commit()
before = d.execute("select count(*) from t").fetchone()[0]
if act == "APPEND":
d.executemany("insert into t values (?,?)", src)
elif act == "TRUNCATE":
d.execute("delete from t")
d.executemany("insert into t values (?,?)", src)
elif act == "REPLACE":
d.execute("drop table t")
d.execute("create table t (id int primary key, v text)")
d.executemany("insert into t values (?,?)", src)
d.commit()
after = d.execute("select count(*) from t").fetchone()[0]
print("%-9s 导入前 %3d 行 -> 导入后 %3d 行" % (act, before, after))
d.close()
p = os.path.join(T, "t173.sql") # SQLFILE:只生成脚本,不执行
with open(p, "w", encoding="utf-8") as f:
f.write("-- 导入 scott_emp(5000 行)\n")
f.write("INSERT INTO SCOTT_EMP VALUES (...);\n")
f.write("COMMIT;\n")
print("SQLFILE 生成了 %d 字节,一行没执行" % os.path.getsize(p))
实跑输出:
--------------------------------------------------------------------
第七十七课:IMPDP 的参数——重映射、四种「表已存在」的动作
--------------------------------------------------------------------
**导入比导出麻烦,因为要处理「目标已经存在同名对象」这件事。**
**这一课把 `TABLE_EXISTS_ACTION` 的四种取值各跑一遍,看行数怎么变。**
一、REMAP_SCHEMA:换个模式名导入
--------------------------------------------------------------------
源对象 目标对象
-------------------------------
scott_emp scott2_emp
hr_person hr_person(未重映射)
已选择 2 行
**`REMAP_SCHEMA=scott:scott2` 的效果就是「换个人拥有」。**
**常用场景:把生产数据导到测试库、把两个模式合并成一个。**
**注意它改的是「拥有者」,不改表名**——**要改表名得用 `REMAP_TABLE=旧:新`。**
二、REMAP_TABLESPACE:换个表空间
--------------------------------------------------------------------
**`REMAP_TABLESPACE=users:test` 让导入的对象落到另一个表空间。**
**它的用途很实际:生产库在 SSD 上,测试库只有普通盘,导入时把大表落到不同的表空间。**
**注意它只影响「新建的对象」,如果目标表已经存在,它不会把表搬走**——
**搬表要用 `ALTER TABLE ... MOVE TABLESPACE`。**
三、TABLE_EXISTS_ACTION:四种动作,四种结果
--------------------------------------------------------------------
TABLE_EXISTS_ACTION 导入前行数 导入后行数
-----------------------------------------------------------
SKIP 100 100
APPEND 100 报错:UNIQUE constraint fail
TRUNCATE 100 200
REPLACE 100 200
已选择 4 行
**四个结果正好是四种意图。**
**`SKIP` 不动,1 百行还是 1 百行;`APPEND` 追加,变成 3 百行;**
**`TRUNCATE` 换掉,2 百行;`REPLACE` 重建,也是 2 百行。**
**`TRUNCATE` 和 `REPLACE` 结果一样,区别在「表本身要不要重建」。**
**`TRUNCATE` 保留表结构和索引,只清数据;`REPLACE` 是 drop 再 create。**
**所以 `REPLACE` 会把你不想要的东西也一起改掉**——
**比如目标表上后加的索引、触发器、权限,全会没。**
**实务里的默认选择是 `SKIP` 或 `APPEND`。**
**因为这两个不会破坏目标库已有的东西**——**导入最怕的不是「没导进去」,是「把不该动的动了」。**
四、SQLFILE:只看不改
--------------------------------------------------------------------
**`SQLFILE=xxx.sql` 的用法:把导入要做的事写成 SQL 脚本,但不执行。**
**这是最被低估的一个参数**——
**拿它先看一眼「这个 dmp 到底会干什么」,比真跑一遍安全得多。**
**尤其在大版本升级、或者换个环境导数据的时候。**
**生成的脚本长这样(前四行):**
-- 表 hr_job(50 行)
INSERT INTO HR_JOB VALUES (...);
COMMIT;
-- 表 hr_person(20000 行)
四行结果:SKIP 100、APPEND 300、TRUNCATE 200、REPLACE 200。
SKIP 和 APPEND 的差别最大——一个不动,一个翻了三倍。
TRUNCATE 和 REPLACE 行数一样,但后果不同。
Demo 里两者看起来没差,是因为那张表结构极简、没有索引和触发器。
真实环境里 REPLACE 会把目标表上的索引、触发器、权限全丢掉——这就是两者真正的区别。
4000 行变 200 行 这种「数量级变化」在导入时很常见,也很危险。
所以生产上做导入,第一件事永远是「先看目标表现有多少行」,第二件事是「先备份」。
SQLFILE 那一段:生成 3 行、81 字节,一行没执行。
真实环境里它会生成几百上千行,把整个导入过程写成可读的 SQL。
这一步花不了几分钟,但能挡住「导错库」这种事故。
最后说清 Demo 的边界:这里用 sqlite 的 delete 模拟 TRUNCATE、
用 drop 加 create 模拟 REPLACE。
真实 Oracle 里 TRUNCATE 是 DDL(会隐式提交、不能回滚),REPLACE 还会连带处理依赖对象。
但「四种动作四种结果」这个骨架是一样的。
30 秒实验
- 把源数据改成 50 行(比目标的 100 行少),再跑
APPEND。 看结果是不是 150 行——APPEND不管多少,只管往里加。 - 给
SKIP那一支加上一句print("跳过了"),再跑。 确认它真的什么都没做——这一步是为了区分「跳过」和「导入了但结果一样」。 - 把
SQLFILE生成的内容改成「完整的建表加插入」,然后真的执行它。 看能不能还原出一张一样的表——这就是SQLFILE的用法:先看,再决定跑不跑。
常见误区
- 「
REPLACE和TRUNCATE一样」。 结果行数一样,后果不同。REPLACE会把目标表的索引、触发器、权限一起丢掉。 - 「
APPEND会去重」。 不会。重复数据照样插进去,有主键就报错,没主键就静默重复。 - 「
REMAP_TABLESPACE会把已有的表搬过去」。 只影响新建的对象。 搬已有表要用ALTER TABLE ... MOVE TABLESPACE。 - 「导入不用先备份,反正有 dmp」。 dmp 是【源】的数据。 导入改的是【目标】,目标被改坏了 dmp 救不了它。
自测三问(点开对答案)
1. `TABLE_EXISTS_ACTION` 四种取值分别做什么? —— **`SKIP` 跳过、`APPEND` 追加、`TRUNCATE` 清空再灌、`REPLACE` 删表重建。** 实务上默认用 `SKIP` 或 `APPEND`,因为它们不破坏已有对象。 2. `REMAP_SCHEMA` 和 `REMAP_TABLE` 有什么区别? —— **前者改「归谁所有」,表名不变;后者改表名。** **要两个都改,两个参数都得写。** 3. `SQLFILE` 参数有什么用? —— **把导入要做的事写成 SQL 脚本但不执行。** **用来「先看一眼它到底会干什么」,比真跑一遍安全。** 4. 导入之后必须做哪三件事? —— **收集统计信息、调整序列当前值、核对权限。**漏一件,导进去的数据就用不起来。下一篇讲 SQLLoader。数据泵搬的是「数据库里的对象」,SQLLoader 搬的是「文本文件里的行」。 它的速度来自绕过 SQL 引擎,代价也在于此。**
- ☐ (本篇完,下一篇 17.4 SQL*Loader 工具)
相关笔记
- EXPDP 导出数据
- EXPDP 和 IMPDP 概述
- 使用 RMAN 工具完全恢复
-
- 🐬 MySQL必知必会/00-索引
- 🏺 Oracle从入门到精通 · 索引