🏺 第2章 Oracle 体系结构
📄 一句话概括

数据字典不是「给你查的表」,是「系统自己维护的表」。你建一张表,它往字典里写一条; 你加个索引,再写一条。所以字典里永远只有「已经发生的事实」,没有你的意图。

📝 本章和前面几章的关系

这一篇是第 2 章的收尾, 也是全书最实用的一篇——后面每一章查「有没有、在哪、多大、谁在用」,都是查字典。 它同时是「三层前缀」的说明书:user_ 看自己、all_ 看有权、dba_ 看全库。 这个三层结构在第 12 章讲权限时会再出现一次。

一、三层前缀:同一件事,三种视野

前缀 范围 谁能查
user_ 当前用户自己拥有的对象 所有人
all_ 当前用户有权访问的对象(含别人的) 所有人
dba_ 全库所有对象 要有 DBA 权限
v$ 动态性能视图:内存与运行状态 要有相应权限

同一个对象在三个前缀下都有记录,只是可见范围不同。 user_tables 和 dba_tables 的结构几乎一样—— 因为底层是同一张基表,只是视图上加了过滤条件。 v$ 那一类要单独记:它们不是表,是内存结构的映射。 实例没起来就查不到(1.5 节演过)。

二、最常用的那几个视图

要看什么 查哪个 说明
有哪些表 user_tables 含行数、块数、最后分析时间
表有哪些列 user_tab_columns 类型、长度、是否可空
有哪些索引 user_indexes/user_ind_columns 索引在哪些列上
有哪些约束 user_constraints 主键、外键、检查
当前会话 v$session 谁连着、在等什么
正在跑的 SQL v$sql 文本、执行次数、逻辑读

这几张表能回答 DBA 的日常问题: 「这张表多大」「这个索引有没有人用」「现在有几个会话在等锁」—— 答案全在字典里,不用去翻数据。

三、为什么字典不能随便改

字典是系统维护的,你不该 update 它。改字典等于骗系统: 告诉它「这张表有 8 列」,而实际有 9 列,之后所有依赖字典的操作都会错。 官方给的唯一「改字典」的正道是 DDL——alter table、create index。 DDL 会同时改数据字典和实际结构,两边保持一致。 有些老资料会教你 update sys.obj$ 之类——那是拿库开玩笑,改完可能连启动都起不来。别学。

四、desc 不是 SQL

教材里天天敲 desc emp,但它是 SQL*Plus 命令,不是 SQL 语句。 所以它不带分号、也不能在 JDBC 里执行。 想在程序里看表结构,得查字典: select column_name, data_type from user_tab_columns where table_name='EMP'。 这条才是 SQL,能塞进任何客户端。 这个区别在 1.4 节提过一次,这里再钉一遍: SQL*Plus 命令是「工具的命令」,SQL 是「数据库的语言」。 换工具,前者失效,后者照样跑。

五、动手 Demo:用 SQLite 的字典查自己

SQLite 的字典只有一层:sqlite_master。 但它足够把「查字典」这件事演完整——建视图、建索引,再看字典里怎么记的。 最后一段是「用字典查字典」: 表名从 sqlite_master 来,列数从 pragma_table_info 来,两边对一下。

con = sqlite3.connect(':memory:')   # 里面是 scott 四表
con.execute('CREATE VIEW v_emp AS SELECT empno, ename, sal FROM emp WHERE sal > 1000')
con.execute('CREATE INDEX idx_ename ON emp (ename)')

# 1 这个库里有什么:一张表全记着
con.execute('select type, count(*) from sqlite_master group by type').fetchall()

# 2 看表结构(相当于 DESC)
con.execute("select cid, name, type, [notnull], pk from pragma_table_info('emp')").fetchall()

# 3 看索引
con.execute("pragma index_list('emp')").fetchall()
con.execute("pragma index_info('idx_ename')").fetchall()

# 4 用字典查字典:每张表几列
con.execute('select m.name, (select count(*) from pragma_table_info(m.name)) '
            'from sqlite_master m where m.type = \'table\'').fetchall()

实跑输出:

