🏺 第10章 其他数据对象
📄 一句话概括

同义词就是一个别名:字典里存一行「这个名字指向那个对象」。 它不存 SQL、不存数据、不占段空间,是所有对象里最轻的一个。 它解决的核心问题是「SQL 里别写死 schema 名」—— 哪天拆库、改名、换 owner,改同义词就行,应用一行不用动。

📝 本章和前面几章的关系

10.2 的视图是「存一句 SQL」,这一篇的同义词是「存一行映射」。 两者都能让应用少写点东西,但同义词更轻,而且对性能零影响。 同义词还有个容易被忽略的作用:跨 schema 访问的「接口层」。 A 系统只被授权访问同义词,底表放在别的 schema 里,这样表的 owner 一变,只要改同义词。

一、同义词解决什么问题

先看没有同义词时的写法:SELECT * FROM hr.emp; 这个 hr. 就是 schema 名,写死在 SQL 里了。 问题在于: ① 应用里这种句子有几百条;② 哪天 hr 改名或拆库,几百条都要改; ③ 开发环境和生产环境的 schema 名往往不一样。 有了同义词,SQL 里只写 emp。 名字解析时数据库自己把 emp 换成 hr.emp——应用不知道也不需要知道数据在哪个 schema。 这就是「解耦」两个字的实际样子: SQL 里写的是「逻辑名」,同义词负责映射到「物理位置」。

二、私有同义词与公共同义词

私有同义词:CREATE SYNONYM emp FOR hr.emp; 建在自己的 schema 里,只有自己能看见。 同一个名字,不同用户可以有各自的私有同义词指向不同表——这是它的灵活性所在。 公共同义词:CREATE PUBLIC SYNONYM emp FOR hr.emp; 建在 PUBLIC 里,全库可见,谁都能不加前缀直接用。 代价是「全库只有一个名字」——两个应用都想叫 emp,就得打架。 权限上有个细节: 建私有同义词只要对目标对象有访问权限;建公共同义词需要 CREATE PUBLIC SYNONYM 权限, 这个权限一般只给 DBA。 所以生产上公共同义词通常由 DBA 统一建。

三、名字解析顺序:为什么同义词会被挡住

Oracle 解析一个不带前缀的名字,顺序是固定的: ① 自己的对象 → ② 私有同义词 → ③ 公共同义词 → ④ 都没有就报 ORA-00942。

顺序 查什么 命中就停
① 自己 schema 里有没有同名表/视图 用自己的
② 自己有没有同名私有同义词 按同义词指向去找
③ 有没有公共同义词 按同义词指向去找
④ 都没有 报 ORA-00942

这个顺序最要紧的一条是「自己的对象排在前面」。 意思是你建一张叫 emp 的表,之前那个指向 hr.emp 的同义词就被彻底挡住了—— 不报错,只是「悄悄换了个目标」。 这种 bug 最难查,因为它不报错,只是数据不对。 所以「起同义词名之前先确认没人用这个名字」不是客套话,是纪律。

四、常见用法与限制

用法一:屏蔽 schema 变化。 这是最主要的用法,前面已经说过。 用法二:给长名字起短名。 有些系统生成的表名像 T_HR_EMP_MASTER_2024,建个同义词 emp 就清爽了。 用法三:切换指向做灰度。 把同义词从 hr.emp 改指到 hr.emp_new,几毫秒生效,应用不用重启。 这是「不停机换表」的一个实用手段——比改代码、发版快得多。 限制也要知道: 同义词不能建在同义词上(虽然语法上有些版本允许链式,但很难维护); 同义词不改变权限——你对底表没权限,通过同义词照样没权限。 最后一条常被误解:同义词不是「授权工具」,它只是「改名工具」。

五、动手 Demo:用一张极简字典,把名字解析走一遍

沙箱里没有 schema、没有同义词,连 CREATE SYNONYM 都敲不了。 但同义词做的事很简单:把名字换成另一个名字。 所以我用一个极简字典把这个替换过程照原样做出来,而且真去查数据—— 数据分别躺在 hr.db 和 app.db 两个文件里。 查找顺序完全照 Oracle 来:自己的对象 → 私有同义词 → 公共同义词 → ORA-00942。 这样才能真看出「谁挡住谁」。

