UP | HOME

Oracle 关系数据库

Table of Contents

1 数据库概述

Oracle Database,又名 Oracle RDBMS,或简称 Oracle。是甲骨文公司的一款关系数据 库管理系统。它是在数据库领域一直处于领先地位的产品。可以说 Oracle 数据库系统是 目前世界上流行的关系数据库管理系统,系统可移植性好、使用方便、功能强,适用于各 类大、中、小、微机环境。它是一种高效率、可靠性好的、适应高吞吐量的数据库解决方 案。

1.1 数据库和实例

  • 数据库 (Database) 指的是存放于磁盘中的一系列文件,数据库是独立于数据库实例
  • 数据库实例 (Database Instance) 指定是用于管理数据库文件的一系列内存数据结 构。实例包含:
    • SGA (System Global Area) 全局共享内存
    • PGA (Program Global Area) 每个 Client 都有自己私有的会话内存
    • Background Processes 后台进程

下图展示了 Oracle 的数据库和 Client Process 交互的细节

oracle-instance-and-database.png

1.2 数据存储结构

数据存可以分成物理存储好逻辑存储两个部分,两者的联系见下图

oracle-segments-tablespaces-datafiles.png

物理存储结构当执行 CREATE DATABASE 时,下面的文件将被创建

  • 数据文件 (Data Files) 包含所有的数据库的数据,数据的逻辑结构例如表、索引 都是存储在数据文件
  • 控制文件 (Control Files) 包含物理结构的一些元信息,例如:数据库名称和数据 库文件的路径
  • 重做日志 (Online Redo Log Files) 包含重做的日志文件,每个重做日志由一系列 的 Redo Entires (也被称为 Redo Records)组成
  • 还一些其他的物理文件 例如:Parameter Files, Diagnostic Files, Backup Files, Archived Redo Log Files 等

逻辑存储逻辑结构是存储在一系列 Data Blocks 中

  • (Data Blocks) 由定长字节的文件组成的二进制文件
  • (Extents) 由定长的逻辑连续 Data Blocks 构成。Extends 一次性分配,用来 存储特定类型的信息
  • (Segments) 由一系列的 Extents 构成,可以对应一些用户的对象,例如:表、 索引、Undo Data 和 Temporary Data
  • 表空间 (Tablespace) 一个 Database 被分成多个逻辑的 Tablespace 单元。 Tablespace 是 Segments 的逻辑容器。

1.3 数据库实例结构

数据库实例是使用内存数据结构和进程来管理数据库,当应用需要连接数据库时,它将 会首先连接到一个数据库实例中

1.3.1 Oracle 数据进程

Oracle Processes 包括 Server Processes 和 Background Processes。大多数情况下, Oracle Processes 和 Client Processes 运行在不同电脑中。 Oracle 数据库在启动 时会启动一系列的数据库实例

  • Client Processes 用来运行软件或 Oracle 工具,大部分运行环境是在不同电脑上
  • Background Processes 用来处理多个 Oracle 数据库程序,可以进行异步 I/O 或者 监控其它 Oracle 数据库。
  • Server Processes 和 Client Processes 与 Oracle 数据库交互,用来完成处理请求

1.3.2 实例内存结构

  • SGA 是包含一个数据库实例的数据和控制信息的一组共享内存的。例如:SGA 包含 Cached Data Blocks 和 Shared SQL Area
  • PGA 是包含一个 Background Processes 和 Server Processes 实例的数据和控制信 息的一组内存区域

2 关系数据结构

2.1 数据库和表

2.1.1 创建表

CREATE TABLE [scmname.]tabname (
  colname DBTYPE [NOT NULL | NULL] [DEFAULT DEFAULT_VALUE] [PRIMARY KEY]
);
  • Oracle 数据库中 scmname, tabname, 和 colname 不加引号默认大写,即使 使用小写的名称也会自动转成大写。如果非要使用小写需要使用单引号将名称包起来
  • Oracle 一张表最多只能有 254 列
  • dbtype 是数据类型,Oracle 数据库中常见的有 number, varchar2, date,布尔型一 般使用 char(1)number(1) 来模拟

简单的用户表创建

CREATE TABLE USERS (
  ID NUMBER(16) PRIMARY KEY NOT NULL,
  CREATED_BY VARCHAR2(64) DEFAULT 'SYSMAN' NOT NULL,
  UPDATED_AT DATE DEFAULT SYSDATE NOT NULL,
  VALID CHAR(1) DEFAULT 'Y' NOT NULL,
  -- CODE RULE AND TYPE
  CODE VARCHAR2(32) UNIQUE NOT NULL,
  NAME VARCHAR2(32)
);

COMMENT ON COLUMN USERS.CODE IS '用户编号, 用于登录';
COMMENT ON COLUMN USERS.NAME IS '用户名字, 用于显示';

2.1.2 数据类型

  1. 数值型
    • number(precision[,scale]) : 数值型,用于表示整数和实数。 precision 表示 精度,scale 表示数值范围。precision 的取值范围为 1 到 38,scale 的取值范 围为 -84 到 127。一句话: precision 是总的数字位数, scale 是小数点后的数 字个数
  2. 字符串
    • varchar2(size[byte|char]) : 变长字符串类型,size 表示字符串最大长度,单 位可以是 byte 或 char。 size 的取值范围为 1 到 4000。单位 byte 表示所用的 字节数,单位 char 表示所用的字符(character)数
    • nvarchar2(size) : 边长 unicode 编码字符串类型, AL16UTF16 字符编码的 byte 数是 utf8 字符编码的三倍,所以 nvarchar2 的 size 取决于具体的编码 集。size 的取值范围为 1 到 4000
    • char(size[byte|char]) : 定长字符串类型,size 的取值范围是 1 到 2000 。 单位 byte 和单位 char 语义一样都是表示字符(character)数
    • long : 2gb 的超长字符串。 每张表只允许一列是 long 类型

    注意 数据库中的 char 存储时每个字段大小占用的字节数是固定的, varchar2 存储时暂用的字节是变化的。两者使用的场景不同,对于使用字符串固定长度的,例 如:valid 字段, 'y' 表示合法, 'n' 表示不合法,可以使用 char 来存储, 这样访问速度快;大部分的场景:字符串长度是不确定的,而且变化比较大,这时最 好使用 varchar2 来存储,这样节约存储空间

  3. 时间/日期类型
    • date : 时间和日期类型。 Oracle 的时间和日期都用 date 类型表示, 默认的 时间格式字符由 NLS_DATE_FORMAT 参数决定,它是 'DD-MMM-YY' 格式,例如: '13-OCT-92', '07-JAN-98'
  4. 二进制 Lob
    • clob : 最多可以存储 4GB 的数据字符的大对象(Character Data of Large Object)
    • blob : 最多可以存储 4GB 数据的二进制大对象(Binary Large Object)

2.1.3 修改表结构

-- 添加一列
alter table tabname add colname varchar2;
comment on column tabname.colname is 'comments';
alter table t_employee_base add column is_active char(1) default 'n';
comment on column t_employee_base.is_active is '用户是否激活,默认为 n,表示未激活';
-- 删除一列
alter table tabname drop column colname;
-- 添加主键
alter table tabname add primary key ("pk_name");
-- 重命名列
alter table tabname rename column oldcol to newcol;

2.1.4 查看所有表

-- 查看当前数据库
select name from v$database;
-- 查看所有表
select table_name from user_tables order by table_name;
-- 查看所有表和视图,以及注释
select
  a.table_name || ' ' || a.comments
from
  user_tab_comments a
where
  a.table_type in ('table', 'view')
order by
  a.table_name;
-- 查看单个表结构
desc tabname;

2.1.5 临时表

临时表主要作用是保存事务或者会话的中间数据,所以一般包含事务级别的和会话级别 的临时表

  1. 事务级临时表 on commit delete rows 当 COMMIT 的时候删除数据(默认情况)
  2. 会话级临时表 on commit preserve rows 当 COMMIT 的时候保留数据,当会话结 束删除数据
create global temporary table tabname (
  id number
) on commit delete rows;

Oracle 的临时表创建完就是真实存在的,无需每次都创建。 删除临时表和删除普通表 一样

drop table tabname;
truncate table tabname;

2.1.6 其它

  • Oracle 表默认存储结构是堆 (Heap)
  • Oracle 的空值 NULL 不同于字符串,空值在数据库中的存储方式有以下两种:
    1. 如果 NULL 在两列之间,需要一字节来存储,一般是 ASCII 码 Zero 值
    2. 如果 NULL 位于行末端,一般默认不存

2.2 索引

索引 (Index) 是数据库的可选的一种数据结构,主要用处是加速数据存取

2.2.1 创建索引

-- 单行索引
create index idxname on tabname(colname);
-- 组合索引
create index idxname on tabname(colname1, colname2);

2.2.2 修改和删除索引

-- 重建索引:如果经常在索引列上执行 dml 操作,需要定期重建索引
alter index idxname rebuild;
-- 删除索引
drop index idxname;

2.2.3 索引类型

  1. B-tree Indexes 适合与大量的增删改 (OLTP)
  2. Bitmap and Bitmap Join Indexes 适合与决策支持系统,做 Update 代价非常高
  3. Funtion-based Indexes 经常对某个字段做查询的时候是带函数操作的

    -- 函数索引
    create index emp_name_upper_ix on emp (upper(name));
    
  4. Application Domain Indexes

2.3 分区,视图,序列等

2.3.1 分区

分区指的是对数据量较大,访问速度较慢的表进行分成几个子区域进行管理。分区表包 含 一个或多个 分区,注意包含一个分区的分区表和不分区表是不同的,包含一个分 区的分区表可以添加新的分区,而不分区表却不能添加新的分区。

分区主要包含以下要素

  • 分区键 (Partition Key)
  • 分区策略 (Partition Strategies),常见的分区有一下几种
    • 基于范围的分区 (Range Partition)
    • 基于列表的分区 (List Partition)
    • 基于哈希的分区 (Hash Partition)

2.3.2 创建分区表

创建分区表主要是在 create table 语句后面加 partition by 来说明分区策略

基于范围的分区表

CREATE TABLE TIME_RANGE_SALES (
  PROD_ID NUMBER (6), -- 产品 ID
  CUST_ID NUMBER,     -- 顾客 ID
  TIME_ID DATE,       -- 时间 ID
  CHANNEL_ID CHAR(1), -- 渠道 ID
  PROMO_ID NUMBER(6),
  QUANTITY_SOLD NUMBER (3),
  AMOUNT_SOLD NUMBER (10, 2)
) PARTITION BY RANGE (TIME_ID) (
  PARTITION SALES_1998 VALUES LESS THAN (TO_DATE('1999-01-01', 'YYYY-MM-DD')),
  PARTITION SALES_1999 VALUES LESS THAN (TO_DATE('2000-01-01', 'YYYY-MM-DD')),
  PARTITION SALES_2000 VALUES LESS THAN (TO_DATE('2001-01-01', 'YYYY-MM-DD')),
  PARTITION SALES_2001 VALUES LESS THAN (MAXVALUE)
);

基于列表的分区表

CREATE TABLE LIST_SALES (
  PROD_ID NUMBER(6), -- 产品 ID
  CUST_ID NUMBER,     -- 顾客 ID
  TIME_ID DATE,       -- 时间 ID
  CHANNEL_ID CHAR(1), -- 渠道 ID
  PROMO_ID NUMBER(6),
  QUANTITY_SOLD NUMBER (3),
  AMOUNT_SOLD NUMBER (10, 2)
) PARTITION BY LIST (CHANNEL_ID) (
  PARTITION EVEN_CHANNELS VALUES (2, 4),
  PARTITION ODD_CHANNELS VALUES (3, 9)
);

基于哈希的分区表

