Oracle 里「用户」不只是登录账号,它还是一个容器,装着这个用户建的所有对象。 这个容器就叫模式(schema),一个用户对应一个模式,名字一样。 「表不存在」的头号原因不是没建,是建在别人名下了——不写模式名前缀, Oracle 只认你自己那个容器。
2.5 节讲过数据字典,
user_tables/all_tables/dba_tables 的区别就是「看自己/看有权/看全库」。
这一篇解释那个区别的由来:因为对象是分装在各个模式里的。
教材后面所有例子都跑在 scott 这个示例用户上。先认识它,后面才看得懂那些表名。
一、用户就是模式,模式就是容器
在 MySQL 里,「用户」和「数据库」是两件事:用户管登录,数据库装表。
Oracle 把这两件事合成一件:一个用户登录进来,看到的就是以自己名字命名的那个容器。
所以 scott.emp 里的 scott 有两重身份:它既是用户名,也是模式名。
不是「scott 用户查 emp 表」,而是「scott 这个容器里的 emp 表」。这个措辞的差别,
在排查「表找不到」时很关键。
好处是隔离彻底:两个用户各建一张 emp,互不干扰,名字一样,内容不同,谁也不会覆盖谁。
代价是引用麻烦:跨模式必须写全名,还得有权限。
二、容器里能装什么
模式里装的不只是表。 日常打交道的就这六种:
| 对象 | 干什么 |
|---|---|
| 表 table | 存数据,最核心的对象 |
| 视图 view | 存一条 SQL,查它等于查那条 SQL |
| 索引 index | 给某列建一份目录,换取查得更快 |
| 序列 sequence | 专门发号,主键自增就靠它 |
| 同义词 synonym | 给对象起个别名,跨模式引用时省字 |
| 过程/函数 | 存起来的 PL/SQL 代码块 |
这六种里,只有「表」真正存数据。
视图存的是 SQL 文本、索引存的是排序结果、序列存的是一个数、过程存的是代码。
理解这一点,「删了索引数据还在」就不奇怪了。
Oracle 里对象总数超过三十种(还有簇、分区、物化视图、目录、类型……)。
不用背,遇到再查 user_objects 的 object_type 列就行。
三、跨模式怎么引用,以及那道权限门
引用别人的表,要写 模式名.表名。
但光写对名字不够,还得有权限——这是两道独立的门。
最坑的一点:Oracle 把「表不存在」和「没权限」报成同一个错。
都是 ORA-00942: 表或视图不存在。
这是故意的:如果报错能区分,你就能靠试错探出别人有哪些表。
所以看到 ORA-00942,要同时怀疑两件事:名字写错了,或者没授权。
排查顺序:先查数据字典确认表在不在(all_tables 里有就说明是权限问题),
再看有没有 grant 过。
同义词就是为这件事生的。
DBA 建一个 create synonym emp for scott.emp,之后所有人直接写 emp 就行,不用记是谁名下的。
代价是「这个 emp 到底是谁的」变得更难查——同义词查起来要两步。
四、scott:教材从头用到尾的四张表
scott 是 Oracle 建库时可选安装的示例用户,密码传统上是 tiger。
它只有四张表,但这四张表够讲完整个 SQL。
| 表 | 行数 | 是什么 |
|---|---|---|
dept |
4 | 部门:编号、名称、所在地 |
emp |
14 | 员工:编号、姓名、工种、上级、入职日、工资、奖金、部门 |
salgrade |
5 | 工资等级:等级号、下限、上限 |
bonus |
0 | 奖金表——故意留空,用来讲「空表查出来是什么」 |
emp 这十四行设计得非常好:有总裁(没有上级)、有拿奖金的、有不拿的、
有入职日期差十年的、有同一个部门不同工种的。
几乎所有 SQL 的边界情况,它都能演。
bonus 是空的,这不是数据丢了。
空表是专门留的教具:count(*) 给 0、sum() 给 NULL、
where 过滤后还是空——这些行为只有对着空表才看得清。
五、SQLite 里怎么演这件事
SQLite 没有「用户」这个概念,但它有 schema,而且能挂多个。
默认那个叫 main,临时表在 temp,attach 上来的叫你自己起的名字。
pragma database_list 能列出当前挂着的所有 schema。
内存库只有一条 main,文件路径是空的——因为它根本没落到磁盘上。
attach database '文件路径' as scott 之后,就多了一个叫 scott 的 schema。
这时候 main.emp 和 scott.emp 可以同时存在,各查各的。
这就把 Oracle 的「模式隔离」演出来了——虽然底层机制完全不同。
差别在于「不写前缀时找谁」:
SQLite 按 temp → main → 挂载库的顺序找;Oracle 只认你自己那个模式,一个都不越界。
所以 SQLite 上「不写前缀也能查到」的代码,搬到 Oracle 上会直接失败。
六、动手 Demo:attach 一个库,造出两个同名表
这一段就做一件事:
在同一份连接里造出两个 schema,各放一张 emp,然后分别用带前缀和不带前缀的方式去查。
你会看到「同名不打架」和「不写前缀只认 main」这两件事同时成立。
import os, sqlite3, tempfile
c = sqlite3.connect(":memory:")
c.execute("create table emp (empno int, ename text)")
c.executemany("insert into emp values (?,?)", [(7369, 'SMITH'), (7839, 'KING')])
# 一、SQLite 也有 schema,默认那个叫 main
print(c.execute("pragma database_list").fetchall())
# 二、attach 一个库文件,就多出一个 schema
db = os.path.join(tempfile.mkdtemp(), "scott.db")
c.execute("attach database '%s' as scott" % db)
c.execute("create table scott.emp (empno int, ename text)")
c.execute("insert into scott.emp values (9001, 'TOM')")
# 三、两张同名表,靠模式名区分
print(c.execute("select count(*) from main.emp").fetchone())
print(c.execute("select count(*) from scott.emp").fetchone())
# 四、不写模式名,只认自己那个
print(c.execute("select count(*) from emp").fetchone())
print(c.execute("pragma database_list").fetchall())
实跑输出:
--------------------------------------------------------------------
第十六课:用户模式——Oracle 把「用户」和「一堆对象」绑在一起
--------------------------------------------------------------------
**Oracle 里「用户」不只是一个登录账号,它还是「一堆对象的容器」。**
**这个容器就叫模式(schema)。**
**一个用户对应一个模式,名字一样,一一对应。**
**你建的表、视图、索引、序列,全都装在你自己的模式里。**
先看这个「容器」里能装什么
--------------------------------------------------------------------
SQL> select kind, note from obj
KIND NOTE
-----------------------------------------------
表 table 存数据的地方,最核心的对象
视图 view 存一条 SQL,查它等于查那条 SQL
索引 index 给某列建一份目录,换取查得更快
序列 sequence 专门发号,主键自增就靠它
同义词 synonym 给对象起个别名,跨模式引用时省字
过程/函数 存起来的 PL/SQL 代码块
已选择 6 行
**这张表只列了六种,实际有三十多种。**
**但日常打交道的就是这六种**,剩下的遇到再查。
模式之间怎么互相引用
--------------------------------------------------------------------
[Oracle 原码 · 本沙箱不执行]
| -- 当前用户是谁
| select user from dual;
|
| -- 带上模式名前缀,跨模式访问
| select count(*) from scott.emp;
|
| -- 不写前缀,只看自己模式里的
| select count(*) from emp;
dual 是 Oracle 特有的单行伪表,只为凑语法。
**`scott.emp` 里的 `scott` 就是模式名。**
**不写前缀,默认就是你自己的模式——所以「表不存在」最常见的原因是「它在别人名下」。**
**权限是另一道门:** 就算你写了 `scott.emp`,
**没被 `grant select on scott.emp to 你` 过,照样报「表或视图不存在」。**
**Oracle 故意不区分「没这个表」和「没权限」,免得你靠报错试探别人的表名。**
SQLite 里怎么演这件事
--------------------------------------------------------------------
**SQLite 没有「用户」这个概念,但它有 schema,而且可以挂多个。**
**默认那个叫 `main`,临时表在 `temp`,用 `attach` 挂上来的叫你自己起的名字。**
SQL> pragma database_list
SEQ NAME FILE
-------------
0 main
已选择 1 行
**只有一条 `main`,指向一个空路径——因为是内存库。**
SQL> pragma database_list
SEQ NAME FILE
-------------------------------------------------------------------
0 main
2 scott C:\Users\admin\AppData\Local\Temp\ora47_hteiv0kl\scott.db
已选择 2 行
**现在有两个 schema 了:`main` 和 `scott`。**
**注意它们各自有一张叫 `emp` 的表,互不干扰——这正是模式的作用:同名不打架。**
SQL> select empno, ename, sal from main.emp order by empno
EMPNO ENAME SAL
-----------------
7369 SMITH 800
7499 ALLEN 1600
7521 WARD 1250
7566 JONES 2975
7654 MARTIN 1250
7698 BLAKE 2850
7782 CLARK 2450
7788 SCOTT 3000
7839 KING 5000
7844 TURNER 1500
7876 ADAMS 1100
7900 JAMES 950
7902 FORD 3000
7934 MILLER 1300
已选择 14 行
SQL> select empno, ename, sal from scott.emp order by empno
EMPNO ENAME SAL
----------------
9001 TOM 1200
9002 JERRY 1500
已选择 2 行
SQL> select 'main' as schema_name, count(*) as n from main.emp union all select 'scott', count(*) from scott.emp
SCHEMA_NAME N
--------------
main 14
scott 2
已选择 2 行
**上面这条是「带上模式名」的写法,跟 Oracle 的 `scott.emp` 一模一样。**
**不写模式名时会先找 `main`,找不到再找 `temp`,都不会自动去 `scott` 里翻。**
**Oracle 的规则更硬:不写前缀就只认自己的模式,连「别人的同名表」都不会看一眼。**
scott 示例模式:教材从头用到尾的四张表
--------------------------------------------------------------------
**scott 是 Oracle 装库时自带的一个示例用户,教材里所有例子都跑在它身上。**
**它只有四张表,但这四张表够讲完整个 SQL。**
SQL> select 'dept' as t, count(*) as rows from dept union all select 'emp', count(*) from emp union all select 'salgrade', count(*) from salgrade union all select 'bonus', count(*) from bonus
T ROWS
-------------
dept 4
emp 14
salgrade 5
bonus 0
已选择 4 行
**`dept` 4 行是部门,`emp` 14 行是员工,`salgrade` 5 行是工资等级,`bonus` 是空表。**
SQL> select * from dept order by deptno
DEPTNO DNAME LOC
--------------------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
已选择 4 行
SQL> select * from salgrade order by grade
GRADE LOSAL HISAL
-----------------
1 700 1200
2 1201 1400
3 1401 2000
4 2001 3000
5 3001 9999
已选择 5 行
**`bonus` 是空的,这不是漏了——教材故意留一张空表,用来讲「查询空表返回什么」「聚合函数在空表上返回什么」。**
第一次 pragma database_list 只有 main 一行,文件路径是空的(内存库)。
attach 之后变成两行,scott 那行带着真实的文件路径。
main.emp 查出 2 行,scott.emp 查出 1 行。
两张表名字一模一样,靠前缀区分——这就是模式最直接的作用。
最后那句 select count(*) from emp 查出 2 行,不是 3 行。
因为不写前缀时只找 main,scott 里的那张根本不在候选里。
Oracle 的规则更硬:它连「别人的同名表」都不会看一眼。
attach 的路径不能当参数传。
我一开始写 attach database ? as scott 加参数绑定,直接报错——
SQLite 的 attach 只接受字符串字面量。
这类「看起来该支持参数却不支持」的语句,在 DDL 里很常见。
注意这里两张表都叫 emp、都有 empno 和 ename 两列,但约束、行数、内容都不同。
这才是「模式」的意义:它不是命名空间的花架子,是真的隔离。
30 秒实验
- 在
scott里再建一张dept,然后不写前缀去查dept,看查到的是哪一张。 结果是main那张(如果存在)或者报错——顺序永远是temp→main→ 挂载库。 - 把
attach换成attach database ':memory:' as tmp,再pragma database_list看一眼。 内存库也能挂,而且路径同样为空——说明 schema 和「有没有文件」是两件事。 - 用
select * from sqlite_master和select * from scott.sqlite_master各查一次。 每个 schema 都有自己的数据字典,这就是 2.5 节说的「字典是表」的进一步证明。
常见误区
- 以为「用户」只是登录账号。 在 Oracle 里它同时是容器。 建表和建用户是两件不同的事,但表必须有主——所以先有用户,才有地方放表。
- 看到
ORA-00942就认定表不存在。 也可能只是没权限。 Oracle 故意不区分这两种情况,你得自己去all_tables里确认。 - 以为同义词能解决一切。 它省了打字,但让「这个表到底是谁的」更难查。 接手别人的库时,同义词是排查成本最高的一类对象。
- 在 SQLite 上验证过就以为 Oracle 一样。 不写前缀时找谁,两套引擎规则不同。 跨库迁移时,「要不要写模式名」是最先炸的地方。
自测三问(点开对答案)
1. Oracle 里「用户」和「模式」是什么关系? —— **一一对应,名字相同。** 用户是登录身份,模式是这个用户名下的对象容器。 **所以 `scott.emp` 的意思是「scott 这个容器里的 emp」。** 2. `ORA-00942` 一定代表表不存在吗? —— 不一定。**没权限也是这个错。** Oracle 故意不区分,防止你靠报错试探别人的表。 **排查时先去 `all_tables` 确认表在不在。** 3. `bonus` 是空表,为什么教材要留它? —— **空表是最好的教具。** `count(*)` 给 0、`sum()`/`avg()` 给 NULL、 `where` 过滤后还是空——**这些边界行为只有对着空表才看得清。**下一篇进正题:检索数据。简单查询、筛选、分组、排序、多表关联,五种写法配五个真实的坑。
- ☐ (本篇完,下一篇 4.3 检索数据)
相关笔记
- SQL 语言简介
- 数据字典
- 逻辑存储结构
-
- 🗄️ 数据库基础/00-索引
- 🏺 Oracle从入门到精通 · 索引