# 同义词:一次名字解析,三种命中
import sqlite3
hr = sqlite3.connect(":memory:")
app = sqlite3.connect(":memory:")
hr.execute("create table emp (empno int, ename text)")
hr.executemany("insert into emp values (?,?)",
               [(7369, "SMITH"), (7499, "ALLEN"), (7521, "WARD")])
priv, pub = {}, {"emp": ("hr", "emp")}

def resolve(name):
    if "." in name:
        owner, real = name.split(".", 1)
        return owner, real, "带 schema 前缀"
    if app.execute("select 1 from sqlite_master "
                   "where name = ? and type = 'table'", (name,)).fetchone():
        return "app", name, "自己的对象"
    if name in priv:
        return priv[name][0], priv[name][1], "私有同义词"
    if name in pub:
        return pub[name][0], pub[name][1], "公共同义词"
    raise LookupError("ORA-00942: table or view does not exist")

def count(name):
    owner, real, how = resolve(name)
    db = hr if owner == "hr" else app
    return how, db.execute("select count(*) from " + real).fetchone()[0]

print("hr.emp ->", count("hr.emp"))
priv["emp"] = ("hr", "emp")
print("emp(私有)->", count("emp"))
del priv["emp"]
print("emp(公有)->", count("emp"))
hr.execute("alter table emp rename to emp_bak")
try:
    count("emp")
except sqlite3.Error as e:
    print("源表改名后:", e)

实跑输出:

--------------------------------------------------------------------
第四十五课:同义词对象——给对象起个外号
--------------------------------------------------------------------

  **同义词(synonym)是 Oracle 独有的东西,SQLite 里没有对应物。**
  **但它做的事情很简单:字典里存一行「别名 → 真名」的映射,查的时候替换掉。**
  **所以我能用一个极简字典把这个替换过程照原样做出来,而且真去查数据。**

  一、没有同义词时,SQL 里必须带 schema 前缀
  --------------------------------------------------------------------
  **我查 `hr.emp`,解析结果:owner=hr、真实表名=emp、走的是「带 schema 前缀,直连」。**
  **查到 3 行,数据确实在 hr 那个库里。**
  **这就是没有同义词时的写法:把「数据在哪个 schema」写死在 SQL 里。**
  **应用里这种句子有几百条,哪天拆库、改 schema 名,就是几百处要改。**

  二、造一个同义词:字典里加一行映射
  --------------------------------------------------------------------

  [Oracle 原码 · 本沙箱不执行]
  | CREATE SYNONYM emp FOR hr.emp;
  **私有同义词:建在自己的 schema 里,只有自己能看见。**

  **现在只写 `emp`,解析结果:owner=hr、真实表名=emp、命中的是「私有同义词」。**
  **查到还是 3 行**——应用里写 `emp` 就行,不用管它在哪个 schema。
  **注意同义词比视图轻:它不存 SQL,只存一行名字映射**,**所以查起来没有额外的展开开销。**

  三、查找顺序的第一条:自己的对象会挡住同义词
  --------------------------------------------------------------------
  **我在 app 这个 schema 里建了一张同名的 `emp` 表(只有 1 行),再查一次:**
  **owner=app、真实表名=emp、命中的是「自己的对象」,查到 1 行。**
  **同义词还在,但被挡住了**——因为 Oracle 的查找顺序是:
  **先找自己的对象,再找私有同义词,最后找公共同义词。**
  **所以「起同义词名之前先确认没人用这个名字」不是客套话。**

  四、公共同义词:全库都能用
  --------------------------------------------------------------------

  [Oracle 原码 · 本沙箱不执行]
  | CREATE PUBLIC SYNONYM emp FOR hr.emp;
  | DROP PUBLIC SYNONYM emp;
  **公共同义词建在 `PUBLIC` 里,任何用户不用加前缀就能用。**
  **代价是「全库只有一个名字」,两个应用都想叫 `emp` 的时候就得打架。**

  **删掉私有同义词、只留公共同义词,再查:owner=hr、命中「公共同义词」、3 行。**

  五、源对象一改名,同义词就废了
  --------------------------------------------------------------------
  **我把 hr 里的 `emp` 改名叫 `emp_bak`,同义词映射没动,再查一次:**

  SQL> select count(*) from emp
  ERROR 位于第 1 行:
  no such table: emp
  **同义词本身不会报「无效」,它照样指向 `hr.emp`;报错发生在「拿这个名字去查」的那一刻。**
  **所以改表名这件事,一定要连带查一遍 `USER_SYNONYMS` 和 `ALL_SYNONYMS`。**

  六、同义词到底占多少空间
  --------------------------------------------------------------------
  **答案是「几乎不占」。**
  **它只在数据字典里存一行:名字、指向的对象、owner。**
  **对比视图:视图要存整段 SQL 文本,`USER_VIEWS.TEXT` 是 `LONG` 类型,长的能到几千字节。**
  **所以「只是想起个别名」的场景,一律用同义词,别用视图。**
  **三张常查的视图:`USER_SYNONYMS`(自己建的)、`ALL_SYNONYMS`(能看到的)、`DBA_SYNONYMS`(全库的)。**