CREATE TABLE HASH_SALES (
  PROD_ID NUMBER(6), -- 产品 ID
  CUST_ID NUMBER,     -- 顾客 ID
  TIME_ID DATE,       -- 时间 ID
  CHANNEL_ID CHAR(1), -- 渠道 ID
  PROMO_ID NUMBER(6),
  QUANTITY_SOLD NUMBER (3),
  AMOUNT_SOLD NUMBER (10, 2)
) PARTITION BY HASH (PROD_ID) PARTITIONS 2;

2.3.3 分区索引

分区表和不分区表建索引也是不同的。分区表的索引包含全局索引 (Global Indexes) 和局部索引 (Local Indexes)。局部索引根据局部索引策略的不同又分成了 Local Prefixed Indexes 和 Local NonPrefixed Indexes 两类

对于索引有以下几个注意点:

  • 分区的列必须是主机列的一个子集
  • 第二分区索引可以进行局部分区或者全局分区
  • 溢出的数据会被均分到各个分区中,而不会堆积在某一个单独的分区

创建分区表的索引方法如下

-- 创建分区表的局部索引
CREATE INDEX HASH_SALES_IDX
  ON HASH_SALES (TIME_ID) LOCAL;

-- 创建分区表的全局索引
CREATE INDEX TIME_CHANNEL_SALES_IDX
  ON TIME_RANGE_SALES (CHANNEL_ID) GLOBAL
  PARTITION BY RANGE (CHANNEL_ID) (
    PARTITION P1 VALUES LESS THAN (3),
    PARTITION P2 VALUES LESS THAN (4),
    PARTITION P3 VALUES LESS THAN (MAXVALUE)
  );

2.3.4 视图

视图 (View) 也称虚表, 不占用物理空间,这个也是相对概念,因为视图本身的定义 语句还是要存储在数据字典里的。视图只有逻辑定义。每次使用的时候,只是重新执行 SQL,视图的好处如下:

  1. 提供各种数据表现形式, 可以使用各种不同的方式将基表的数据展现在用户面前, 以便符合用户的使用习惯 (主要手段: 使用别名);
  2. 隐藏数据的逻辑复杂性并简化查询语句, 多表查询语句一般是比较复杂的, 而且 用户需要了解表之间的关系, 否则容易写错; 如果基于这样的查询语句创建一个视 图, 用户就可以直接对这个视图进行"简单查询"而获得结果. 这样就隐藏了数据的 复杂
  3. 执行某些必须使用视图的查询. 某些查询必须借助视图的帮助才能完成. 比如, 有 些查询需要连接一个分组统计后的表和另一表, 这时就可以先基于分组统计的结果 创建一个视图, 然后在查询中连接这个视图和另一个表就可以了
  4. 提供某些安全性保证. 视图提供了一种可以控制的方式, 即可以让不同的用户看见 不同的列, 而不允许访问那些敏感的列, 这样就可以保证敏感数据不被用户看见;
  5. 简化用户权限的管理. 可以将视图的权限授予用户, 而不必将基表中某些列的权限 授予用户, 这样就简化了用户权限的定义。

2.3.5 创建视图

简单的创建视图可以暴露源表的某些字段

CREATE VIEW STAFF AS
SELECT
  EMPLOYEE_ID,
  LAST_NAME,
  JOB_ID
FROM
  EMPLOYEES;

视图可以封装做一些对源表的数据处理

CREATE VIEW STAFF_DEPT_10 AS
SELECT
  EMPLOYEE_ID,
  LAST_NAME,
  JOB_ID
FROM
  EMPLOYEES
WHERE
  DEPARTMENT_ID = 10 WITH CHECK OPTION CONSTRAINT STAFF_DEPT_10_CNST;

合并多个表,给用户暴露一个单一的表

CREATE VIEW STAFF_DEPT_10_30 AS
SELECT
  E.EMPLOYEE_ID,
  E.LAST_NAME,
  E.JOB_ID,
  D.DEPARTMENT_NAME
FROM
  EMPLOYEE E,
  DEPARTMENT D
WHERE
  E.DEPARTMENT_ID IN (10, 30)
  AND E.DEPARTMENT_ID = D.DEPARTMENT_ID;

2.3.6 物化视图

物化视图 (Materialized View) 将视图选取的数据进行存储,目的是减少构建视图时 的计算时间,用空间换时间

CREATE MATERIALIZED VIEW SALES_MV AS
SELECT
  T.CALENDAR_YEAR,
  P.PROD_ID,
  SUM(S.AMOUNT_SOLD) AS SUM_SALES
FROM
  TIMES T,
  PRODUCTS P,
  SALES S
WHERE
  T.TIME_ID = S.TIME_ID
  AND P.PROD_ID = S.PROD_ID
GROUP BY
  T.CALENDAR_YEAR,
  P.PROD_ID;

2.3.7 序列

序列可以生成连续的数,主要用于解决数据库并发访问时 ID 值生成策略

-- 创建一个序列
CREATE SEQUENCE CUST_SEQ
  START WITH 1000
  INCREMENT BY 1 NOCACHE NOCYCLE;
-- 使用方法
SELECT CUST_SEQ.NEXTVAL FROM DUAL;
-- 查看当前序列值
SELECT CUST_SEQ.CURRVAL FROM DUAL;

2.3.8 同义词

同义词定义了数据库结构的别名,可以方便访问。同义词分为 public 和 private 的 两种, public 对所有用户公开, private 针对特定用户

CREATE PUBLIC SYNONYM PEOPLE HR.EMPLOYEES;

2.4 数据完整性

数据完整性 (Data Integrity) 保证存储在数据库中的所有数据值均正确的状态

2.4.1 非空约束

非空 (NOT NULL) 约束限制数据库表列的值不能为 NULL,那么当插入数据时,必须为 列提供,数据不能为 NULL。约束只能在列级定义,不能在表级定义。

建表时指定在对应列后面添加 not null 指定该列为非空

CREATE TABLE EMPLOYEES (
  ID NUMBER(16) PRIMARY KEY,
  CREATED_BY VARCHAR2(64) DEFAULT 'SYSMAN' NOT NULL, -- 指定当前列为非空
  NAME VARCHAR2(32)
);

-- 给非空约束添加名称
CREATE TABLE EMPLOYEES (
  ID NUMBER(16) PRIMARY KEY,
  CREATED_BY VARCHAR2(64) DEFAULT 'SYSMAN',
  NAME VARCHAR2(32),
  CONSTRAINT EMP_CREATED_BY_UK(CREATED_BY) NOT NULL; -- 指定当前列为非空
);

修改列的非空属性

-- 添加非空约束
ALTER TABLE TABNAME MODIFY COLNAME [CONSTRAINT CONSTRAINT_NAME] NOT NULL;
-- 删除非空约束
ALTER TABLE TABNAME MODIFY COLNAME NULL;

2.4.2 唯一性约束

唯一性 (Unique) 约束表示该列值是不能重复的,但是可以为 NULL

CREATE TABLE EMPLOYEES (
  EMAIL VARCHAR2 (25),
  CONSTRAINT EMP_EMAIL_NN NOT NULL,      -- 非空
  CONSTRAINT EMP_EMAIL_UK UNIQUE (EMAIL) -- 唯一键
);

-- 添加唯一键
ALTER TABLE TABNAME ADD CONSTRAINT EMP_EMAIL_UK UNIQUE (EMAIL);

2.4.3 主键约束

用于唯一的标识表行的数据,当定义主键约束后,该列不但不能重复而且不能为 NULL。 一张表最多只能有一个主键,但是可以由多个 Unique 约束。创建主键或唯一约束后, Oracle 会自动创建一个与约束同名的索引 (Uniquenes 为 Unique 唯一索引) 需要注 意的是: 每个表只能有且有一个主键约束

CREATE TABLE EMPLOYEES (
  ID NUMBER (16) PRIMARY KEY, -- 主键约束
  NAME VARCHAR2 (32)
);

CREATE TABLE EMPLOYEES (
  ID NUMBER (16),
  NAME VARCHAR2 (32),
  CONSTRAINT EMPLOYEE_PK PRIMARY KEY (ID) -- 主键约束
);

2.4.4 外键约束

用于定义主表和从表之间的关系,外键约束要定义在从表上,主要则必须具有主键约束 或是 Unique 约束,当定义外键约束后,要求外键列数据必须在主表的主键列存在或是 为 NULL,即引用完整性 (Referential Integrity)。如果外键作用于当前表本身的列, 则该约束为自引用完整性 (Self-Referential Integrity)

用来维护从表 (Child Table) 和主表 (Parent Table) 之间的引用完整性。外键约束 是个有争议性的约束,它一方面能够维护数据库的数据一致性,数据的完整性。防止错 误的垃圾数据入库; 另外一方面它会增加表插入、更新等 SQL 性能的额外开销,不少 系统里面通过业务逻辑控制来取消外键约束。例如在数据仓库中,就推荐禁用外键约束。

修改外键时的从表行为有以下三种:

  • No Action on Deletion and Update, 默认为删除和更新是不操作
  • Cascading Deletion, 级联删除
  • Deletion that Set NULL, 删除是置空

外键插入、更新或删除形容如下

DML 主表 从表
insert 要求主表键唯一 要求外键值在从表中存在,或者包含多行,或者为 NULL
update no action 如果从表中没有没有引用主表的的行 如果更新后新的外键仍然引用旧的外键
delete no action 如果子表中没有引用主表的行 任何情况都可行
delete cascade 任何情况都可行 任何情况都可行
delete set null 任何情况都可行 任何情况都可行
CREATE TABLE DEPARTMENTS (
  ID NUMBER (16) PRIMARY KEY
);

CREATE TABLE EMPLOYEES (
  ID NUMBER (16),
  DEPT_ID NUMBER (16),
  -- 添加外键
  CONSTRAINT EMP_DEPT_FK FOREIGN KEY (DEPT_ID) REFERENCES DEPARTMENTS (ID)
);

-- 修改表,添加外键
ALTER TABLE EMPLOYEES
  ADD CONSTRAINT EMP_DEPT_FK FOREIGN KEY (DEPT_ID) REFERENCES DEPARTMENTS (ID);

2.4.5 条件约束

条件约束 (check constraint) 用于强制行数据必须满足的条件

ALTER TABLE EMPLOYEES ADD CONSTRAINT MAX_EMP_SAL_CK CHECK(SALARY < 10001);

2.4.6 约束命名规范

约束名称建议自己定义一套命名规则,否则使用系统生成的约束名,很难能把它和对 应的表、字段联系起来。

类型 命名
非空约束 tabname_colname_nn
唯一约束 tabname_colname_uk
主键约束 tabname_pk
外键约束 tabname_colname_fk
条件约束 tabname_colname_ck
默认约束 tabname_colname_df

如果约束名称超过 32 位长度,建议应该缩写表名,而不应用 tabname_colname_nn 不过具体视情况而定

2.5 数据字典和动态性能视图

2.5.1 数据字典

Oracle 的数据库组织方式中所有数据库的表信息,类信息等元信息也是存放在一个对 用户不可见的数据表中,这里称之为元表。Oracle 定义了一系列示例图来表示数据字 典,大体上有如下几种

前缀 用户可见性 内容
dba_* 数据库管理员 所有的对象
all_* 所有用户 当前用户可访问的对象
user_* 所有用户 当前用户的对象

例如: user_tab_comments 表中存放着用户表的注释, dba_tab_comments 表中 则存放在管理员的所有表注释。

2.5.2 数据字典的使用

表列属性,可以查看表中每列的名字,数据类型,长度等信息

SELECT C.TABLE_NAME, C.COLUMN_NAME, C.DATA_TYPE, C.DATA_LENGTH, C.NULLABLE
  FROM USER_TAB_COLUMNS C ORDER BY C.TABLE_NAME;