--------------------------------------------------------------------
第十课:数据字典——数据库自己的那本账
--------------------------------------------------------------------

  **数据字典不是「给你查的表」,是「系统自己维护的表」。**
  **你建一张表,它往字典里写一条记录;你加个索引,再写一条。**
  **所以字典里的内容永远是「已经发生的事实」,不是你的意图。**


  三层前缀:谁看得到什么
  --------------------------------------------------------------------

  SQL> select pre, scope, who from pre
  PRE   SCOPE                              WHO
  --------------------------------------------------------------
  user_ 当前用户自己的对象                 所有人都有
  all_  当前用户有权访问的对象(含别人的) 所有人都有
  dba_  全库所有对象                       **只有 DBA 权限能查**
  v$    动态性能视图:内存与运行状态       需要相应权限
  已选择 4 行
  **记忆法:**
  **`user_` 是我的、`all_` 是我能碰的、`dba_` 是全部的。**
  **同一个东西在三个前缀下都有一条,只是可见范围不同。**

  SQLite 的字典长什么样
  --------------------------------------------------------------------
  **SQLite 只有一个「全库」层,就是 `sqlite_master`:**
  **一张普通的表,五列——类型、名字、所属表、根页号、建它的那句 SQL。**

  SQL> select type, count(*) as n from sqlite_master group by type
  TYPE  N
  -------
  index 1
  table 4
  view  1
  已选择 3 行

  SQL> select type, name, tbl_name from sqlite_master where type in ('view','index') order by name
  TYPE  NAME      TBL_NAME
  ------------------------
  index idx_ename emp
  view  v_emp     v_emp
  已选择 2 行

  看表结构:DESCRIBE 的等价物
  --------------------------------------------------------------------
  教材里天天敲 `desc emp`,**它在 SQLite 里就是 `pragma_table_info`:**

  SQL> select cid, name, type, "notnull", pk from pragma_table_info('emp')
  CID NAME     TYPE    NOTNULL PK
  -------------------------------
  0   empno    INTEGER 0       1
  1   ename    TEXT    1       0
  2   job      TEXT    0       0
  3   mgr      INTEGER 0       0
  4   hiredate TEXT    0       0
  5   sal      REAL    0       0
  6   comm     REAL    0       0
  7   deptno   INTEGER 0       0
  已选择 8 行

  [Oracle 原码 · 本沙箱不执行]
  | DESCRIBE emp;
    Oracle 的 desc 是 SQL*Plus 命令,不是 SQL,所以不带分号也行。

  **注意 `pragma_table_info` 能当表来查**——
  **可以 `where`、可以 `join`、可以算统计。**
  **Oracle 里对应的写法是 `select * from user_tab_columns where table_name='EMP'`,一样能筛能连。**

  看索引:DBA 查的是什么
  --------------------------------------------------------------------

  SQL> pragma index_list('emp')
  SEQ NAME      UNIQUE ORIGIN PARTIAL
  -----------------------------------
  0   idx_ename 0      c      0
  已选择 1 行
  **五列:序号、索引名、是否唯一、来源、是否部分索引。**
  **「来源」那一列最有意思:`c` 是 `create index` 建的(就是这条),`u` 是建表时写 `unique` 自动建的。**

  SQL> pragma index_info('idx_ename')
  SEQNO CID NAME
  ---------------
  0     1   ename
  已选择 1 行

  [Oracle 原码 · 本沙箱不执行]
  | SELECT index_name, column_name, column_position
  |   FROM user_ind_columns WHERE table_name = 'EMP';


  一条查询把「字典能干什么」说清
  --------------------------------------------------------------------

  SQL> select m.type, m.name, (select count(*) from pragma_table_info(m.name)) as cols from sqlite_master m where m.type = 'table' order by m.name
  TYPE  NAME     COLS
  -------------------
  table bonus    4
  table dept     3
  table emp      8
  table salgrade 3
  已选择 4 行
  **这就是「用字典查字典」:**
  **表名从 `sqlite_master` 来,列数从 `pragma_table_info` 来,两边对得上。**
  **DBA 日常干的事有一大半是这个:**
  **「这个库有多少张表、多少索引、哪张表最大、哪个索引没人用」**——**全是查字典,不是查数据。**

  **一条提醒:**
  **别在业务 SQL 里直接依赖 `dba_` 视图。**
  **它们结构会随版本变,而且查全库很重。** **要看结构用 `user_`,要看运行状态用 `v$`。**

sqlite_master 只有五列:类型、名字、所属表、根页号、建它的那句 SQL。 最后那列最有价值——建表语句原文都在里面, 相当于 Oracle 的 user_tables 加 user_views 加 user_indexes 三合一。 pragma_table_info 能当表查(where、join、子查询都行),这是它比 desc 强的地方: desc 只给你看,pragma_table_info 能参与计算。 pragma index_list 的「来源」列值得记: c 是 create index 建的,u 是 unique 约束自动建的,pk 是主键自动建的。 Oracle 里查 user_indexes.index_type 和 generated 列,看的是同一件事。 最后那条「每张表几列」是这一篇的重点: DBA 干的活一大半是这种「查结构、算统计」,不是「查数据」。 所以会写这种嵌套查询,比会写业务查询更接近运维。

30 秒实验

  1. 给 emp 加一个 unique 约束,再看 pragma index_list 的「来源」列——会多出一条 u。
  2. 查 sqlite_master 里的 sql 列,把 emp 的建表语句原样打出来。 这就是「库自己记得你要建什么」。
  3. 把 pragma_table_info 换成 pragma_index_info,查索引建在哪一列上。 再看 pragma_index_list 的结果,两张一对,索引结构就出来了。

常见误区

  1. 在业务代码里 select * from dba_tables。 查全库很重,而且 dba_ 视图结构会随版本变。 看自己的用 user_。
  2. 以为 desc 是 SQL。 它是 SQLPlus 的命令,JDBC、Navicat、Python 驱动里都用不了*, 只能用字典视图代替。
  3. 直接 update 数据字典。 网上有些「修复」教程教你改 sys.obj$, 那是骗系统,改完可能起不来。 正统做法只有 DDL。
自测三问(点开对答案) 1. `user_tables`、`all_tables`、`dba_tables` 三者的结构为什么几乎一样? —— **因为底层是同一张基表**,三个视图只是加了不同的过滤条件: **`user_` 过滤「owner 是我」,`all_` 过滤「我有权限」,`dba_` 不过滤。** 2. 为什么 `v$` 开头的视图在实例没起来时查不到? —— **它们不是表,是内存结构的映射。** 实例没起来,内存结构不存在, **自然查不到**(报 `ORA-01034` 或类似错误)。 3. 想在程序里看表结构,用什么? —— **查字典:`user_tab_columns`。** `desc` 是 SQL*Plus 的命令,**程序里没有这个命令**, 只有 SQL 能用。

第 2 章到这里结束。 下一篇进第 3 章:SQL*Plus 命令——第 3.1 节先讲「SQLPlus 和数据库怎么交互」, 把「本地连接」和「通过网络连接」这两条路拆开。*

相关笔记