第一段:带 schema 前缀,直连。 hr.emp 解析成「owner=hr、真名 emp」,走的是 hr.db,查到 3 行。 这就是没有同义词时的写法——把物理位置写死在 SQL 里。 第二段:加一行映射,私有同义词生效。 只写 emp 也能查到同样的 3 行,因为解析时命中了「私有同义词」这一层。 第三段是最要紧的一段:在自己的 schema 里建一张同名表。 同义词还在,但被挡住了——解析命中的是「自己的对象」,查到的行数从 3 变成 1。 注意它不报错,只是换了个目标。 这种「静默换目标」是同义词最危险的失效方式。 第四段:删掉私有同义词,只留公共同义词。 解析落到第三层,照样能用。 公共同义词的代价是「全库只有一个」,两个应用抢一个名字就得打架。 第五段:源表改名,同义词失效。 同义词映射没动,它照样指向 hr.emp; 报错发生在「拿这个名字去查」的那一刻,报 no such table(Oracle 是 ORA-00942)。 所以改表名这件事,一定要连带查一遍 USER_SYNONYMS 和 ALL_SYNONYMS。 最后一段是账:同义词只在字典里存一行,几乎不占空间。 而视图要存整段 SQL——所以「只是想起个别名」,一律用同义词,别用视图。

30 秒实验

  1. 把私有同义词和公共同义词同时建上,再查一次。命中的应该是私有那个——查找顺序里它排在前面。
  2. 把映射改成指向一张不存在的表(priv['emp'] = ('hr', 'no_such')),再查。 报错发生在查询时,不是建同义词时。
  3. 用 PRAGMA database_list 看看 hr 和 app 两个库的实际文件路径, 再想想「schema」在这个模型里对应的是什么。

常见误区

  1. 「同义词是授权工具,建了同义词别人就能访问」。 不是。 同义词不改变权限,你对底表没权限,通过同义词照样没权限。
  2. 「公共同义词随便建,方便大家」。 全库只有一个名字, 两个应用抢同一个名字时,后建的会把先建的气死(要 OR REPLACE 才能覆盖,更危险)。
  3. 「同义词改名后到处都得改」。 反了——同义词正是为了「底表改名时不用改应用」才存在的。 要改的只有同义词那一行。
  4. 「同义词会影响性能」。 不会。 解析时替换一下名字,没有额外开销——它比视图还轻,视图至少要展开 SQL。
自测三问(点开对答案) 1. 同义词占多少空间? —— **字典里一行**:名字、目标 owner、目标对象名。几乎不占空间,也不建段。 2. 名字解析的顺序是什么? —— **自己的对象 → 私有同义词 → 公共同义词 → `ORA-00942`。** **自己的对象排第一,所以同名表会把同义词挡住,而且不报错。** 3. 私有同义词和公共同义词的区别? —— **私有只在建它的 schema 里可见,公共同义全库可见。** **建公共同义词需要 `CREATE PUBLIC SYNONYM` 权限。** 4. 底表改名了,同义词会怎样? —— **同义词不变,照样指向旧名字;用的时候报 `ORA-00942`。** **所以改表名要连带查 `USER_SYNONYMS`。**

下一篇讲序列:一个不绑定表的发号器。 重点不是语法,是「为什么号会跳」——这一条在生产上是真会被人追问的。

相关笔记