唯一性约束,获取列的约束名称

SELECT C.OWNER, C.CONSTRAINT_NAME, C.TABLE_NAME, C.COLUMN_NAME, C.POSITION
  FROM USER_CONS_COLUMNS C ORDER BY C.TABLE_NAME;

获取列的约束类型

SELECT C.CONSTRAINT_NAME, C.CONSTRAINT_TYPE, C.STATUS
  FROM USER_CONSTRAINTS C
 WHERE UPPER(C.CONSTRAINT_TYPE) IN ('U', 'P')
 ORDER BY C.CONSTRAINT_NAME;

获取数据库中的一些注释信息

-- 获取所有表注释
SELECT C.TABLE_NAME, C.TABLE_TYPE, C.COMMENTS
  FROM USER_TAB_COMMENTS C ORDER BY C.TABLE_NAME;
-- 获取所有表的对应列的注释
SELECT C.TABLE_NAME, C.COLUMN_NAME, C.COMMENTS
  FROM USER_COL_COMMENTS C ORDER BY C.TABLE_NAME;

2.5.3 动态性能视图

Oracle 提供了一下可以查看当前数据库性能的视图,这些视图的统一前缀为 v$*

  • v$instance 实例视图
  • v$bgprocess 后台进程视图
  • v$sql SQL 执行记录视图
  • v$datafile Data files 的视图

查看所有系统性能视图名称

SELECT D.TABLE_NAME FROM DICTIONARY D
 WHERE UPPER(D.TABLE_NAME) LIKE 'V$%' ORDER BY D.TABLE_NAME;

查看数据库对象

-- 所有 DBA_ 开头的数据库对象信息
SELECT O.OWNER, O.OBJECT_NAME, O.OBJECT_TYPE
  FROM DBA_OBJECTS O ORDER BY O.OWNER, O.OBJECT_NAME;
-- 所有 ALL_ 开头的数据库对象信息
SELECT O.OWNER, O.OBJECT_NAME, O.OBJECT_TYPE
  FROM ALL_OBJECTS O ORDER BY O.OWNER, O.OBJECT_NAME;

-- 所有 USER_ 开头的数据库对象信息SELECT O.OBJECT_NAME, O.OBJECT_TYPE FROM
USER_OBJECTS O ORDER BY O.OBJECT_NAME;

所有用户对象的多少和用户的角色相关,见如下的例子

SQL> set role all;
Role set.
SQL> select count(1) from all_objects;
  COUNT(1)
----------
     18474
SQL> set role none;
Role set.
SQL> select count(1) from all_objects;
  COUNT(1)
----------
     15267

3 数据存取

3.1 SQL 语句的分类

  1. DDL (Data Definition Language) 是数据定义语言
  2. DML (Data Manipulation Language) 是数据操作语言
  3. TCS (Transaction Control Statements) 是事务控制语句

3.2 DDL 语句

DDL 语句包括如下几类:

  • 创建,修改和删除数据库对象的语句,大多数以 create, alterdrop 开头
  • 删除数据库对象中的所有的数据的语句, truncate
  • 授权和取消权限的语句, grant, revoke
  • 修改审计相关选项, audit, noaudit
  • 添加注释到数据字典的语句, comment

执行 DDL 语句相当于隐式提交事务, 常见的 DDL 语句如下:

CREATE TABLE PLANTS (
  PLANT_ID NUMBER PRIMARY KEY,
  COMMON_NAME VARCHAR2(15)
);

ALTER TABLE PLANTS ADD (LATIN_NAME VARCHAR2(40));
GRANT SELECT ON PLANTS TO SCOTT;
REVOKE SELECT ON PLANTS FROM SCOTT;
DROP TABLE PLANTS;

3.3 DML 语句

DML 语句操作数据库已经存在的数据,主要有以下几类:

  • 获取数据库表和视图中的数据 select
  • 将数据或子查询的数据插入表 insert
  • 修改表的数据值 update
  • 更新或插入列到表或视图中 merge
  • 删除表或视图中的列 delete
  • 查看执行计划 explain plan
  • 锁表或锁视图 lock table
SELECT * FROM EMPLOYEES;
INSERT INTO
  EMPLOYEES (EMPLOYEE_ID, LAST_NAME, EMAIL, JOB_ID, HIRE_DATE, SALARY)
  VALUES (1234, 'MASCIS', 'JMASCIS', 'IT_PROG', '14-FEB-2011', 9000);
UPDATE EMPLOYEES SET SALARY=9100 WHERE EMPLOYEE_ID=1234;
DELETE FROM EMPLOYEES WHERE EMPLOYEE_ID=1234;

3.3.1 查询语句

Select 查询语句是最常见的 DML,并且大多数和数据库打交道的工作就是查数据,下面 是一个查询的例子

SELECT EMAIL,
       DEPARTMENT_NAME
  FROM EMPLOYEES
  JOIN DEPARTMENTS
    ON EMPLOYEES.DEPARTMENT_ID = DEPARTMENTS.DEPARTMENT_ID
 WHERE EMPLOYEE_ID IN (100,103)
 ORDER BY EMAIL;

Join 类型有以下几种:

  • inner join 内连接,默认连接方法,只有满足条件的才返回
  • outer join 外链接,外链接包括 left outer joinright outer join , 外链接将一段的数据全量返回,如果不满足条件的补 NULL
  • 笛卡尔积,对待处理表的所有排列,即笛卡尔积进行返回
-- 左连接,如果有些员工没有部门 ID 对应的部门名称返回 NULL
SELECT EMAIL,
       DEPARTMENT_NAME
  FROM EMPLOYEES
  LEFT JOIN DEPARTMENTS
    ON EMPLOYEES.DEPARTMENT_ID = DEPARTMENTS.DEPARTMENT_ID
 WHERE EMPLOYEE_ID in (100,103)
 ORDER BY EMAIL;

-- 笛卡尔积,返回所有员工和部门的排列
SELECT EMPLOYEE_NAME,
       DEPARTMENT_NAME
  FROM EMPLOYEES,
       DEPARTMENTS
 ORDER BY EMPLOYEE_NAME,
          DEPARTMENT_NAME;

3.3.2 子查询和隐式查询

如果一个查询包含在另外一个查询语句中,则该语句被称为子查询 (subquery)

SELECT FIRST_NAME,
       LAST_NAME
  FROM EMPLOYEES
 WHERE DEPARTMENT_ID IN (
        SELECT DEPARTMENT_ID -- 子查询
          FROM DEPARTMENTS
         WHERE LOCATION_ID = 1800
       );

隐式查询指一些语句没有使用 select 关键字,但实际上执行了查询结果集

UPDATE EMPLOYEES SET SALARY = SALARY * 1.1 WHERE LAST_NAME = 'Baer';

3.4 TCS 语句

set transaction name 'Update salaries';
savepoint before_salary_update;

update employees set salary = 9100 where employee_id = 1234; -- DML
-- 这里回退了,上一句 DML 实际不会被提交
rollback to savepoint before_salary_update;

-- 这里提交了,上一句 DML 才会生效
update employees set salary = 9200 where employee_id = 1234; -- DML
commit comment 'Updated salaries';

3.5 SQL 优化与解析

3.5.1 优化器

Oracle 在执行 DML 语句时,不是直接将语句解析出来执行,而是生成相应的执行计划 (Execution Plan),根据生成的执行计划后,优化器就可以获取语句涉及到的执行信息, 包括 query 条件,对应的存取路径 (Access Path),优化器最后生成一个高效的查询 计划 (Query Plan),最后执行查询

oracle-optimizer-components.png

下面是一个查看语句执行计划的示例

SQL> set autotrace on
SQL> select count(1) from t_department;

  COUNT(1)
----------
        26

Execution Plan
----------------------------------------------------------
Plan hash value: 3597430146

----------------------------------------------------------------------------
| Id  | Operation        | Name            | Rows  | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT |                 |     1 |     1   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE  |                 |     1 |            |          |
|   2 |   INDEX FULL SCAN| T_DEPARTMENT_PK |    24 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          1  consistent gets
          0  physical reads
          0  redo size
        526  bytes sent via SQL*Net to client
        524  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed
autotrace Note
on Display both explain and statistics
on explain Display explain only
on statistics Display statistics only
traceonly Disable print result set
off Disable auto trace

3.5.2 SQL 处理机制

DDL 语句用来创建数据库中的对象, DML 语句用来获取数据库中的数据。这些 SQL 语句在执行前都要进行处理,处理时,Oracle 创建一个 parse call 来准备执行,这 个 parse call 打开或创建一个 cursor,cursor 的作用是处理特定会话的私有 SQL 区域,以上的处理都是需要消耗 PGA。解析流程如下图所示

oracle-sql-processing.png

根据上面的图,解析大体上包含下面几个步骤:

  • 词法检查
  • 语法检查
  • Shared Pool 检查

oracle-shared-pool-check.png

已经执行过的 SQL 语句会放到 Library Cache 中缓存。在解析 SQL 语句前,Oracle 会通过哈希值在 Library Cache 中查找缓存的 SQL 语句,如果没有命中,则执行硬解 析,否则执行软解析

查看 SQL 的哈希值就是, v$sql 视图中的 sql_id, 下面是一个查看哈希值的例子

SQL> select sql_id, sql_text from v$sql where sql_text like '%hashtag%';

SQL_ID        SQL_TEXT
------------- ----------------------------------------------------------------
d6a6j07p0jfu3 select sql_id, sql_text from v$sql where sql_text like '%hashtag%'

SQL> select /* hashtag */ sysdate from dual;

SYSDATE
-------------------
2020-02-25 03:42:29

SQL> select sql_id, sql_text from v$sql where sql_text like '%hashtag%';

SQL_ID        SQL_TEXT
------------- -----------------------------------------------------------------
d6a6j07p0jfu3 select sql_id, sql_text from v$sql where sql_text like '%hashtag%'
gadbmhc20xhx8 select /* hashtag */ sysdate from dual
-- 这里发生了硬解析

SQL> select /* hashtag */ sysdate from dual;

SYSDATE
-------------------
2020-02-25 03:51:54

SQL> select sql_id, sql_text from v$sql where sql_text like '%hashtag%';

SQL_ID        SQL_TEXT
------------- -----------------------------------------------------------------
d6a6j07p0jfu3 select sql_id, sql_text from v$sql where sql_text like '%hashtag%'
gadbmhc20xhx8 select /* hashtag */ sysdate from dual
-- library cache 命中,这里发生了软解析

SQL> select /* hashtag new */ sysdate from dual;

SYSDATE
-------------------
2020-02-25 04:00:29

SQL> select sql_id, sql_text from v$sql where sql_text like '%hashtag%';

SQL_ID        SQL_TEXT
------------- -----------------------------------------------------------------
d3k0qy7gmnz75 select /* hashtag new */ sysdate from dual
d6a6j07p0jfu3 select sql_id, sql_text from v$sql where sql_text like '%hashtag%'
gadbmhc20xhx8 select /* hashtag */ sysdate from dual
-- 注意,SQL 的哈希值是根据字符串计算的
-- 如果 SQL 的语义一样,但字符串不一样导致哈希值不一样也会发生硬解析

3.6 PL/SQL

PL/SQL 是直接存在 Oracle 数据库内存中的,减少了获取和解析 SQL 计算的资源消耗, 可以有效地提高执行效率

3.6.1 变量

变量类型

  • 普通变量(char, varchar2, date, number, boolean, long)
  • 特殊变量(引用型变量,记录型变量)
varname varchar2(20);

变量赋值

  • 直接赋值 :=
  • 语句赋值,使用 select ... into ...
declare
  v_name varchar(20) := ''; -- 初始化赋值
  v_sal number;
  v_addr varchar(200);
begin
  -- 直接赋值
  v_sal := 2383;

  -- 语句赋值
  select '北京市朝阳区' into v_addr from dual;

  dbms_output.put_line('姓名: ' || v_name || ', 薪水: ' || v_sal || ', 地址 c: ' || v_addr);
end;

3.6.2 子程序 Subprogram

子程序包括: procedure 和 function 两类,其中 function 有返回值, procedure 没 有返回值

create procedure hire_employees (
  p_last_name varchar2,
  p_job_id varchar2,
  p_manager_id number,
  p_hire_date date,
  p_salary number,
  p_commission_pct number,
  p_department_id number
) is
begin
  -- ...
  insert into employees
      (employee_id, last_name, job_id, manager_id,
          hire_date, salary, commission_pct, department_id)
  values (emp_sequence.nextval, p_last_name, p_job_id,
          p_manager_id, p_hire_date, p_salary, p_commission_pct, p_department_id);
  -- ...
end;

执行子程序的方法如下:

execute hire_employees ('TSMITH', 'CLERK', 1037, sysdate, 500, null, 20);

3.6.3 包 Package

PL/SQL 的包和 Java 的很相似,提供封装性

create package employees_management as
  function hire_employees
      (last_name varchar2, job_id varchar2, manager_id number,
         salary number, commission_pct number, department_id number)
             return number; -- 定义函数
  procedure fire_employees(employee_id number); -- 定义过程
  procedure salary_raise(employee_id number, salary_incr number);
  -- ...
  no_sal exception;
end employees_management; -- 包尾的名称需要和前面一致

调用包中函数的方法

execute employees_management.hire_employees
    ('tsmith', 'clerk', 1037, sysdate, 500, null, 20);

3.6.4 匿名块 Anonymous Block

匿名块是没有名字的过程,使用 / 来执行

declare
  v_lname varchar2(25);
begin
  select last_name into v_lname
      from employees where employee_id = 101;
  -- sqlplus 中默认输出是关闭的,可以使用下面方法开启选项
  -- set serveroutput on
  dbms_output.put_line('Employee last name is ' || v_lname);
end;

3.7 触发器 Trigger

触发器定义了 Oracle 表操作的一些自动使用的行为,和 Hook 机制很像

3.7.1 创建触发器

create trigger trigger_name
  triggering_statement
  [trigger_restriction]
begin
 triggered_action;
end;

3.7.2 触发器使用

创建示例表

create table orders (
  order_id number primary key,
  /* other attributes */
  line_items_count number default 0
);

create table lineitems (
  order_id references orders,
  seq_no number,
  /* other attributes */
  constraint lineitems primary key(order_id, seq_no)
);

创建一个触发器,自动适配上述两张表对应操作的行为

create or replace trigger lineitems_trigger
  after insert or update or delete on lineitems
  for each row
begin
  if (inserting or updating)
  then
    update orders set line_items_count = nvl(line_items_count,0)+1
    where order_id = :new.order_id;
  end if;
  if (deleting or updating)
  then
    update orders set line_items_count = nvl(line_items_count,0)-1
    where order_id = :old.order_id;
  end if;
end;
/

3.8 Java

Oracle 支持服务器端使用 Java 编程的存储过程 (Java stored procedure),但是目前 很少使用,因为与 Oracle 数据库紧耦合,一般还是使用 JDBC 连接数据库来操作, JDBC 主要分以下几种连接方式:

  • OCI (Oracle Call Interface) 包含了 Oracle 内置的代码,Java 代码等一般不能直 接使用 Java 连
  • Thin 纯 Java 实现的数据库连接库

4 事务管理

4.1 事务隔离级别

对于并发读数据会出现以下 3 种情况

  1. Dirty Read 事务 Tx1 更新了一行记录,还未提交所做的修改,这个 Tx2 读取了 更新后的数据,然后 Tx1 执行回滚操作,取消刚才的修改,所以 Tx2 所读取的行就 无效,也就是脏数据
  2. Nonrepeatable Read 事务 Tx1 读取一行记录,紧接着事务 Tx2 修改了 Tx1 刚刚 读取的记录并 commit,然后 Tx1 再次查询,发现与第一次读取的记录不同,这称为 不可重复读
  3. Phantom Read 事务 Tx1 读取一条指定 where 条件的语句,返回结果集。此时事 务 Tx2 插入一行新记录并 commit,恰好满足 Tx1 的 where 条件。然后 Tx1 使用 相同的条件再次查询,结果集中可以看到 Tx2 插入的记录,这条新纪录就是幻想

标准 SQL 针对上述读问题设计 4 中隔离级别:

  Dirty Read Nonrepeatable Read Phantom Read
Read Uncommitted Possible Possible Possible
Read Committed Not Possible Possible Possible
Repeatable Read Not Possible Not Possible Possible
Serializable Not Possible Not Possible Not Possible

Oracle 事务包含以下三个隔离级别

  1. Read Committed 是 Oracle 默认的事务隔离级别,事务内只能读到事务开始时间 点的数据 ,事务执行过程中改变的数据对当前事务不可见
  2. Serializable 事务内不仅读到事务开始时间点的数据,事务执行中的修改也能读 到,所以这种事务隔离级别可以读到最新数据,相当于串行操作,所以并发性不是很 好,该类事务常用于以下几种场合
    • 数据规模大但事务时间短并且更新行数少
    • 两个事务竞争方式修改的行,并且这些行是不相关的
    • 执行时间比较长,但是是只读的事务
  3. Read-Only 只读级别的事务

事务的隔离级别可以使用以下的 SQL 来设置

set transaction isolation level read committed;   -- 提交可读
set transaction isolation level serializable;     -- Serializable 级别
set transaction isolation level read uncommitted; -- 标准 SQL 支持,Oracle 不支持

4.2 数据库锁机制

数据库锁是模式包含两种: 共享 (Share) 和 互斥 (Exclusive)。如果两个用户在 同时等待的资源同时被对方锁住了就会出现 死锁 (Deak lock)

4.2.1 DML 锁

DML 锁在 Oracle 中被称为数据所,主要分成以下两类

  1. TX lock 即行锁 (Row Lock) 在 Oracle 表中锁住表的一行
  2. TM lock 即表锁 (Table Lock) 在 Oracle 中对表操作进行锁
    • RS (Row Share)
    • RX (Row Exclusive Table)
    • S (Share Table)
    • SRX (Share Row Exclusive Table)
    • X (Exclusive Table)

TM 锁可以的锁模式见下表

Share / eXclusive Table Row
RS   S
RX   X
S S  
SRX X S
X X  
-- 请求 TX 的语句
select /* ... */ for update;
insert /* ... */ ;
update /* ... */ ;
delete /* ... */ ;
merge /* ...*/ ;
-- 请求 TM 的语句
lock table /* ... */ ;
select /* ... */ for update;
insert /* ... */ ;
update /* ... */ ;
delete /* ... */ ;
merge /* ... */ ;

4.2.2 DDL 锁

DDL 锁主要是保护数据字典,用户一般不会直接获取 DDL 锁,但是在创建数据库对象 是会隐式获取改锁,例如:创建存储过程是会获取 DDL 锁

4.2.3 系统锁

  • Latch 是 Oracle 数据库中的低级别的系统锁,用来保护数据库对象,数据结构, 对象和文件等。数据库内部的后台进程,共享池,写进程都是使用 Latch 来完成同 步的。
  • Mutex 是另一个低级别的系统锁,与 Latch 相似,不同之处是 Latch 通常保护一 组数据库对象,而 Mutex 保护单个数据库对象
  • Internal Locks 系统还有其它的锁
    • Dictionary cache locks
    • File and log management locks
    • Tablespace and undo segment locks

4.3 事务概述

在数据库中事务是工作的逻辑单元,一个事务是由一个或多个完成一组的相关行为的 SQL 语句组成,通过事务机制确保这一组 SQL 语句所作的操作要么完全成功执行,完成 整个工作单元操作,要么一点也不执行。

事务的四个特性 ACID 见如下描述

  1. 原子性 (Atomicity) 事务中 SQL 语句不可分割,要么都做,要么都不做
  2. 一致性 (Consistency) 指事务操作前后,数据库中数据是一致的,数据满足业务 规则约束 (例如账户金额的转出和转入),与原子性对应。
  3. 隔离性 (Isolation) 多个并发事务可以独立运行,而不能相互干扰,一个事务修 改数据未提交前,其他事务看不到它所做的更改。
  4. 持久性 (Durability) 事务提交后,数据的修改是永久的

4.4 事务开始和结束

事务开始 在 Oracle 数据库中,没有提供开始事务处理语句,所有的事务都是隐式开 始的,也就是说在 Oracle 中,用户不可以显示使用命令来开始一个事务。Oracle 任务 第一条修改数据库的语句, 或者一些要求事务处理的场合都是事务的隐式开始。

事务结束 主要发生在以下几种情况下:

  1. 执行 DDL 语句时,系统自动执行 commit 语句,相当于隐式提交事务
  2. 显示执行 commitrollback
  3. 退出/断开数据库的连接自动执行 commit 语句
  4. 进程意外终止,事务自动 rollback
  5. 事务 commit 时会生成一个唯一的系统变化号保存到事务表,这个系统变化号被称 为 SCN (System Check Number) 号

开始的事务都可以在 v$transaction 视图中看到

SQL> select xid from v$transaction;

XID
----------------
02001700A8010000

4.5 事务属性和约束模式

  • 事务属性 主要包括:
    1. 指定事务的隔离级别,默认为 read committed
    2. 规定回滚事务所使用的存储空间,存储空间的设置很少使用
    3. 命名事务,即事务的名称。对于命名事务也非常简单,只有在分布式事务处理中才 会体现出命名事务的用途
  • 约束模式 在事务中修改数据时,数据库中的约束立即应用于数据,还是将约束推迟 到当前事务结束后应用。
-- 设置事物属性 只对当前事务有效,事务终止,事务当前的设置将会失效。
set transaction <options>;
-- 设置事物的约束模式
set constrains <options>;

-- 在事务中建立一个存储的点.当事务处理发生异常而回滚事务时,
-- 可指定事务回滚到某存储点.然后从该存储点重新执行。
savepoint spname;
-- 删除存储点
release savepoint spname;

-- 回滚事务
rollback;
rollback to savepoint spname;
-- 提交事务
commit;

5 数据库底层结构

5.1 物理存储结构

在使用 create database 建立数据库后,数据库会创建数据文件、控制文件和重做日 志这三个文件。在实际生产环境,我们一般使用 dbca 图形化工具来创建数据库,而 不直接使用 SQL 语句来创建

oracle-logical-data-structure.png

5.1.1 数据文件 Data Files

Oracle 数据库中的数据文件包含永久数据文件和临时数据文件

  1. 数据库中的永久存储对象,例如表,不会放在临时数据文件中
  2. 临时数据文件没有 NOLOGGING 模式
  3. 临时数据文件不再设置成只读模式
  4. 临时数据文件信息存放在 dba_temp_filesv$tempfile 视图中,而永久数 据文件信息存放在 dba_data_filesv$datafile 视图中

    -- 查看临时数据文件
    SELECT T.CREATION_TIME AS "Creation Time",
           ROUND(T.BYTES / 1024 / 1024, 2) AS "Size (M)",
           SUBSTR(T.NAME, 1, 60) AS "File Name"
      FROM V$TEMPFILE T
     ORDER BY T.NAME;
    
    -- 查看永久数据文件
    SELECT T.CREATION_TIME AS "Creation Time",
           ROUND(T.BYTES / 1024 / 1024, 2) AS "Size (M)",
           SUBSTR(T.NAME, 1, 60) AS "File Name"
      FROM V$DATAFILE T
     ORDER BY T.NAME;
    
  5. 数据文件的状态分为 ONLINE 和 OFFLINE,备份文件、重命名文件等操作会使得数 据文件离线

    -- 查看离线的数据文件
    SELECT T.CREATION_TIME AS "Creation Time",
           ROUND(T.BYTES / 1024 / 1024, 2) AS "Size (M)",
           SUBSTR(T.NAME, 1, 60) AS "File Name"
      FROM V$TEMPFILE T
     WHERE LOWER(T.STATUS) = 'offline'
     UNION SELECT T.CREATION_TIME AS "Creation Time",
           ROUND(T.BYTES / 1024 / 1024, 2) AS "Size (M)",
           SUBSTR(T.NAME, 1, 60) AS "File Name"
      FROM V$DATAFILE T
     WHERE LOWER(T.STATUS) = 'offline';
    

5.1.2 控制文件 Control Files

控制文件是一个很小的二进制记录文件,数据库启动是通过控制文件查找数据库的数据 文件的位置,大小以及其他的信息。控制文件中包含数据关闭或宕机时的 SCN 号,数 据库进行实例恢复是需要控制文件提供必要的信息。

5.1.3 重做日志 Redo Log Files

重做日志记录着数据库的操作,如果数据库发送宕机,可以通过控制文件和重做日志将 数据从一个比较老的检查点 (Checkpoint) 上恢复回来。

重做日志包含:在线重做日志和归档重做日志。重做日志是 循环 使用的,过了检查 点的操作日志会被重用。归档重做日志和数据的 ARCHIVELOG 模式有关系,开启归档 模式的话,数据库会按照一定规则生成归档重做日志,用于备份和恢复。

重做日志记录了以下信息

  • SCN 号,时间戳
  • 事务 ID,记录重做日志完成的事务
  • SCN 时间点,事务 ID 对应的事务是否提交的信息
  • 修改的类型
  • 修改段的名称和类型

5.2 逻辑存储结构

数据库的逻辑存储结构和物理储存有点区别,具体见下图

oracle-physical-data-structure.png

5.2.1 块 Block

块是数据库中最小的数据管理单元,可以通过下面启动参数指定大小

show parameter db_block_size;

注意:数据库块大小一但启动后不要轻易修改,11g 中默认的块大小为 8K。在创建新 的表空间是可以通过参数指定表空间的块大小,一旦表空间创建,块大小不要修改

数据库表的数据是存在块上的,通过列的相对位置来寻找到 Row Data,然后在 Row Data 中查找 Column Data

  • Oracle 会将 LONG 类型的数据放在 Row Data 的最后面。
  • Oracle 的 rowid 伪列满足下面的结构

    SQL> select rowid from employees where employee_id = 100;
    
    ROWID
    ------------------
    AAAPecAAFAAAABSAAA
    

    oracle-rowid-format.png

Oracle 运行手工管理表分配块的大小,一旦指定 pctfree 参数,Oracle 在创建块 是要是的为分配的块不低于 pctfree 的值

-- Percentage of Free Space in Data Blocks
create table test_table (n number) pctfree 20;

5.2.2 区 Extent

区是数据库管理数据的一个单位,主要记住一个区只能放在一个 Datafile 中,而段是 可以跨 Datafile 的,所以段在扩展时,新扩展的分区可能会放在不同的 Datafile 中。

区的管理可以是手动的,也可以是自动的。如果需要统一管理在创建表空间是指定扩展 区大小为 1M

5.2.3 段 Segment

Oracle 的段一般对应与一张表

oracle-1-table-1-segment.png

对于包含主键、索引和 Lob 字段的表,可能会包含多个段

oracle-1-table-many-segments.png

段主要有以下几个点:

  1. Oracle 11g 使用延迟段创建 (Deferred Segment Creation) 的策略,该策略是数 据库在建表时不创建对象,当向表里插入数据是才开始创建对象
  2. Oracle 为临时表分配临时段
  3. Undo 段是采用循环回收机制
  4. 段存在高水位线 HWM (High water mark),高水位线会对全表扫描效率产生影响

5.2.4 表空间 Tablespace

表空间是管理数据库的逻辑单元

  • 一个表空间可以对应与多个数据文件
  • SYSTEM 和 SYSAUX 是系统的表空间,Oracle 启动时必须要有这两个表空间
  • Undo 表空间是循环管理的,对于循环空间的保鲜时间通过 undo_retention 启动 参数配置
  • Oracle 建立用户前必须先建立表空间,因为建立用户是一般会指定该用户的默认表 空间和默认临时表空间
-- 查看表空间和数据文件的对应关系
COLUMN "Tablespace" FORMAT A20;
COLUMN "Datafile" FORMAT A60;

SELECT A.TABLESPACE_NAME AS "Tablespace",
       A.FILE_NAME AS "Datafile"
  FROM DBA_DATA_FILES A
 ORDER BY A.TABLESPACE_NAME,
          A.FILE_NAME;
-- 看表空间剩余的容量
COLUMN "Tablespace" FORMAT A20;

SELECT A.TABLESPACE_NAME AS "Tablespace",
       SUM(BLOCKS) AS "Blocks",
       ROUND(SUM(BYTES) / 1024 / 1024, 2) AS "Free (M)"
  FROM DBA_FREE_SPACE A
 GROUP BY A.TABLESPACE_NAME
 ORDER BY A.TABLESPACE_NAME;
-- 查看表空间使用量
COLUMN "Tablespace" FORMAT A20;

SELECT SUBSTR(A.TABLESPACE_NAME, 1, 20) AS "Tablespace",
       ROUND(A.BYTES / 1024 / 1024, 2) AS "Size (M)",
       DECODE(B.USED_BYTES, NULL, 0, ROUND(B.USED_BYTES / 1024 / 1024, 2)) AS "Current (M)",
       DECODE(C.FREE_BYTES, NULL, 0, ROUND(C.FREE_BYTES / 1024 / 1024, 2)) AS "Free (M)",
       DECODE(B.USED_BYTES, NULL, 0, ROUND((B.USED_BYTES / A.BYTES) * 100, 2)) AS "Used (%)"
  FROM DBA_DATA_FILES A,
       (
        SELECT FILE_ID,
               SUM(BYTES) USED_BYTES
          FROM DBA_EXTENTS
         GROUP BY FILE_ID
       ) B,
       (
        SELECT SUM(BYTES) FREE_BYTES,
               FILE_ID
          FROM DBA_FREE_SPACE
         GROUP BY FILE_ID
       ) C
 WHERE B.FILE_ID(+) = A.FILE_ID
   AND C.FILE_ID(+) = A.FILE_ID
 ORDER BY A.TABLESPACE_NAME,
          A.FILE_NAME;
-- 查看表空间对应数据文件数量
COLUMN "Tablespace" FORMAT A20;
COLUMN "Datafile" FORMAT A60;

SELECT A.TABLESPACE_NAME AS "Tablespace",
       COUNT(*) AS "Count",
       ROUND(SUM(BYTES) / 1024 / 1024, 2) AS "Current (M)",
       ROUND(SUM(MAXBYTES) / 1024 / 1024, 2) AS "Maximum (M)",
       ROUND(SUM(BYTES) / SUM(MAXBYTES), 2) AS "Used (%)"
  FROM DBA_DATA_FILES A
 GROUP BY A.TABLESPACE_NAME
 ORDER BY A.TABLESPACE_NAME;
-- 创建默认表空间
CREATE TABLESPACE PFILE1
  DATAFILE '/oradata/xe/part1.dbf'
  SIZE 128M AUTOEXTEND OFF
  LOGGING ONLINE PERMANENT
  EXTENT MANAGEMENT LOCAL AUTOALLOCATE
  BLOCKSIZE 8K
  SEGMENT SPACE MANAGEMENT AUTO
  FLASHBACK ON;

-- 创建临时表空间
CREATE TEMPORARY TABLESPACE TFILE1
  TEMPFILE '/oradata/xe/temp1.dbf'
  SIZE 2048M AUTOEXTEND OFF
  TABLESPACE GROUP ''
  EXTENT MANAGEMENT LOCAL UNIFORM SIZE 1M;

5.3 数据库实例

Oracle 常见的实例包含单实例和 RAC 两种模式,他们的物理结构如下:

oracle-single-rac.png

实例有以下几个知识点:

  1. 实例是运行在内存中,它包含多种内存结构:SGA,PGA,Buffer Cache 等
  2. SID 是 Oracle 数据库实例在一个主机上的唯一标识
  3. 数据库启动需要读一个二进制的配置文件 Server Parameter File,一般称为 spfile, spfile 记录着数据库实例的启动参数,示例启动后 spfile 就没有 作用了。另外,Oracle 数据库还有一个文本的参数文件,一般称之为 pfile, pfile 可以和 spfile 相互转换。 修改数据库参数前要备份 spfile, 这样的 话如果参数修改错误可以替换文件来解决 Oracle 宕机无法启动的问题

    -- pfile 和 spfile 的转化
    create pfile from spfile;
    create spfile from pfile;
    
  4. 实例启动分为:Shutdown -> NoMount -> Mount -> Open 四个阶段

    -- 启动到 NoMount 阶段
    startup nomount;
    -- 直接启动到 Open 阶段
    startup;
    -- 在 NoMount 阶段将数据库开启到 Open 阶段
    ALTER DATABASE OPEN;
    
  5. 实例关闭有以下四种模式,一般使用 shutdown immediate

    -- 异常关闭,和断点是一样的
    shutdown abort;
    -- 立即关闭,不等待事务提交,不等待会话断开
    shutdown immediate;
    -- 事务性关闭,等待事务提交,不等待会话断开
    shutdown transactional;
    -- 正常关闭,等待事务提交,等待会话断开
    shutdown normal;
    
  6. 数据库异常关闭后,示例重启可能会出现 SCN 不一致,这时 Oracle 会更加记录的 Checkpoint 以及 Redo Log 等文件来 Roll Forward 或 Roll Back,确保 SCN 号同 步。这个过程叫 实例恢复
  7. 数据库初始化参数分 Static 和 Dynamic 两种。 db_block_sizedb_name 等是静态参数,不允许用户修改。动态参数根据作用域不同有分为如下三种:
    • scope=memory 正在运行的实例生效,重启后失效
    • scope=spfile 正在运行的实例不生效,重启后失效
    • scope=both 正在运行的实例生效,重启后也生效

5.4 内存、进程和网络

5.4.1 数据库内存

数据库内存包含 SGA 和 PGA 两个主要部分,所有 SGA 和 PGA 管理在 Oracle 数据库调优中 是至关重要的。可以通过下面参数查看 Oracle 内存参数值

-- 查看 sga 和 pga 相关参数
show parameter sga;
show parameter pga;

修改 SGA 和 PGA 参数的原则如下:一般物理内存 20% 用作操作系统保留,其他 80% 用于数据库,对于只作为数据库服务的机器可以加将 Oracle 的内存分配得更高。在 Oracle 数据库的内存确定后,SGA 可以分配可用内存 40% ~ 60% 之间,PGA 可以分配 可用内存 20% ~ 40% 之间

- parameter range
OS total_memory  
OS available_memory (60% ~ 90%) × total_memory
SGA sga_max_size (60% ~ 80%) × available_memory
SGA sga_target (60% ~ 80%) × available_memory
PGA pga_aggregate_target (40% ~ 20%) × available_memory
alter system set sga_max_size = 20g scope = both;
alter system set sga_target = 20g scope = both;
alter system set pga_aggregate_target = 8g scope = both;

修改 PGA 的自动管理方式和大小

alter system set workarea_size_policy = auto scope = both;
alter system set pga_aggregate_target = 3072m scope = both;

缓存区 (Buffer Cache) 是 SGA 内部的一个重要的结构,Buffer Cache 缓存了 Oracle 数据库中数据表的 CR 块。下面的命令可以查看缓冲区的参数值,如果是 0 表 示让 Oracle 自动管理

-- 查看 Buffer Cache 参数
COLUMN "Name" FORMAT A32;
COLUMN "Value" FORMAT A64;

SELECT T.NAME AS "Name",
       T.VALUE AS "Value"
  FROM V$PARAMETER T
 WHERE T.NAME IN ('db_cache_size', 'db_keep_cache_size', 'db_recycle_cache_size');

缓冲区的命中率一般要达到 98% 以上才算正常

-- 查看缓冲区命中率
SELECT 100 * (1 - ((PHYSICAL.VALUE - DIRECT.VALUE - LOBS.VALUE) / LOGICAL.VALUE)) AS "Buffer Cache Hit Ratio (%)"
  FROM V$SYSSTAT PHYSICAL,
       V$SYSSTAT DIRECT,
       V$SYSSTAT LOBS,
       V$SYSSTAT LOGICAL
 WHERE PHYSICAL.NAME = 'physical reads'
   AND DIRECT.NAME = 'physical reads direct'
   AND LOBS.NAME = 'physical reads direct (lob)'
   AND LOGICAL.NAME = 'session logical reads';

获取推荐的缓冲区值

-- 获取推荐缓冲区值
column "Name" format a12;
select
  a.name as "Name",
  a.size_for_estimate as "Adviced Size (M)",
  a.estd_physical_reads as "Estimed Physical Reads"
from
  v$db_cache_advice a
where
  block_size = '8192'
  and advice_status = 'ON';

游标 (Cursor) 是在 PLSQL 编程中操作数据集的句柄,相当于一个指针变量。他的形 成原因如下,由于存储过程是放在 Shared Pool 里的,当 Client Process 和 Server Process 建立连接后,待操作的结构集在 Buffer Cache 中,因此需要提供 Client Process 一个游标来寻找结果集

oracle-cursor.png

对应存储过程比较多的系统,如果代码中没有释放游标会照成 Oracle 数据库出现问题, 所以一般需要设置可使用的游标数。

alter system set open_cursors = 500 scope = both;

5.4.2 数据库进程

Oracle 的进程包括 Server Process 和 Background Process, 当客户端建立会话后, 客户端还会出现 Client Process, Background Process 主要处理 Oracle 数据库内部 的事务,下面一些常见的后台进程

  • Process Monitor Process (PMON)
  • System Monitor Process (SMON)
  • Database Writer Process (DBWn)
  • Log Writer Process (LGWR)
  • Checkpoint Process (CKPT)
  • Manageability Monitor Processes (MMON and MMNL)
  • Recoverer Process (RECO)

对于进程参数的调整确保以下参数是正确的

-- cpu 数量要和物理主机的线程数一致
show parameters cpu_count;

查看系统的进程数目,会话数目和最大事务数目分别

set heading off;
show parameter processes;
show parameter sessions;
show parameter transactions;

直接在 v$parameter 视图中查看进程数和会话数

COLUMN NAME FORMAT A32;
COLUMN VALUE FORMAT A64;

SELECT T.NAME,
       T.VALUE
  FROM V$PARAMETER T
 WHERE NAME in ('sessions', 'processes', 'transactions');

一般讲 processes 的数值根据系统的性能来设置,但是 sessions 和 transactions 和 processes 有一定的换算关系

Oralce sessions transactions
11g (11.2.0.4) 1.5 × processes + 22 1.1 × sessions
10g 1.1 × processes + 5 1.1 × sessions

Oracle 设置对应参数的快速对照表

  11g 11g 10g 10g
processes sessions transactions sessions transactions
100 172 110 115 110
128 214 141 146 141
200 322 220 225 220
256 406 282 287 282
300 472 330 335 330
512 790 563 568 563
800 1222 880 885 880
1000 1522 1100 1105 1100
1024 1558 1126 1131 1126
1200 1822 1320 1325 1320
1500 2272 1650 1655 1650

可以根据上述对应关系表格设置相应的参数

alter system set processes = 1024 scope = both;
alter system set sessions = 1568 scope = both;
alter system set transactions = 1126 scope = both;

5.4.3 数据库网络架构

Oracle 的网络采取监听模式,客户端可以通过 HTTP 或者 TNS 等协议连接 Oracle 的服务 器本地启动的监听器,通过监听代理来完成和数据库的交互

oracle-network-arch.png

Oracle 监听的工具主要有以下两个

$ tnsping localhost  -- tnsping 工具

TNS Ping Utility for Linux: Version 11.2.0.4.0 - Production on 26-FEB-2020 17:22:33

Copyright (c) 1997, 2013, Oracle.  All rights reserved.

Used parameter files:
/u01/app/oracle/product/11.2.0/dbhome_1/network/admin/sqlnet.ora

Used EZCONNECT adapter to resolve the alias
Attempting to contact (DESCRIPTION=(CONNECT_DATA=(SERVICE_NAME=))(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1521)))
OK (10 msec)

$ lsnrctl status -- lsnrctl 工具

LSNRCTL for Linux: Version 11.2.0.4.0 - Production on 26-FEB-2020 17:22:45

Copyright (c) 1991, 2013, Oracle.  All rights reserved.

Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC1521)))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for Linux: Version 11.2.0.4.0 - Production
Start Date                26-FEB-2020 12:36:36
Uptime                    0 days 4 hr. 46 min. 9 sec
Trace Level               off
Security                  ON: Local OS Authentication
SNMP                      OFF
Listener Parameter File   /u01/app/oracle/product/11.2.0/dbhome_1/network/admin/listener.ora
Listener Log File         /u01/app/oracle/diag/tnslsnr/ol64-52/listener/alert/log.xml
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)(KEY=EXTPROC1521)))
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=ol64-52.localdomain)(PORT=1521)))
Services Summary...
Service "ora11g" has 1 instance(s).
  Instance "ora11g", status READY, has 1 handler(s) for this service...
Service "ora11gXDB" has 1 instance(s).
  Instance "ora11g", status READY, has 1 handler(s) for this service...
The command completed successfully

Oracle 数据库实例的动态监听注册细节

  1. 如果是先启动监听,后启动数据库实例,则动态监听会自动识别到启动的数据库实 例;
  2. 在数据库实例正常运行的情况下重启监听,则数据库实例会等很长时间才能在动态 监听中注册成功,大约需要 1 分钟的等待时间
  3. 如果是先启动数据库实例,后启动监听,效果和 2 一样
  4. 如果不希望长时间等待动态监听注册的过程,下面命令加速
alter system register;

6 数据库管理

6.1 用户和角色

6.1.1 查看当前用户

show user;
select user from dual;

例如:

SQL> show user;
USER is "APPLE"
SQL> select user from dual;
APPLE

6.1.2 创建/解锁用户

create user <username> identified by <password>;
alter user <username> account unlock identified by <password>;
alter user hr account unlock identified by hr;

6.1.3 删除用户

drop user <username>;

6.1.4 修改用户密码

有时候修改用户密码但是不知道用户的原始密码,可以添加 VALUES 传入的是数据库 加密后的密码字符串

alter user <username> identified by <password>;
alter user <username> identified by values <encrypted_password>;

6.1.5 角色和授权

-- 创建角色
create role <rolename> identified by <password>;
-- 授权用户
grant all on <schema>.* to <username>;
-- 解除授权
revoke all on <schema>.* from <username>;

7 工作流

7.1 日期格式

查看时间相关的参数

show parameters nls;

Oracle 不支持直接修改底层系统的时间格式,但是默认的时间格式为 DD-MON-RR 的 字符串,显示得不是很友好,所以需要使用一些曲线救国的方式来修改日期时间的格式

方案一:在当前会话的启动后直接修改当前会话的时间格式

alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS';

方案二:添加环境变量,每次启动 sqlplus 会直接读取环境变量从而达到修改日期格式 的目的

export NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'

方案三:在选取时间日期字段时显示使用 to_char 函数来格式化时间

select
  to_char(sysdate, 'YYYY-MM-DD HH24:MI:SS') as now
from
  dual;

7.2 时区

查看数据库的时区

select dbtimezone from dual;

通过 tz_offset 转化数据库的时区和当前会话的时区

SELECT TZ_OFFSET (DBTIMEZONE) AS "Remote Time Zone",
       TZ_OFFSET (SESSIONTIMEZONE) AS "Local Time Zone"
  FROM DUAL;

7.3 创建带自增 ID 的表

Oracle 创建自增 ID 分为三个步骤

  1. 创建表
  2. 创建自增序列
  3. 添加更新 ID 值的触发器
CREATE TABLE TB_USERS (
  ID NUMBER NOT NULL,
  AVAILABLE CHAR(1) DEFAULT 'Y' NOT NULL,
  CREATED_AT DATE DEFAULT SYSDATE NOT NULL,
  UPDATED_AT DATE DEFAULT SYSDATE NOT NULL,
  CONSTRAINT TB_USERS_PK PRIMARY KEY (ID)
);

CREATE SEQUENCE TB_USERS_ID_SEQ
  INCREMENT BY 1
  START WITH 1
  MINVALUE 1
  MAXVALUE 999999999;

CREATE OR REPLACE TRIGGER TB_USERS_ID_TGR
  BEFORE INSERT ON TB_USERS
  FOR EACH ROW WHEN (NEW.ID IS NULL)
BEGIN
  SELECT TB_USERS_ID_SEQ.NEXTVAL INTO :NEW.ID FROM DUAL;
END;
/

7.4 创建新的表空间以及用户

7.4.1 创建数据储存的目录

Oracle 数据库的表空间的存储目录需要手工创建,注意创建的文件夹要求 oracle:dba 能够读写

mkdir -p /u01/app/oracle/oradata/XE

7.4.2 创建表空间

Oracle 在创建用户前最好为当前用户显示创建表空间,否则创建的新用户会使用默认 的表空间。为了避免这种情况,提前创建默认表空间和临时表空间

-- 创建默认表空间
CREATE TABLESPACE PFILE1
  DATAFILE '/u01/app/oracle/oradata/xe/pfile1.dbf'
  SIZE 128M AUTOEXTEND OFF
  LOGGING
  ONLINE
  PERMANENT
  EXTENT MANAGEMENT LOCAL AUTOALLOCATE
  BLOCKSIZE 8K
  SEGMENT SPACE MANAGEMENT AUTO
  FLASHBACK ON;

-- 创建临时表空间
CREATE TEMPORARY TABLESPACE TFILE1
  TEMPFILE '/u01/app/oracle/oradata/xe/tfile1.dbf'
  SIZE 2048M AUTOEXTEND OFF
  TABLESPACE GROUP ''
  EXTENT MANAGEMENT LOCAL UNIFORM SIZE 1M;

7.4.3 创建用户

创建新的用户

drop user avictor cascade;

create user avictor
  identified by secret
  default tablespace pfile1
  temporary tablespace tfile1
  profile default
  account unlock;

grant connect to avictor;
grant dba to avictor;
alter user avictor default role all;

grant select any dictionary to avictor;
grant select any sequence to avictor;
grant select any table to avictor;
grant select any transaction to avictor;
grant unlimited tablespace to avictor;

alter user avictor identified by secret;

使用 sqlplus 测试一下连接是否成功

sqlplus avictor/secret@localhost/xe

7.5 导入/导出数据

7.5.1 导出全部数据库

如果两个数据库的磁盘的物理路径都是相同的,可以指定 full=y 的方式导入所有的 数据库,这种方式导出的表空间会绑定到磁盘上存储的 Datafile 路径

-- 导出所有的表
exp user/pass@host:port/sid FULL=y FILE=data.dmp

-- 导入所有的表
imp user/pass@host:port/sid FULL=y FILE=data.dmp

7.5.2 导出用户的数据库

如果要导出某个特定用户的所有数据库,这种方式导出的数据库的表空间文件不会绑定 到磁盘存储的物理位置,使用下面命令可以查看所有的帮忙选项

-- 查看帮助和可选的参数
exp help=y
imp help=y

最后新建一个保存参数是文件,这样使用 exp 和 imp 文件时就可以少敲一些命令,这 里将相关参数保存成 options.txt

USERID=system/oracle@host/sid
FILE=data.dmp
GRANTS=y
INDEXES=y
CONSTRAINTS=y
ROWS=y

导入和导出命令

-- 导出数据库
exp PARFILE=options.txt LOG=export.log OWNER=owner1

-- 导入数据库
imp PARFILE=options.txt LOG=import.log FROMUSER=owner1 TOUSER=owner2

7.6 正则化表达式匹配

Oracle 可以使用 regexp_like 来做正则表达式匹配

SQL> SELECT TABLE_NAME FROM USER_TAB_COMMENTS WHERE regexp_like(TABLE_NAME, '^PRDT');

TABLE_NAME
-----------------------------
PRDT_DIC
PRDT_DIC_ALL_V
PRDT_DIC_BOM
PRDT_DIC_CODE_RULE
PRDT_DIC_EXT01
PRDT_DIC_EXT02
PRDT_DIC_PROCESS
PRDT_DIC_PROCESS_ALL
PRDT_DIC_PROCESS_DEVICE_WORK
PRDT_KIND_TYPE
PRDT_LIST_TYPE
PRDT_PROCESS_ACT_DIC
PRDT_PROCESS_DIC
PRDT_PROCESS_RES_DIC

7.7 获取序列值

创建序列

CREATE SEQUENCE SEQ_GLOBAL_ID
  START WITH 10000
  INCREMENT BY 1;

NEXTVAL 获取下一个序列值,同时自增, CURRVAL 获取当前序列值,单不自增。

SQL> SELECT SEQ_GLOBAL_ID.NEXTVAL FROM DUAL;

     10000

SQL> SELECT SEQ_GLOBAL_ID.NEXTVAL FROM DUAL;

     10001

SQL> SELECT SEQ_GLOBAL_ID.CURRVAL FROM DUAL;

     10001

SQL> SELECT SEQ_GLOBAL_ID.CURRVAL FROM DUAL;

     10001

SQL>

7.8 限制查询结果的行数

使用 ROWNUM 在条件语句中限制

SELECT ID FROM EMPLOYEE WHERE ROWNUM < 100;

7.9 删除表中重复的行数据

Oracle 里面每行都有一个 ROWID 的伪列,即使两行的数据是相同的, ROWID 也是 唯一的。

SELECT DISTINCT MYID
  FROM T_EMPLOYEE_BASE E1
 WHERE ROWID != (
        SELECT MAX(ROWID)
          FROM T_EMPLOYEE_BASE E2
         WHERE E1.MYID = E2.MYID
       );

将 SELECT 修改成 DELETE 即可删除重复行。

7.10 查看数据库磁盘容量

7.11 查看 Query 的执行时间

查看单个 Query 执行计时直接开启 set timing on 选项就可以看到

SQL> select count(*) from t_log;

  COUNT(*)
----------
     85718

SQL> set timing on
SQL> select count(*) from t_log;

  COUNT(*)
----------
     85718

Elapsed: 00:00:00.04
SQL> set timing off

查询多条 query 语句的计时需要借助计时器

SQL> timing start mytimer;

SQL> select count(*) from t_log;

  COUNT(*)
----------
     85718

SQL> timing show mytimer;

timing for: mytimer
Elapsed: 00:00:00.04
SQL> select max(myid) from t_log;

 MAX(MYID)
----------
    103234


SQL> timing stop mytimer;

timing for: mytimer
Elapsed: 00:00:00.07
SQL>

7.12 字符串拼接的聚合函数

oracle 10g 和 11g 的字符串拼接函数不是一样的,10g 中使用 WM_CONCAT() 聚合函 数,而在 11g 中需要使用 LISTAGG() 聚合函数。两者的语法也是不太一样的

10g 的使用方式

SELECT A.CONSTRAINT_NAME,
       WM_CONCAT (TO_CHAR(A.COLUMN_NAME)) AS COLNAMES
  FROM USER_CONS_COLUMNS A,
       USER_CONSTRAINTS B
 WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME
   AND UPPER(B.CONSTRAINT_TYPE) = 'U'
   AND UPPER(A.TABLE_NAME) = UPPER('tabname')
 GROUP BY A.CONSTRAINT_NAME;

11g 的使用方式

SELECT A.CONSTRAINT_NAME,
       LISTAGG (A.COLUMN_NAME, ',') WITHIN GROUP (ORDER BY A.POSITION) AS COLNAMES
  FROM USER_CONS_COLUMNS A,
       USER_CONSTRAINTS B
 WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME
   AND UPPER(B.CONSTRAINT_TYPE) = 'U'
   AND UPPER(A.TABLE_NAME) = UPPER('tabname')
 GROUP BY A.CONSTRAINT_NAME;

8 常见问题

8.1 客户端和服务器字符集不一致

首先查看一下服务器中的编码格式,最好设置服务器的编码格式为 Unicode

SELECT USERENV('language') FROM DUAL;
SQL> SELECT USERENV('language') FROM DUAL;
AMERICAN_AMERICA.AL32UTF8

根据服务器的编码格式来修改客户端的编码格式

# 将客户端修改成英文的 Unicode 码
export NLS_LANG="AMERICAN_AMERICA.AL32UTF8"

# 或者直接修改成中文的 Unicode 码,解决中文乱码
export NLS_LANG="SIMPLIFIED CHINESE_CHINA.AL32UTF8"

8.2 将服务器字符集从 UTF8 强转成 GBK

修改数据库字符集最好放在在安装数据库的最初阶段,如果修改一个已经运行的数据库 可能会发生一些不可预料的错误

-- 本操作必须使用 DBA 权限
CONNECT SYS/oracle AS SYSDBA;

-- 关闭连接断开所有已经建立的链接
SHUTDOWN IMMEDIATE;
STARTUP MOUNT;

-- 预先设置处理工作
ALTER SYSTEM ENABLE RESTRICTED SESSION;
ALTER SYSTEM SET JOB_QUEUE_PROCESSES=0;
ALTER SYSTEM SET AQ_TM_PROCESSES=0;
ALTER DATABASE OPEN;

-- ALTER DATABASE CHARACTER SET ZHS16GBK ;
-- *ERROR at line 1:
-- ORA-12712: new character set must be a superset of old character set
-- 报字符集不兼容,此时下 INTERNAL_USE 指令不对字符集超集进行检查:
ALTER DATABASE CHARACTER SET INTERNAL_USE ZHS16GBK;

-- 重启数据库
SHUTDOWN IMMEDIATE;
STARTUP;

查看当前数据库的字符集

COLUMN PROPERTY_NAME FORMAT A32 HEADING 'Name';
COLUMN PROPERTY_VALUE FORMAT A32 HEADING 'Value';
COLUMN DESCRIPTION FORMAT A80 HEADING 'Description' TRUNCATE;

SELECT PROPERTY_NAME, PROPERTY_VALUE, DESCRIPTION
  FROM DATABASE_PROPERTIES WHERE PROPERTY_NAME LIKE 'NLS_%';

8.3 ORA-00904: "POLTYP": invalid identifier

客户端版本如下:

Export: Release 11.2.0.2.0 - Production on Thu Sep 12 00:56:51 2019
Copyright (c) 1982, 2009, Oracle and/or its affiliates.  All rights reserved.

服务器数据版本如下:

Connected to: Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

在使用以下 11g 的 exp 客户端程序导出 10g 服务器数据出现问题以下:

EXP-00008: ORACLE error 904 encountered
ORA-00904: "POLTYP": invalid identifier
EXP-00000: Export terminated unsuccessfully

查了一下在不动数据库的前提下解决这种问题最好方法是: 将导出客户端换成 10g

8.4 ORA-21561: OID generation failed

数据库的主机名解析错误

$ hostname
Jesenia.local
$ ping Jesenia.local
ping: cannot resolve Jesenia.local: Unknown host
$ cat /etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1 localhost
255.255.255.255 broadcasthost
::1             localhost

在 hosts 文件结尾追加当前主机的解析地址即可

sudo echo "127.0.0.1 $(hostname)" >> /etc/hosts

9 sqlplus

Oracle 的 sqlplus 是与 Oracle 数据库进行交互的客户端工具,借助 sqlplus 可以查 看、修改数据库记录。在 sqlplus 中,可以运行 sqlplus 命令与 SQL 语句。

9.1 Instantclient 安装

在 mac 和 linux 系统中如果只是想要安装 sqlplus 命令行工具,需要安装 Instantclient 工具

9.1.1 macOS

mac 下的 Oracle 安装需要下载几个 Instantclient 安装包

  1. instantclient-basic-macos.x64-11.2.0.4.0.zip
  2. instantclient-sdk-macos.x64-11.2.0.4.0.zip
  3. instantclient-sqlplus-macos.x64-11.2.0.4.0.zip

解压安装包

unzip instantclient-basic-macos.x64-11.2.0.4.0.zip
unzip instantclient-sdk-macos.x64-11.2.0.4.0.zip
unzip instantclient-sqlplus-macos.x64-11.2.0.4.0.zip

解压后生成一个 instantclient_11_2 文件夹,直接拷贝到安装的文件夹中

sudo mkdir -p /usr/local/java
sudo cp -r instantclient_11_2 /usr/local/java

建立软连接

cd /usr/local/java/instantclient_11_2
mkdir lib
ln -s libclntsh.dylib.11.1 libclntsh.dylib
ln -s libocci.dylib.11.1 libocci.dylib
ln -s $PWD/{libclntsh.dylib.11.1,libnnz11.dylib,libociei.dylib} lib/
ln -s $PWD/{libsqlplus.dylib,libsqlplusic.dylib} lib/

添加环境变量到 .bashrc 中

export SQLPATH="/usr/local/java/instantclient_11_2"
export PATH="${SQLPATH}:$PATH"
export DYLD_LIBRARY_PATH="${SQLPATH}/lib${DYLD_LIBRARY_PATH:+:${DYLD_LIBRARY_PATH}}"

9.1.2 Linux

需要下载的安装文件

  1. instantclient-basic-linux.x64-11.2.0.4.0.zip
  2. instantclient-sdk-linux.x64-11.2.0.4.0.zip
  3. instantclient-sqlplus-linux.x64-11.2.0.4.0.zip

解压安装包

unzip instantclient-basic-linux.x64-11.2.0.4.0.zip
unzip instantclient-sdk-linux.x64-11.2.0.4.0.zip
unzip instantclient-sqlplus-linux.x64-11.2.0.4.0.zip

构建目录结构

[ ! -d /usr/local/java ] && mkdir -p /usr/local/java
sudo mv instantclient_11_2 /usr/local/java
cd /usr/local/java/instantclient_11_2
sudo mkdir lib

建立软连接

ln -s libclntsh.so libclntsh.so.11.1
ln -s libocci.so libocci.so.11.1

cd /usr/lib
ln -s /usr/local/java/instantclient_11_2/libclntsh.so libclntsh.so.11.1
ln -s /usr/local/java/instantclient_11_2/libocci.so libocci.so.11.1
ln -s /usr/local/java/instantclient_11_2/libnnz11.so libnnz11.so
ln -s /usr/local/java/instantclient_11_2/libociei.so libociei.so
ln -s /usr/local/java/instantclient_11_2/libocijdbc11.so libocijdbc11.so
ln -s /usr/local/java/instantclient_11_2/libsqlplusic.so libsqlplusic.so
ln -s /usr/local/java/instantclient_11_2/libsqlplus.so libsqlplus.so

添加环境变量

export SQLPATH="/usr/local/java/instantclient_11_2"
export PATH=${SQLPATH}${PATH:+:${PATH}}
export LD_LIBRARY_PATH=${SQLPLUS}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

9.2 添加 Readline 的支持

sqlplus 本身不支持 Readline 的特性,但可以通过安装 rlwrap 来将 Readline 特性 添加到 sqlplus 中

# mac
brew install rlwrap
# ubuntu
sudo apt-get install -y rlwrap

通过 rlwrap 来启动 sqlplus

rlwrap -c sqlplus user/pass@host/sid

或者直接定义一个别名

alias sp='rlwrap -c sqlplus'

9.3 启动 sqlplus

第一种登录方式的命令如下

sqlplus username/password@hostname:port/service_id

登录过后可以看到成功的登录界面

sqlplus-login.png

第二种登录方式需要使用 tnsnames.ora 这种格式,下面是 Oracle Client 提供的默 认的配置

# This is a sample tnsnames.ora that contains the NET8 parameters that are
# needed to connect to an HS Agent

hsagent  =
  (DESCRIPTION=
    (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))
    (CONNECT_DATA=(SID=hsagent))
    (HS=)
  )

熟悉了 tnsnames.org 这种格式后就可以编写相应的登录命令,具体方式如下:

sqlplus username/password@"(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=hostname)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=service_id)))"

当命令中的密码字段中含义有 @ 字段时需要将 password 字段用双引号包起来,如下:

sqlplus user/\"my@password\"@hostname:port/service_id

9.4 启动配置

在启动 sqlplus 时需要预先设置一些启动的基本配置,例如启动的每行显示的字符数量, 显示是否折行,分页大小配置等。sqlplus 启动配置文件通常在下面两位置中

  • $ORACLE_HOME/sqlplus/admin/glogin.sql
  • $SQLPATH/glogin.sql

下面是我的配置

-- glogin.sql

-- set underline off
-- set verify off
-- set trimout on
-- set trimspool on

set tab off
set wrap off
set linesize 32767
set pagesize 9999

-- responsive linesize
host echo "set linesize $(stty -a | head -n 1 | cut -d';' -f3 | cut -d' ' -f3)" > .tmp.sql
@.tmp.sql
host rm .tmp.sql

-- macOS use
-- host echo "set linesize $(stty -a | head -n 1 | cut -d';' -f3 | cut -d' ' -f2)" > .tmp.sql
-- @.tmp.sql
-- host rm .tmp.sql

-- add user and sid to prompt
column global_name new_value gname
set termout off
select '('|| lower(user || '@' || global_name) || ')' || chr(10) || 'SQL> '
  as global_name
  from global_name;
  set sqlprompt '&gname'
set termout on

-- change date format
alter session set nls_date_format = 'yyyy-mm-dd hh24:mi:ss';

9.5 sqlplus 的常见命令

  • 执行本地 SQL 脚本: start filename 或者 @ filename
  • 在 sqlplus 里面执行 shell 命令: host cmd
  • 将 sqlplus 执行的命令存成本地文件: save filename
  • 装载本地 SQL 脚本,但不立即执行: get filename
  • 执行最后一条 SQL 语句: /
  • 打印最后执行的 SQL 语句: L
  • 结果输出到文件: spool filename
  • 查看/修改 sqlplus 的环境变量: show/set variablename, 使用 show all 打印 所有变量值

9.6 在 sqlplus 中使用变量

9.6.1 定义和使用变量

定义的变量只在一个 sqlplus 的会话期间有效

-- 定义一个变量
define var = text;
-- 读取用户输入,将用户输入放到变量中
accept var;
-- 在变量名前面添加 & 来使用变量
define mydate = 01-FEB-98;
select '&mydate' from dual;

下面使用一个使用例子

SQL> DEFINE MYDATE = 01-FEB-98;
SQL> SELECT '&MYDATE' FROM DUAL;
原值    1: SELECT '&MYDATE' FROM DUAL
新值    1: SELECT '01-FEB-98' FROM DUAL

'01-FEB-98'
---------------------------
01-FEB-98

SQL>

9.6.2 将列值赋给变量

具体语法如下:

column colname new_value var

使用示例

SQL> column len1 new_value l1
SQL> select max(length(t.table_name)) as len1 from user_tab_comments t;

      LEN1
----------
        29
SQL> select &l1 from dual;
old   1: select &l1 from dual
new   1: select         29 from dual

        29
----------
        29
SQL>

9.7 执行 SQL 脚本的方法

执行没有参数的 SQL 脚本

# 执行 script.sql 脚本后退出,
echo 'exit' | sqlplus -S user/pass@host:port/sid @script.sql
# 使用重定向执行 script.sql 脚本
sqlplus -S user/pass@host:port/sid < script.sql
# 使用重定向执行脚本并追加方式输出日志
sqlplus -S user/pass@host:port/sid < script.sql >> /tmp/sqlplus.log
# .sql 文件后缀名不是必须的,例如下面的两句是同样效果的
echo 'exit' | sqlplus -S user/pass@host:port/sid @script.sql
echo 'exit' | sqlplus -S user/pass@host:port/sid @script

将参数传入 script.sql 脚本,在调用脚本时直接将参数写到后面,见下面的例子

echo 'exit' | sqlplus -S user/pass@host:port/sid @script.sql arg1 arg2

然后在脚本中使用 &1 引用第一个参数, &2 引用第二个参数,以此类推

define arg1 = &1
select '&arg1' from dual;

9.8 打开/关闭终端输出

set termout off;
-- do a lot of parepare work
set termout on;

10 杂项

10.1 Oracle 基本常识

  • Oracle 数据库中语句 不区分大小写, 10g 中包括登录名的用户名和密码都是不区 分大小写的,当然这个是可以设置的
  • 一般关键字要求大写,但是不大写也没关系
  • Oracle 的 select 语句必须要加 from 语句,如果没有的话使用 dual 伪表

    select 5/2 from dual
    

10.2 Ubuntu 18.04 安装 Oracle 11g XE 数据库

如果有 Oracle 11g XE 版本的 deb 包,可以使用如下方式安装,否则最好参考官方的 手册来安装

# 安装依赖包
sudo apt-get install -y libaio1 libaio-dev net-tools
# 安装软件包
dpkg --install /assets/oracle-xe_11.2.0-1.0_amd64.deb

10.3 判断回滚段竞争的 SQL 语句

当 Ratio 大于 2 时存在回滚段竞争,需要增加更多的 回滚段

select
  rn.name as "Name",
  rs.gets as "Gets",
  rs.waits as "Waits",
  (rs.waits / rs.gets) * 100 as "Ratio (%)"
from
  v$rollstat rs,
  v$rollname rn
where
  rs.usn = rn.usn;

10.4 判断恢复日志竞争的 SQL 语句

Immediate Contention 或 Wait Contention 的值大于 1 时存在竞争

column name format a20;

select
  name,
  round(100 * decode(sign(t.immediate_gets - t.immediate_misses), 1,
    t.immediate_misses / (t.immediate_gets - t.immediate_misses), 0), 2) as
    "Immediate Contention (%)",
  round(100 * decode(sign(t.gets - t.misses), 1, t.misses / (t.gets -
    t.misses), 0), 2) as "Wait Contention (%)"
from
  v$latch t
where
  lower(name) in ('redo copy', 'redo allocation');

10.5 判断表空间碎片

如果最大空闲空间占总空间很大比例则可能不存在碎片,如果比例较小,且有许多空闲 空间,则可能碎片很多

select
  t.tablespace_name,
  sum(t.bytes),
  max(t.bytes),
  count(*),
  max(t.bytes) / sum(t.bytes) radio
from
  dba_free_space t
group by
  t.tablespace_name
order by
  t.tablespace_name;

10.6 确定命中排序域的次数

select t.name, t.value from v$sysstat t where lower(t.name) like 'sort%';

10.7 查看当前 SGA 值

select * from v$sga;

10.8 确定高速缓冲区命中率

如果命中率低于 70%,则应该加大 init.ora 参数中的 db_block_buffer 的值

select
  100 * (1 - ((physical.value - direct.value - lobs.value) / logical.value)) as
    "Buffer Cache Hit Ratio (%)"
from
  v$sysstat physical,
  v$sysstat direct,
  v$sysstat lobs,
  v$sysstat logical
where
  physical.name = 'physical reads'
  and direct.name = 'physical reads direct'
  and lobs.name = 'physical reads direct (lob)'
  and logical.name = 'session logical reads';

10.9 确定共享池中的命中率

如果 ratio1 大于 1 时,需要加大共享池,如果 ratio2 大于 10%时,需要加大共享 池 shared_pool_size

select
  sum(pins) as pins,
  sum(reloads) as reloads,
  (sum(reloads) / sum(pins)) * 100 as ratio1
from
  v$librarycache;

select
  sum(gets) as gets,
  sum(getmisses) as getmisses,
  (sum(getmisses) / sum(gets)) * 100 as ratio2
from
  v$rowcache;

10.10 数据库参数属性

column property_name format a25;
column property_value format a30;
column description format a100;
select * from database_properties;
select * from v$version;

10.11 求当前会话的 SID, SERIAL#

select sid, serial# from v$session
  where audsid = sys_context('userenv', 'sessionid');

10.12 闪回表

闪回表需要数据库和表空间都开启了闪回模式

SQL> alter table t1 enable row movement;

Table altered.

SQL> drop table t1;

Table dropped.

SQL> flashback table t1 to before drop;

Flashback complete.

SQL> select count(*) from t1 as of timestamp to_date('20201220 17:00:00', 'yyyymmdd hh24:mi:ss');

  COUNT(*)
----------
     87188

SQL> select count(*) from t1 as of timestamp to_date('20201220 17:04:00', 'yyyymmdd hh24:mi:ss');

  COUNT(*)
----------
       298

10.13 追溯其它会话

通过 set_sql_trace_in_session 包来追溯其它会话

-- 当前会话 sid
SELECT USERENV('sid') FROM DUAL;

-- 查看当前默认的追溯文件位置
SELECT VALUE FROM V$DIAG_INFO WHERE NAME LIKE 'Default%';

SELECT SID,
       SERIAL#,
       PROGRAM
  FROM V$SESSION
 WHERE PROGRAM like '%sqlplus%';

-- 开启其它会话的追溯文件
execute dbms_system.set_sql_trace_in_session(5,131,true);

-- 关闭其它会话的追溯文件
execute dbms_system.set_sql_trace_in_session(5,131,false);

-- 查看追溯会话的 spid, 找追溯文件
SELECT B.SPID
  FROM V$SESSION A,
       V$PROCESS B
 WHERE A.PADDR = B.ADDR
   AND A.SID = 5
   AND A.SERIAL# = 131;

通过 tkprof 来分析会话的运行资源消耗数

tkprof ora11g_ora_10868.trc d:\a.txt

11 参考链接

  1. Oracle Database 11g Release 2 在线文档
  2. Oracle 11g Release 2 文档离线 - 点击后下载得到 Zip 包
  3. 2 Day DBA - 《两天数据库管理员》
  4. Oracle Database Concepts, PDF - 《Oracle 数据库概念》
  5. Oracle Database SQL Reference - 《Oracle 数据库 SQL 参考手册》 可以查看 SQL 内置函数
  6. Vitaliy Mogilevskiy

oracle-database-documentation.png

Last Updated 2021-06-18 Fri 16:10. Created by Jinghui Hu at 2019-07-08 Mon 11:04.