Open Yehnn opened 6 years ago
本节实验主要讲解了 Oracle 的备份与恢复,包括了逻辑和物理备份与恢复,完整和增量备份,以及完全恢复和不完全恢复。
概述
逻辑备份与恢复
物理备份与恢复
完整备份
增量备份
完全恢复
不完全恢复
备份包括逻辑备份和物理备份:
逻辑备份与恢复使用的是 Data Pump Export
和 Data Pump Import
工具。
这里以导出表 student 和导入表 student 为例。
在开始进行导出导入之前,我们需要做如下准备:
$ORACLE_BASE/admin/<database_name>/dpdump
)首先修改目录 /u01
的所有者:
$ sudo chown -R oracle.dba /u01
然后用 sqlplus 登录进 system 用户:
$ sqlplus system/Syl12345
然后创建一个指向该目录的指针:
SQL> create directory dpd as '<oracle_base>/admin/xe/dpdump';
上面的
<oracle_base>
替换成自己的ORACLE_BASE
目录。可以在终端使用命令echo $ORACLE_BASE
查看。
创建好后可以用一下命令查询到:
SQL> select * from dba_directories where directory_name='DPD';
接下来使用 expdp
备份,注意这个命令是在 linux 命令行执行的:
$ expdp system/Syl12345 tables=student directory=dpd dumpfile=exp_student.dmp
system/Syl12345
是执行备份的用户名和密码。
tables
是指定要备份的表。
directory
是指定备份存放的位置。如果不指定参数,则会存放到默认位置DATA_PUMP_DIR
,可以使用select * from dba_directories where directory_name='DATA_PUMP_DIR';
查询到。
dumpfile
是指定备份的文件名。
除了可以指定上述的参数外,还有很多其他的参数,可参见 expdp 命令行输入参数 。
将 student 表导入并更名为 studentbak 。
$ impdp system/Syl12345 remap_table=student:studentbak directory=dpd dumpfile=exp_student.dmp
remap_table
指定在导入时更改表名。冒号左边的是旧表名,冒号右边的是新表名。如果你想直接覆盖 student 表的话可以使用TABLE_EXISTS_ACTION=REPLACE
。dumpfile
指定要导入备份的文件名。
除了可以指定上述的参数外,还有很多其他的参数,可参见 impdp 命令行输入参数。
物理备份包含脱机备份和联机备份:
ACHIVELOG
)模式。我们通常使用 RMAN
工具在归档日志模式下进行备份。
RMAN (Recovery Manage)是一个备份工具。它有两种跟踪备份的方法:
通常我们将执行备份和还原操作的数据库称为目标数据库。更多有关 RMAN 的介绍可参考 RMAN Backup Concepts 。
在开始备份之前,先执行我们的准备工作。
更改 NLS_DATE_FORMAT
参数。这个步骤主要是为了让 RMAN 显示输出信息中的时间包含小时,分钟和秒,以便于能够获取执行命令更精确的时间信息。因为默认是不包含小时,分钟和秒的,所以需要更改。
$ export NLS_DATE_FORMAT='dd-mon-yyyy hh24:mi:ss'
数据库默认是在非归档日志模式(NOARCHIVELOG)下。我们可以使用下面的方法进行查看。
首先以 sys 用户登入:
$ sqlplus sys/Syl12345 as sysdba
然后即可查询当前归档模式:
SQL> select name,log_mode from v$database;
NAME LOG_MODE
------------------ ------------------------
XE NOARCHIVELOG
想要切换到归档日志模式下,有以下几个主要步骤:
MOUNT
阶段SQL> shutdown immediate;
SQL> startup mount;
SQL> alter database archivelog;
SQL> alter database open;
SQL> archive log list;
这样就切换到归档模式了,归档程序进程 ARCn 会启动,归档日志会写入快速恢复区,它所在的目录可以通过如下命令查询到:
SQL> select dest_name,destination from v$ARCHIVE_DEST where dest_name='LOG_ARCHIVE_DEST_1';
SQL> show parameter db_recovery;
使用如下语句可以登入本地数据库实例:
$ rman target /
是以
sysdba
权限的用户身份登入的。
更多和连接目标数据库相关内容可参考 Starting RMAN and Connecting to a Database 。
登入进去过后也可以执行 sql 语句,比如查询表空间有哪些:
注意:前面的 RMAN>
不用输入,只是用来代表命令是在 RMAN
命令行输入的。
RMAN> select * from v$tablespace;
TS# NAME INC BIG FLA ENC CON_ID
---------- ------------------------------ --- --- --- --- ----------
1 SYSAUX YES NO YES 0
0 SYSTEM YES NO YES 0
2 UNDOTBS1 YES NO YES 0
4 USERS YES NO YES 0
3 TEMP NO NO YES 0
5 SYLTP1 YES NO YES 0
9 TMP_SP1 NO NO YES 0
RMAN 有许多可用命令,参见 About RMAN Commands 。
在执行备份前,我们最好配置一下 RMAN 。我们可以通过如下命令查看它的默认配置:
RMAN> show all;
注:它的配置很多,不止下面将会用到的配置,想了解更多配置可参考 RMAN 配置 。
下面将会做这些配置步骤:
如下命令配置了 RMAN 不会废弃 3 天内的数据文件和归档重做日志备份:
RMAN> configure retention policy to recovery window of 3 days;
RMAN> configure device type disk backup type to compressed backupset;
compressed
代表压缩备份集,可节省空间。 在企业版中还可以通过命令configure device type disk parallelism n backup type to compressed backupset;
设置以 n 个通道并行执行。(n 代表一个数字)。
RMAN> configure channel device type disk format '/u01/rman_%U';
%U
是动态字符串。这样每次 RMAN 存储的备份文件的文件名不重复。
下面的命令配置当归档重做日志在磁盘中至少备份两次时,归档重做日志可自动被删除。
RMAN> configure archivelog deletion policy to backed up 2 times to disk;
完成了上述的准备和配置操作后,就可以开始进入到我们备份操作的实验中了。
下面以备份数据库和表空间为例。
直接执行下面的命令就可以备份数据库了:
RMAN> backup database;
备份完成过后,在我们开始设置的备份文件的存储位置可以看到我们有我们的备份文件。
这里就以备份我们之前在安全性管理的实验中创建的 syltp1
表空间为例。
RMAN> backup tablespace syltp1;
备份完成后,在我们配置的位置就可以看到该备份文件。
我们上面的备份操作都是将整个数据库或者整个表空间备份下来。增量备份就是先创建一个初始备份文件,这个创建操作被称为 0 级备份,以后的备份操作( 1 级备份)只是在初始备份文件的基础上备份了改变了的数据块。这样的话节省了很多时间和空间,因为 1 级备份的备份文件只是包含了改变了的数据块。
下面以增量备份 syltp1
表空间为例。
首先要进行我们的 0 级备份操作:
RMAN> backup incremental level 0 tablespace syltp1;
这个时候就创建了初始备份文件。以后备份的时候可以使用 1 级备份,如下所示:
RMAN> backup incremental level 1 tablespace syltp1;
注意完全恢复并不是对应的完整备份。它只是指这个恢复是完全的,没有丢失数据。它在数据库打开状态即可进行,根据我们上面备份操作产生的备份文件进行还原和恢复。
假设表空间 syltp1 现在已经损坏了,那么我们就需要使用之前的 syltp1 表空间的备份来恢复该表空间。接下来的恢复操作有一下几个主要步骤:
RMAN> restore tablespace syltp1 preview summary;
RMAN> restore tablespace syltp1 validate;
RMAN> alter tablespace syltp1 offline immediate;
脱机后该表空间就不可访问了。
RMAN> restore tablespace syltp1;
RMAN> recover tablespace syltp1;
RMAN> alter tablespace syltp1 online;
至此,恢复 syltp1 表空间的操作就完成了。
同样,不完全恢复并不是对应的增量备份。有时候,我们在一段时间内执行了很多错误的修改,比如说误删了很多东西,这个时候就可以用不完全备份将数据库恢复到过去的某个时刻的状态。它要在数据库装载模式下进行。并且要重建联机重做日志文件。
下面的实验将学习如何进行不完全恢复,不完全恢复主要有以下几个步骤:
RMAN> select current_scn from v$database;
注:SCN:系统变更编号。 SCN的值是对数据库进行更改的逻辑时间点。它有点像一个版本号的概念。
创建一个测试表。
RMAN> create table syl_res (id number);
我们下面来基于 SCN 创建一个还原点。
RMAN> create restore point syl_res_scn;
RMAN> select name,scn from v$restore_point;
此操作查询出还原点的名称和 SCN。
还原点有一定的保存时间,由初始参数 CONTROL_FILE_RECORD_KEEP_TIME
控制的。可以使用如下命令查看其值:
RMAN> select name,value from v$parameter where name='control_file_record_keep_time';
创建好还原点后,我们删除表 syl_res :
RMAN> drop table syl_res;
RMAN> select * from tab where tname='syl_res';
后来我们发现这个表是误删,想要回到之前的还原点,就可以执行如下操作:
RMAN> shutdown immediate;
RMAN> startup mount;
RMAN> restore database until restore point syl_res_scn;
RMAN> recover database until restore point syl_res_scn;
RMAN> alter database open resetlogs;
再查询表 syl_res 会发现这个表已经被恢复了:
RMAN> select * from tab where tname='SYL_RES';
更多有关 RMAN 备份还原的内容可参考 Getting Started withRMAN 。
本节实验主要讲解了 Oracle 的存储过程的相关操作,包括了如何创建、删除存储过程,和在存储过程的操作。
存储过程简介
创建存储过程
删除存储过程
存储过程中使用 DDL 语句
存储过程中使用游标
存储过程中使用事务
函数
一个过程是可以通过名字调用的一组 PL/SQL 语句。它将复杂的业务规则从应用程序中分离出来交给数据库,应用程序只需要调用存储过程获取返回的结果就行了。这种方式将复杂的数据处理交给了服务器处理,大大提高了效率,并且后期维护会很容易,因为不用在大量的应用程序代码中修改,而只需要修改存储过程。
创建存储过程必须拥有 CREATE PROCEDURE
系统权限或者 CREATE ANY PROCEDURE
系统权限。后者用于在其他用户模式中创建存储过程。
创建存储过程的具体语法可参见 创建存储过程 。我们可以创建无参和有参存储过程,下面从实践中去体会它们的实际运用。
接下来我们来创建一个简单的存储过程,实现打印 hello world
。
首先确定 SERVEROUTPUT 是否打开:
SQL> show serveroutput;
--如果显示未打开,则使用如下方式打开
SQL> set serveroutput on;
接着创建一个名为 pro1
的存储过程:
CREATE OR REPLACE PROCEDURE pro1 --创建无参存储过程
AS
BEGIN
DBMS_OUTPUT.PUT_LINE('hello world');
END;
/
我们可以查看是否创建成功:
select * from user_source where name='PRO1';
user_source
是查询当前用户的存储过程。如果要查看所有的存储过程使用all_source
。
OR REPLACE
表示如果过程已经存在,则同名的存储过程。
下面执行一下我们创建的这个存储过程:
exec pro1;
--或者使用如下方式执行
BEGIN
pro1;
END;
/
输出结果:
PL/SQL 过程已成功完成。
hello world
创建有参的存储过程的概念就像其他程序语言的创建带参函数,以便于数据的输入输出。创建存储过程中定义的参数有输入,输出,输入输出三种参数。下面通过实践分别学习创建带各种参数的存储过程。
我们之前有写过一个根据键盘输入的学生编号查找学生姓名的 PL/SQL 程序。在这里我们调用存储过程,向其传入学生编号,返回学生姓名的方式实现姓名查询。
CREATE PROCEDURE PRO_NAME(arg_id IN NUMBER)
AS
v_sname VARCHAR2(20); --接收学生姓名
BEGIN
SELECT s_name INTO v_sname FROM student WHERE s_id=arg_id; --把查询出来的值赋给变量 v_sname
DBMS_OUTPUT.put_line('student''s name is : ' || v_sname);
END;
/
IN
代表定义的参数是输入参数,是默认的,可以省略 IN。NUMBER
是输入参数的类型。
AS
后面声明了一个变量用来接收学生姓名,用于打印出查询出来的学生姓名。
BEGIN
和END
之间是执行的查询操作。
查询学生编号为 1001 的学生姓名:
exec PRO_NAME(1001);
输出结果:
PL/SQL 过程已成功完成。
student's name is : shiyanlou1001
如果有其他存储过程想使用查询出来的学生姓名怎么办呢。这个时候就需要在被调用的存储过程中定义输出参数,将接收学生姓名的参数传出来供其他存储过程使用。首先我们创建一个被调用的存储过程,它能根据传入的学生编号,将学生姓名传出。
CREATE PROCEDURE PRO_NAME2(arg_id IN NUMBER,arg_name OUT VARCHAR2)
AS
BEGIN
SELECT s_name INTO arg_name FROM student WHERE s_id=arg_id; --把查询出来的值给输入参数 arg_name
END;
/
OUT
代表定义的参数是输出参数。
然后创建一个存储过程,它调用上面的存储过程,然后将学生姓名打印出来:
CREATE PROCEDURE PRO_GETNAME
AS
v_name student.s_name%TYPE; --接收学生姓名
BEGIN
PRO_NAME2(1001,v_name); --调用存储过程 PRO_NAME2,将传出的学生姓名给变量 v_name
DBMS_OUTPUT.put_line('student''s name is : ' || v_name); --打印出变量 v_name 的值
END;
/
执行存储过程 PRO_GETNAME 及其输出结果:
SQL> exec pro_getname;
PL/SQL 过程已成功完成。
student's name is : shiyanlou1001
顾名思义,也就是既可以输入也可以输出。例如我们根据学生编号查询对应的学生年龄。首先创建被调用的存储过程:
CREATE PROCEDURE PRO_NAME3(arg_id_age IN OUT NUMBER) --定义输入输出参数,它既可以传入学生编号,又可以传出学生年龄
AS
BEGIN
--根据学生编号查询学生年龄
SELECT s_age INTO arg_id_age FROM student WHERE s_id=arg_id_age;
END;
/
然后创建一个存储过程调用上面的存储过程,查询学生编号为 1001 的学生年龄。
CREATE PROCEDURE PRO_GETAGE
AS
v_id_age student.s_age%TYPE;
BEGIN
v_id_age:=1001; --学生编号为 1001
PRO_NAME3(v_id_age); --调用 PRO_NAME3 存储过程,查询出 1001 学生的年龄,再给变量 v_id_age
DBMS_OUTPUT.put_line('student''s age is : ' || v_id_age); --打印出学生年龄
END;
/
执行存储过程以及输出结果:
SQL> exec pro_getage;
PL/SQL 过程已成功完成。
student's age is : 10
注:编译存储过程时难免遇到各种错误,此时可以使用
show errors procedure <procedure_name>
查看更详细的错误信息。
想了解更多有关创建存储过程内容可参考创建存储过程 。
例:删除存储过程 pro1 。
SQL> drop procedure pro1;
--查询可发现已经没有 pro1 这个存储过程。
SQL> select * from user_source where name='PRO1';
上面创建的存储过程中使用的都是 DML 语句,如果要使用 DDL 语句,比如创建表等,不能像使用 DML 语句一样直接使用,而需要加上 EXECUTE IMMEDIATE
。
例如创建一个表,其中包含学生姓名和学生平均成绩。
CREATE PROCEDURE PRO_GRADE
AS
BEGIN
EXECUTE IMMEDIATE 'CREATE TABLE stu_grade AS select s_name,avg(grade) avg_grade from student join sc using(s_id) group by s_name order by s_name'; --创建表
END;
执行存储过程以及查询表 stu_grade 中的内容:
exec pro_grade;
select * from stu_grade;
之前我们编写的存储过程中的查询基本都是返回一个结果或者一行结果。如果想要返回查询出来的多个结果的话,就可以使用游标。游标就像一个游动的指针,它可以获取结果集中的任意行,这样的话,只要搭配循环语句,一行一行地返回,就可以打印出整个结果集的内容。我们用如下实例来具体理解。
将会使用我们在 SQL 一节学习中创建的 student 示例表。例如我们要打印出不同性别的学生编号,学生名称,学生年龄,并计算不同性别的学生数量。
思路:
代码:
CREATE OR REPLACE PROCEDURE PRO_SEX --创建存储过程
AS
v_count NUMBER; --变量,用于接收学生数量
v_sex student.s_sex%TYPE; --变量,用户接收学生性别
CURSOR cur_sex --创建游标
IS
SELECT s_sex FROM student GROUP BY s_sex; --与游标关联的sql语句,此sql语句是查询出有哪些性别
BEGIN
OPEN cur_sex; --打开游标
--第一个循环开始,用来循环性别
LOOP
FETCH cur_sex INTO v_sex; --获取游标当前指向的数据给变量 v_sex,第一次 v_sex 被赋值为 woman,第二次 v_sex 被赋值为 man
EXIT WHEN cur_sex%NOTFOUND; --当获取不到数据时退出
DBMS_OUTPUT.PUT_LINE('===================');
DBMS_OUTPUT.PUT_LINE(v_sex || ' :');
--第二个循环开始,用来循环查询出的结果集,获取每行数据
FOR row_student IN --FOR 循环遍历结果中每行数据给变量 row_student
(
SELECT * FROM student WHERE s_sex=v_sex --查询当前获取的性别有哪些学生
)
LOOP
DBMS_OUTPUT.PUT_LINE(
'student id: ' || row_student.s_id || --打印学生编号
' student name: '|| row_student.s_name || --打印学生姓名
' student age: ' || row_student.s_age --打印学生年龄
);
END LOOP; --第二个循环结束
SELECT count(s_id) INTO v_count FROM student WHERE s_sex=v_sex; --查询出当前获取的性别的学生数量
IF SQL%FOUND THEN --如果此查询有结果就执行下面的语句
DBMS_OUTPUT.PUT_LINE('count: ' || v_count); --打印数量
END IF;
END LOOP; --第一个循环结束
CLOSE cur_sex; --关闭游标
END;
/
执行以及输出结果:
SQL> exec pro_sex;
PL/SQL 过程已成功完成。
===================
woman :
student id: 1002 student name: shiyanlou1002 student age: 20
student id: 1004 student name: shiyanlou1004 student age: 40
count: 2
===================
man :
student id: 1001 student name: shiyanlou1001 student age: 10
student id: 1003 student name: shiyanlou1003 student age: 18
student id: 1005 student name: shiyanlou1005 student age: 17
count: 3
补充:
CURSOR cur_sex
IS
SELECT s_sex FROM student GROUP BY s_sex;
cur_sex
是创建的游标名称。
IS
后面是关联的查询语句,游标可以从此查询的结果集中获取某行的数据。注意不能是 SELECT INTO
。
用这种方式创建的游标被称为显式游标,需要手动打开,手动关闭。
cur_sex%NOTFOUND
这里是使用的显式游标的属性。%NOTFOUND
表示当获取不到数据时返回 TRUE
,反之返回 FALSE
。
显示游标还有其他属性 %FOUND
,%ISOPEN
,%ROWCOUNT
。可参见 游标属性 。
SQL%FOUND
这里是用了一个隐式游标,SQL
是隐式游标的默认名称,%FOUND
是它的属性,表示 DML 操作是否对数据有影响,比如查询语句是否返回有结果,插入语句是否成功等等。如果有影响则返回 TURE
,反之返回 FALSE
。
隐式游标也有和显式游标相同的属性,只是 %ISOPEN
永远为 FALSE
。
事务是用来保证数据的一致性。一个很常见的例子就是银行转账,转出账户减少金额,转入账户增加金额,肯定不允许有转出成功,转入却失败或者转出失败,转入成功的情况发生。这里整个交易过程就是一个事务,在这个事务中任意一个操作失败就都失败,所有的操作成功,整个交易过程才算成功。如下示例更好地理解事务。
新建一个像表 student 插入两条数据的存储过程:
CREATE OR REPLACE PROCEDURE PRO_TRAN
AS
BEGIN
INSERT INTO student VALUES(1006,'shiyanlou1006','woman',22);
COMMIT; --提交事务
INSERT INTO student VALUES(1007,'shiyanlou1007','woman',22);
END;
/
然后查询表中数据:
SQL> select * from student;
S_ID S_NAME S_SEX S_AGE
---------- -------------------- -------------------- ----------
1001 shiyanlou1001 man 10
1002 shiyanlou1002 woman 20
1003 shiyanlou1003 man 18
1004 shiyanlou1004 woman 40
1005 shiyanlou1005 man 17
1006 shiyanlou1006 woman 22
1007 shiyanlou1007 woman 22
可以看到数据已经插入进去。但是如果我们进入一个新的会话,登入同样的账户查询你会发现少了 1007 那条数据:
因为我们是字符界面下,所以使用 tmux 新开一个会话:
$ tmux new -s sqlplus2
$ tmux a -t sqlplus2
$ sqlplus system/Syl12345
SQL> select * from student;
S_ID S_NAME S_SEX S_AGE
---------- -------------------- -------------------- ----------
1001 shiyanlou1001 man 10
1002 shiyanlou1002 woman 20
1003 shiyanlou1003 man 18
1004 shiyanlou1004 woman 40
1005 shiyanlou1005 man 17
1006 shiyanlou1006 woman 22
这是因为我们只提交了第一条插入语句的修改结果。我们新开的这个会话只能查询到已经提交的数据。
下面我们再用一个实例说明数据的一致性。首先退出当前的会话,回到第一个会话:
$ tmux detach
然后将开始插入的数据两条数据删除:
SQL> delete from student where s_id=1006;
SQL> delete from student where s_id=1007;
SQL> commit;
然后编写如下存储过程:
CREATE OR REPLACE PROCEDURE PRO_TRAN
AS
BEGIN
INSERT INTO student VALUES(1006,'shiyanlou1006','woman',22);
INSERT INTO student VALUES(1006,'shiyanlou1007','woman',22);
COMMIT;
EXCEPTION WHEN OTHERS THEN
rollback; --如果出现错误则回滚
END;
/
这里的
COMMIT
表示两条数据都插入成功才提交。
这里故意制造了一个主键错误,我们插入的学生的学生编号相同了。所以都不会插入成功。我们来查询下结果是否如此:
SQL> select * from student;
S_ID S_NAME S_SEX S_AGE
---------- -------------------- -------------------- ----------
1001 shiyanlou1001 man 10
1002 shiyanlou1002 woman 20
1003 shiyanlou1003 man 18
1004 shiyanlou1004 woman 40
1005 shiyanlou1005 man 17
注意:我们在上面提交的都是 DML 操作,而 DDL 操作(比如创建表等)是自动提交的。
实际上 DML 语句也能自动提交,它跟 AUTOCOMMIT
这个参数有关。首先我们来查询一下 AUTOCOMMIT
的值:
SQL> show autocommit;
autocommit OFF
可以看到是关闭的。执行如下语句可以开启它,也就是当我们执行 DML 语句时会自动提交了。
SQL> set autocommit on;
接下来编写如下的存储过程来测试一下:
CREATE OR REPLACE PROCEDURE PRO_TRAN
AS
BEGIN
INSERT INTO student VALUES(1006,'shiyanlou1006','woman',22);
INSERT INTO student VALUES(1007,'shiyanlou1007','woman',22);
END;
/
然后执行存储过程:
SQL> exec pro_tran;
到第二个会话中去查询 student 表中的数据:
$ tmux a -t sqlplus2
SQL> select * from student;
S_ID S_NAME S_SEX S_AGE
---------- -------------------- -------------------- ----------
1001 shiyanlou1001 man 10
1002 shiyanlou1002 woman 20
1003 shiyanlou1003 man 18
1004 shiyanlou1004 woman 40
1005 shiyanlou1005 man 17
1006 shiyanlou1006 woman 22
1007 shiyanlou1007 woman 22
从结果中你会发现已经自动提交了。
上面的事务都比较短,如果一个事务很长,我们想要回滚的时候能够回滚到某一段的话,可以设置不同的保存点。如下示例将学习如何设置保存点以及回滚到某一个保存点。
首先我们将之前插入的数据删除:
SQL> delete from student where s_id=1006;
SQL> delete from student where s_id=1007;
SQL> commit;
然后回到第一个 会话窗口:
$ tmux detach
编写如下存储过程:
CREATE OR REPLACE PROCEDURE PRO_TRAN
AS
BEGIN
INSERT INTO student VALUES(1006,'shiyanlou1006','woman',22);
SAVEPOINT SP1; --设置第一个保存点
INSERT INTO student VALUES(1007,'shiyanlou1007','woman',22);
SAVEPOINT SP2; --设置第二个保存点
rollback to SP1; --回滚到第一个保存点
END;
/
执行以及输出结果:
SQL> select * from student;
S_ID S_NAME S_SEX S_AGE
---------- -------------------- -------------------- ----------
1001 shiyanlou1001 man 10
1002 shiyanlou1002 woman 20
1003 shiyanlou1003 man 18
1004 shiyanlou1004 woman 40
1005 shiyanlou1005 man 17
1006 shiyanlou1006 woman 22
从结果中可以看到只插入了 1006 那行数据。
最后,设置 AUTOCOMMIT
的值为 off,并且删除上面插入的数据。
SQL> set autocommit off;
SQL> delete from student where s_id=1006;
本节实验主要讲解了 Oracle 的表空间及数据文件的内容,实现对表空间和对数据文件的管理。
默认表空间
表空间的管理
临时表空间的管理
数据文件管理
Oracle 安装时会自动创建几个默认表空间,可以在 dba_tablespaces
这张表查看到默认表空间:
SQL> select tablespace_name from dba_tablespaces;
TABLESPACE_NAME
------------------------------
SYSTEM
SYSAUX
UNDOTBS1
TEMP
USERS
SYSTEM
:管理任何其他表空间。它包含数据字典,有关数据库管理信息的表和视图,编译的存储对象(如触发器,过程等)。SYSAUX
:辅助表空间。UNDOTBS1
:撤销表空间。存储的是撤销信息,可以用于恢复操作。TEMP
:临时表空间。可用于排序操作等。USERS
:存储用户的表和索引数据。
使用 dba_users
这张表可以查询用户的默认表空间。
SQL> select default_tablespace,username from dba_users;
从结果中可以发现,一个用户只能分配一个表空间,一个表空间可以被多个用户使用。
如果想查询 SYS 和 SYSTEM 这两个用户的默认表空间可以使用 where 限定查询条件:
SQL> select default_tablespace,username from dba_users where username='&u1' or username='&u2';
Enter value for u1: SYS
Enter value for u2: SYSTEM
old 1: select default_tablespace,username from dba_users where username='&u1' or username='&u2'
new 1: select default_tablespace,username from dba_users where username='SYS' or username='SYSTEM'
DEFAULT_TABLESPACE USERNAME
-------------------- ---------------
SYSTEM SYS
SYSTEM SYSTEM
可以看到 SYS 和 SYSTEM 这两个用户的默认表空间都是 SYSTEM 表空间。
表空间的类型有如下三种:
(此段来自 创建表空间-官方文档 )
下面我们来创建一个最简单的表空间:
SQL> create tablespace tp1
datafile 'tp1.dbf'
size 1M;
第一行的
tp1
是表空间的名字。第二行是表空间的数据文件是
tp1.dbf
。第三行是表空间大小为
1M
。
默认情况下,创建的表空间不会自动扩展,如果我们想要在数据文件充满时进行自动扩展,可以创建一个自动扩展的表空间,当数据文件装满后,它会自动增加数据文件的尺寸。
SQL> create smallfile tablespace tp2
datafile 'tp2.dbf'
size 10M autoextend on next 1M maxsize 20M
extent management local autoallocate
segment space management auto;
代码详细解释:
行数 | 解释 |
---|---|
1 | 表空间是 smallfile ,也就是小文件表空间,它可以包含多个数据文件或临时文件。默认下就是 smallfile ,可以省略。替换项是 bigfile ,只包含一个数据文件或临时文件。 |
2 | 数据文件的名称 |
3 | 创建的数据文件的大小是 10M,当数据文件充满时,会自动扩大 1M,最大为 20M。 |
4 | 表空间使用位图来分配区间,其大小会自动设置。这是默认的,可以不写。 |
5 | 表空间中的段使用位图来跟踪块的使用情况。这是默认的,可以不写。 |
想了解更多有关 bigfile 和 smallfile 内容可以参考 bigfile|smallfile 。
例如我们想要把 tp2
这个表空间重命名为 syl_tp
,可以使用如下语句:
SQL> alter tablespace tp2 rename to syl_tp;
注意:重命名表空间不会重命名与之关联的任何数据文件。
表空间在创建时默认是读写状态,我们可以将其设置为只读状态。
SQL> alter tablespace tp1 read only;
如果要改回读写状态,可以使用下面的语句:
SQL> alter tablespace tp1 read write;
可用状态指的是表空间的两种状态:
ONLINE
:表空间可用,可以被使用。OFFLINE
:表空间和其数据文件不可用。脱机状态还包括三种方式,即
NORMAL
(正常状态):将表空间中的所有数据文件中的所有块刷新到系统全局区域(SGA)之外。这是默认的方式。TEMPORARY
(临时状态):Oracle 数据库会为表空间中的所有联机数据文件执行检查点,但不能确保可以写入所有文件。IMMEDIATE
(立即状态):立即使表空间和数据文件脱机,不会确保表空间文件可用,并且不执行检查点。可能丢失未提交的更改。我们下面将表空间 tp1
以 normal
方式脱机:
SQL> alter tablespace tp1 offline normal;
注意:不能把一个临时表空间脱机。
再将 tp1
设置成联机状态:
SQL> alter tablespace tp1 online;
在创建表空间时,如果使用了 autoextend
,则可以自动调整数据文件的大小。如果没有使用,就需要我们手动去调整。有两种调整方式可供选择:
下面我们将表空间 tp1
的尺寸更改为 2M
:
SQL> alter database datafile 'tp1.dbf' resize 2m;
SQL> alter tablespace tp1 add datafile 'tp1_02.dbf' size 1m;
使用 v$tablespace
和 v$datafile
这两个视图可以查看到表空间的数据文件和大小:
SQL> select t.name tname,d.name dname,d.bytes from v$tablespace t join v$datafile d using(ts#) where t.name like 'TP1';
ts#
:tablespace number。
例如我们要删除 tp1
这个表空间及其数据文件:
SQL> drop tablespace tp1 including contents and datafiles;
如果表空间包含的表与另一个表空间的表存在外键关系,就会删除失败,这个时候我们可以使用 cascade constraints
将表空间中的完整性也删除:
SQL> drop tablespace syl_tp including contents cascade constraints;
使用下面的命令查看表空间会发现表空间已经被删除:
SQL> select tablespace_name from dba_data_files;
想了解更多删除表空间内容可参考 删除表空间
临时表空间一般用于在进行数据库操作时,内存不够用了,就会写入到临时表空间,操作完成后,临时表空间的内容就会自动清空。oracle 默认创建的 TEMP 表空间就是临时表空间。
例如创建一个临时表空间 tmp_sp
:
SQL> create temporary tablespace tmp_sp1 tempfile 'tmp_sp1.dbf' size 10M;
用 dba_temp_files
表可以查询到我们已经创建好了临时表空间:
SQL> select tablespace_name from dba_temp_files;
例如我们把临时表空间 tmp_sp1
设置成默认表空间:
SQL> alter database default temporary tablespace tmp_sp1;
用如下命令查看默认临时表空间可发现已经更改:
SQL> select * from database_properties where property_name='DEFAULT_TEMP_TABLESPACE';
下面把 TEMP
临时表空间设置成默认表空间:
SQL> alter database default temporary tablespace TEMP;
临时表空组至少包含一个临时表空间。下面我们来创建一个临时表空间组 tmpgroup
,并把上面创建的临时表空间 tmp_sp1
放入其中。
SQL> alter tablespace tmp_sp1 tablespace group tmpgroups;
使用如下命令可以查看到临时空间组已经创建:
SQL> select * from dba_tablespace_groups;
另外,可以在创建临时表空间的时候创建临时表空间组,如下所示:
SQL> create temporary tablespace tmp_sp2 tempfile 'tmp_sp2.dbf' size 3M tablespace group tmpgroup2;
当把一个临时表空间组里的所有临时表空间删除了,临时表空间组也被删除了。
例如我们删除临时表空间 tmp_sp2
:
SQL> drop tablespace tmp_sp2 including contents and datafiles;
因为临时表空间组 tmpgroup2
中只有一个临时表空间 tmp_sp2
,当我们删除过后,临时表空间组 tmpgroup2
也被删除了。
注意:不能删除默认的临时表空间。
oracle 12c 支持在数据文件联机状态时对其进行重命名和移动。其他版本需要先把数据文件脱机才能进行重命名和移动。
例如我们新建一个表空间,包含两个数据文件 mvdata.dbf
和 mvdata2.dbf
,然后将其更名为 syl_mvdata.dbf
:
SQL> create tablespace mvdata datafile 'mvdata.dbf' size 5m;
SQL> alter tablespace mvdata add datafile 'mvdata2.dbf' size 5m;
SQL> alter tablespace move datafile 'mvdata.dbf' to 'syl_mvdata.dbf';
移动数据文件跟重命名类似,只是将 to 后面的字符串改成你的目的位置即可。
可以使用如下命令查看当前有的数据文件:
SQL> select name from v$datafile;
比如我们删除 mvdata2.dbf
这个数据文件:
SQL> alter tablespace mvdata drop datafile 'mvdata2.dbf';
再次查看数据文件会发现已经删除。
注意:删除数据文件是有一些先决条件的:
- 数据文件中不存在数据。
- 数据文件不是表空间中唯一或第一个数据文件。
- 数据文件或数据文件所在的表空间不是只读状态。
本节实验主要讲解了 Oracle 的表操作,如何实现对表进行创建、重重命名、复制、删除和修改等。
以下是介绍一些我们最常用的数据类型:
VARCHAR2
描述(其他数据库用 VARCHAR)。例如姓名,地址等。NUMBER
描述。如果要描述小数则使用 NUMBER(p,s)
,其中 s
是小数位(scale),p-s
是整数位,例如 number(4,2)
代表存储小数位数为 2,总长度最多为 4 的浮点数。也可以用 INT
描述整数,FLOAT
描述小数。DATE
描述。Oracle 里的 DATE 包含有日期时间。其他数据库一般 DATE 只是日期,DATETIME 才表示日期时间。GLOB
描述。最多可以保存 4G
的文字信息。想了解更多数据类型可参考Oracle SQL 数据类型
语法:
CREATE TABLE 表名称(
列名称 类型 [DEFAULT 默认值]
列名称 类型 [DEFAULT 默认值]
...
)
例如:
注:前面的 SQL>
不用输入,只是为了说明是在 SQL 命令行输入内容。
SQL> CREATE TABLE student(
id NUMBER,
name VARCHAR2(20),
age number(3),
birthday DATE DEFAULT SYSDATE,
note CLOB
);
查看是否已经创建好 student 表:
SQL> select * from tab where tname='STUDENT';
查看 student 表结构:
SQL> desc student;
我们可以向其中插入数据:
SQL> INSERT INTO student(id,name,age,birthday,note) VALUES (1,'syl',19,TO_DATE('1999-01-01','yyyy-mm-dd'),'note test');
SQL> INSERT INTO student(id,name,age,birthday,note) VALUES (2,'lou',21,TO_DATE('1997-01-01','yyyy-mm-dd'),'note test');
查看我们插入的数据:
SQL> select * from student;
例如我们查询出 student 表中 name 字段值为 syl 的记录,通过查询结果创建一个 名叫 syl_stu 的表。
SQL> create table syl_stu as select * from student where name='syl';
SQL> select * from syl_stu;
SQL> RENAME student TO stu;
我们使用数据字典查询一个用户全部的数据表,可以发现表已经被更名:
SQL> select * from user_tables;
列出数据库表对象的全部信息内容,例如名称,存储情况等。
数据字典:记录所有对象的信息。
- 用户级别:user_*,指一个用户可以使用的数据字典。
- 管理员级别:dba_*,指由数据库管理员使用的数据字典。
- 全部级别:all_* ,指不管是用户还是管理员都可以使用。
首先我们使用 desc
来查看表的结构。
SQL> desc stu;
名称 空值 类型
-------- -- ------------
ID NUMBER
NAME VARCHAR2(20)
AGE NUMBER(3)
BIRTHDAY DATE
NOTE CLOB
接下来我们来修改表的结构。
例一:为 stu
表增加一列 address
,设默认值为 none。
SQL> alter table stu add(address varchar2(50) default 'none');
然后查看表中数据会发现多了 address 列,并且值为 none:
SQL> select * from stu;
例二:为 stu
表增加一列 email
,不设默认值。
SQL> alter table stu add(email varchar2(50));
再次查看表,会发现多了 email 列,并且为 null。
SQL> select * from stu;
例一:修改 stu
表的 name
列的类型为 varchar2(30)
。
SQL> alter table stu modify (name varchar2(30));
例二:修改 stu
表的 email
列的默认值为 no email
。
SQL> alter table stu modify (email default 'no email');
注意:虽然这里我们设置了默认值,但是以前插入的数据的 email
列的值仍然是 null,只有我们新插入的数据的 email
列的值才默认为 no email
,如下所示:
SQL> select * from stu;
SQL> INSERT INTO stu(id,name,age,birthday,note) VALUES (3,'plus',19,TO_DATE('1999-01-01','yyyy-mm-dd'),'note test');
SQL> select * from stu;
例:将 address
列的名称改为 saddress
。
SQL> alter table stu rename column address to saddress;
重命名列还有一个作用就是在要删除一个列的时候,用来确认是否有用户或应用程序正在使用此列,如果有的话,就会报错。
例:将 saddress
列删除。
SQL> alter table stu drop (saddress);
或者使用
SQL> alter table stu drop column saddress;
在删除一列的时候如果数据太多,通常需要一定时间,如果想快速使用户或应用程序不能访问某些列的时候,可以使用 unused
标记列。例如下面我们标记 email
列:
SQL> alter table stu set unused (email);
标记过后使用 desc stu
查看会发现没有显示 email
列,但是它并没有真正被删除,执行下面的语句可以删除所有被 unused
标记的列。
SQL> alter table stu drop unused columns;
复制表结构类似于用查询创建表,只是修改了 where
条件。例如,我们要复制 stu
的表结构。
SQL> create table stu_cp as select * from stu where 1=2;
该语句只是复制了结构,并没有复制其数据:
SQL> desc stu_cp;
SQL> select * from stu_cp;
有时候我们存储的数据需要满足某些条件,例如:假设我们定义一个学生表,表中每个学生的学号和身份证号码是不可以重复的,学生的学号不能为空等等。这就是约束,数据库上为了保证数据表中的数据完整性而增加了约束。
约束一共有六种:数据类型,非空约束,唯一约束,主键约束,检查约束,外键约束。
注意:约束太多,会导致更新速度缓慢,有些验证操作应交给应用程序去做。
非空约束(NK)指表中的某一个字段的内容不允许为空。使用 not null
声明。
例:创建一个名叫 tech
的表,表中有 id
和 name
两个字段。设置 name
字段不为空。
SQL> create table tech(id number,name varchar2(20) not null);
下面我们就来插入一条没有 name 的数据。
SQL> insert into tech(id) values (2);
你会发现报错了 ORA-01400: 无法将 NULL 插入 ("SYSTEM"."TECH"."NAME")
。
唯一约束(UK)指表中某一字段内容不允许重复。使用 unique
。
例:为表 tech
增加一列 email
,不允许重复。
SQL> alter table tech add(email varchar2(30) unique);
下面来插入两条 email 重复的数据:
SQL> insert into tech(id,name,email) values (2,'syl2','syl2@qq.com');
SQL> insert into tech(id,name,email) values (3,'syl3','syl2@qq.com');
报错 SQL 错误: ORA-00001: 违反唯一约束条件 (SYSTEM.SYS_C007385)
。这个 SYS_c007385
实际上就是约束的对象信息(约束也是数据库对象),可以从数据字典中查到。
注意:这个对象信息可能跟你的报错不一样,实际查询时,要改为你自己实际看到的对象信息才可以查询到。
-- 格式化
SQL> col owner for a10;
SQL> col constraint_name for a20;
SQL> col table_name for a15;
SQL> col column_name for a20;
-- 查询
SQL> select owner,constraint_name,table_name,column_name from user_cons_columns where constraint_name='SYS_C007385';
输出结果如下:
OWNER CONSTRAINT_NAME TABLE_NAME COLUMN_NAME
---------- -------------------- --------------- --------------------
SYSTEM SYS_C007385 TECH EMAIL
可以看到这个约束是在 TECH
表的 EMAIL
列。
这样去数据字典中查还是比较麻烦,我们可以使用下面的方式创建唯一约束,这样能够直接根据报错知道是哪一个字段违反了约束条件。例如我们增加 cid
字段,添加唯一约束。
SQL> alter table tech add(cid varchar2(30),constraint uk_email unique(email));
注意:设置的约束名称(这里是
uk_email
)不可以重复。
向里面插入数据:
SQL> insert into tech(id,name,email,cid) values (3,'syl3','syl3@qq.com',1);
SQL> insert into tech(id,name,email,cid) values (4,'syl4','syl4@qq.com',1);
报错 SQL 错误: ORA-00001: 违反唯一约束条件 (SYSTEM.UK_EMAIL)
。
主键约束(PK)= 非空约束 + 唯一约束 。主键约束在每一个数据表中只有一个,但是一个主键约束可以由数据表中多个列组成。一般我们只把一个字段定义成主键。
例:为表 tech
添加主键约束
SQL> alter table tech add constraints pk_id primary key(id);
检查约束(CK)限定数据的正确性。
例一:设定性别只能为 man
或者 female
。
SQL> alter table tech add (sex varchar2(10),constraints chk_sex check(sex='man' or sex='female'));
例二:设定年龄在 5 到 200 岁。
SQL> alter table tech add (age number(3),constraints chk_age check(age between 5 and 200));
或者
SQL> alter table tech add (age number(3),constraints chk_age check(age>=5 and age<=200));
比如我们现在有两张表,实验楼的课程表和课程类别表。一个课程是属于一个类别的。我们在课程表中增加了一个字段来描述课程的类别。这个字段的值来自于课程类别表的类别 id 。为了不允许插入类别表中类别 id 以外的值,我们就定义一个外键约束。
-- 类别表
SQL> create table syl_category(
cid number(5) primary key,
name varchar2(30)
);
-- 课程表
SQL> create table syl_course(
id number(10) primary key,
name varchar2(30),
cid number(5),
constraint fk_cate foreign key(cid) references syl_category(cid) on delete cascade
);
上面的
syl_category
表就叫父表 ,syl_course
表就叫子表 。
on delete cascade
表示当 syl_category 表中的 cid 被删除时,对应的 syl_cource 表中的 cid 也会被删除。
向其中插入数据:
SQL> insert into syl_category(cid,name) values (1,'cate1');
SQL> insert into syl_course(id,name,cid) values (1,'course1',1);
SQL> insert into syl_course(id,name,cid) values (2,'course1',2);
会发现第三句报错 SQL 错误: ORA-02291: 违反完整约束条件 (SYSTEM.FK_CATE) - 未找到父项关键字
语法:
SQL> alter table <table_name> drop constraint <constraint_name>;
例如我们删除表 tech
的 sex
字段的检查约束 chk_sex
。
SQL> alter table tech drop constraint chk_sex;
首先我们来进行普通的删除操作。
SQL> commit;
SQL> delete from stu;
查看表内容会发现已经被清空。但是在我们执行 rollback
回滚操作过后会发现数据又被恢复:
SQL> rollback;
SQL> select * from stu;
rollback 会恢复到上一次 commit 的数据。
上面的删除操作并没有立即将表所占的资源(约束,索引等)立即释放掉,如果想要完全删除,就需要使用 truncate
,也叫做截断表的操作。例如:
SQL> truncate table stu;
这个时候再执行回滚操作,会发现数据无法恢复:
SQL> rollback;
SQL> select * from stu;
例如我们要删除 syl_stu
这张表,可以使用如下命令:
SQL> drop table syl_stu;
注意:如果删除的表是含有约束的父表的话,将会报错。此时需要使用
drop table <table_name> cascade constraints
删除。
本节实验主要讲解了 SQL 的一些基础知识。
SQL 简介
SQL
创建示例表
简单查询
通配符
函数
分组排序
限制返回的行数
子查询
表的连接
视图
SQL
是 Structured Query Language
的首字母缩写,意为结构化查询语言,它可以告诉 Oracle 对哪些信息进行选择,插入,更新和删除。相信大家已经很熟悉,在 mysql 和 sqlserver 中我们也经常使用。
首先,试着用前面学到的内容创建如下 3 个示例表:
学生表(student):
字段 | 类型 | 约束 | 默认值 |
---|---|---|---|
s_id | number | 主键 | |
s_name | varchar2(20) | not null | |
s_sex | varchar2(10) | 为 man 或者 woman | man |
s_age | number | not null |
插入如下数据:
学号(s_id) | 姓名(s_name) | 性别(s_sex) | 年龄(s_age) |
---|---|---|---|
1001 | shiyanlou1001 | man | 10 |
1002 | shiyanlou1002 | woman | 20 |
1003 | shiyanlou1003 | man | 18 |
1004 | shiyanlou1004 | woman | 40 |
1005 | shiyanlou1005 | man | 17 |
课程表(course):
字段 | 类型 | 约束 |
---|---|---|
c_id | number | 主键 |
c_name | varchar(20) | not null,唯一 |
c_time | number |
插入如下数据:
课程号(c_id) | 课程名(c_name) | 课时(c_time) |
---|---|---|
1 | java | 13 |
2 | python | 12 |
3 | c | 10 |
4 | spark | 15 |
选课表(sc):
字段 | 类型 | 约束 |
---|---|---|
s_id | number | 主键,外键(来自 student 表的 s_id) |
c_id | number | 主键,外键(来自 cource 表的 c_id) |
grade | number |
插入如下数据:
学号(s_id) | 课程号(c_id) | 成绩(grade) |
---|---|---|
1001 | 3 | 70 |
1001 | 1 | 20 |
1002 | 1 | 100 |
1001 | 4 | 96 |
1002 | 2 | 80 |
1003 | 3 | 75 |
1002 | 4 | 80 |
以下是创建示例表的脚本:
CREATE TABLE student(
s_id number,
s_name VARCHAR2(20) NOT NULL,
s_sex VARCHAR2(10) DEFAULT 'man',
s_age NUMBER NOT NULL,
CONSTRAINT pk_sid PRIMARY KEY (s_id)
);
CREATE TABLE course(
c_id NUMBER,
c_name VARCHAR2(20) NOT NULL,
c_time NUMBER,
CONSTRAINT pk_cid PRIMARY KEY (c_id),
CONSTRAINT uk_cname UNIQUE (c_name)
);
CREATE TABLE sc(
s_id NUMBER,
c_id NUMBER,
grade NUMBER,
CONSTRAINT pk_scid PRIMARY KEY (s_id, c_id),
CONSTRAINT fk_sid FOREIGN KEY (s_id) REFERENCES student(s_id),
CONSTRAINT fk_cid FOREIGN KEY (c_id) REFERENCES course(c_id)
);
desc student;
desc course;
desc sc;
INSERT INTO student VALUES(1001, 'shiyanlou1001', 'man', 10);
INSERT INTO student VALUES(1002, 'shiyanlou1002', 'woman', 20);
INSERT INTO student VALUES(1003, 'shiyanlou1003', 'man', 18);
INSERT INTO student VALUES(1004, 'shiyanlou1004', 'woman', 40);
INSERT INTO student VALUES(1005, 'shiyanlou1005', 'man', 17);
INSERT INTO course VALUES(1, 'java', 13);
INSERT INTO course VALUES(2, 'python', 12);
INSERT INTO course VALUES(3, 'c', 10);
INSERT INTO course VALUES(4, 'spark', 15);
INSERT INTO sc VALUES(1001, 3, 70);
INSERT INTO sc VALUES(1001, 1, 20);
INSERT INTO sc VALUES(1002, 1, 100);
INSERT INTO sc VALUES(1001, 4, 96);
INSERT INTO sc VALUES(1002, 2, 80);
INSERT INTO sc VALUES(1003, 3, 75);
INSERT INTO sc VALUES(1002, 4, 80);
commit;
查询年龄在 20-50 岁的学生:
SQL> col s_name for a20
SQL> select * from student where s_age between 20 and 50;
输出结果如下:
S_ID S_NAME S_SEX S_AGE
---------- -------------------- -------------------- ----------
1002 shiyanlou1002 woman 20
1004 shiyanlou1004 woman 40
除了上面的 between ... and ...
,还有一些其他的运算符:
操作符 | 释义 |
---|---|
= |
等于 |
<> |
不等于 |
!= |
不等于 |
> |
大于 |
>= |
大于等于 |
< |
小于 |
<= |
小于等于 |
BETWEEN ... AND... |
检查值的范围 |
IN |
检查是否在一组值中 |
NOT IN |
检查一个值是否不在一组值中 |
IS {TRUE|FALSE} |
判断 bool 值 |
IS NULL |
NULL 值测试 |
IS NOT NULL |
NOT NULL 值测试 |
LIKE |
模式匹配 |
NOT LIKE |
否定匹配 |
另外,我们还可以使用多个表达式进行逻辑运算。逻辑运算符如下表:
逻辑运算符 | 释义 |
---|---|
OR, || |
或 |
AND, && |
与 |
NOT, ! |
非 |
例如,我们也可以通过 AND
查找年龄在 20~50
岁的学生。
SQL> SELECT * FROM student WHERE s_age>=20 AND s_age<=50;
除了上述所列举的运算符和表达式之外,我们还可以进行一些数学的计算操作,例如加减乘除等,如下示例,我们将 student
表中的 s_id
和 s_age
分别进行加减乘除操作。
SQL> SELECT s_id,s_age,s_id+s_age,s_id-s_age,s_id*s_age,s_id/s_age FROM student;
S_ID S_AGE S_ID+S_AGE S_ID-S_AGE S_ID*S_AGE S_ID/S_AGE
---------- ---------- ---------- ---------- ---------- ----------
1001 10 1011 991 10010 100.1
1002 20 1022 982 20040 50.1
1003 18 1021 985 18054 55.7222222
1004 40 1044 964 40160 25.1
1005 17 1022 988 17085 59.1176471
在上述内容中,我们有提到 LIKE
,它是一个字符串比较函数,用于 LIKE
的通配符有两个:
%
百分号,匹配任意数量的字符
_
下划线,匹配一个字符
例如,我们查看学生表中 s_name
以 2
结尾的学生信息:
SQL> SELECT * FROM student WHERE s_name LIKE '%2';
S_ID S_NAME S_SEX S_AGE
---------- -------------------- -------------------- ----------
1002 shiyanlou1002 woman 20
查找列的最大值和最小值。例如查找学生表中的年龄的最大值和最小值:
SQL> SELECT max(s_age),min(s_age) FROM student;
MAX(S_AGE) MIN(S_AGE)
---------- ----------
40 10
SUM
和 AVG
分别可以用来求和以及求平均值。
例如,查找选课表中,s_id=1001
学生成绩的总分及平均值:
SQL> SELECT avg(grade),sum(grade) FROM sc WHERE s_id='1001';
AVG(GRADE) SUM(GRADE)
---------- ----------
62 186
除此之外,我们还可以使用 DISTINCT
修饰符指定从结果集中删除重复的行,对应的是 ALL
,为默认项。通过如下示例来了解:
SQL> SELECT grade FROM sc;
GRADE
----------
70
20
100
96
80
75
80
已选择 7 行。
# 重复的 80 的记录会被删除
SQL> SELECT DISTINCT grade FROM sc;
GRADE
----------
100
70
20
96
75
80
已选择 6 行。
COUNT
函数用于计数。
例如,我们统计选课表中 s_id=1001
有多少条记录,就可以使用 count
SQL> SELECT count(s_id) FROM sc WHERE s_id=1001;
COUNT(S_ID)
-----------
3
CONCAT
是一个字符串函数。用于连接字符串。语法如下:
CONCAT(char1,char2);
意为连接字符串 char1 和 char2 。
例如:把 student
表中 s_id=1001
对应的 s_name
和 s_sex
字段连接。
SQL> SELECT CONCAT(CONCAT(s_name,'''s sex is '),s_sex) "sex" FROM student WHERE s_id=1001;
sex
-------------------------------------------------------------------
shiyanlou1001's sex is man
想了解更多有关函数的内容可以参考 SQL 函数
关于分组我们会学习到 SELECT
的两个子句,分别为:
GROUP BY
HAVING
详细的语法格式对于初学者来说并不友好,下面我们通过实例来讲解相关的内容。
分组(GROUP BY
) 功能,有时也称聚合,一些函数可以对分组数据进行操作,例如我们上述所列的 AVG
SUM
都有相关的功能,不过我们并未使用分组,所以默认使用所有的数据进行操作。
我们可以选择上面的一种进行分组,也可以重复多个,或者综合使用。
例一:我们根据字段名 s_id
进行分组并计数:
SQL> SELECT s_id,count(*) FROM sc GROUP BY s_id;
S_ID COUNT(*)
---------- ----------
1001 3
1002 3
1003 1
例二:根据 s_id
进行分组后查询学生的总成绩,使用 SUM
函数:
SQL> SELECT s_id,sum(grade) FROM sc GROUP BY s_id;
S_ID SUM(GRADE)
---------- ----------
1003 75
1001 186
1002 260
另外我们可以使用多个列进行分组。下面我们使用 s_id
以及 grade
进行分组。
SQL> SELECT s_id,grade, sum(grade) FROM sc GROUP BY s_id,grade;
S_ID GRADE SUM(GRADE)
---------- ---------- ----------
1002 100 100
1003 75 75
1001 70 70
1001 96 96
1002 80 160
1001 20 20
已选择 6 行。
除了可以对数据进行分组之外,我们还可以使用 HAVING
对分组数据进行过滤。
例如:从选课表中筛选出选课总成绩大于 100
分的学生。
SQL> SELECT s_id, sum(grade) FROM sc GROUP BY s_id HAVING sum(grade)>100;
S_ID SUM(GRADE)
---------- ----------
1001 186
1002 260
ORDER BY
用于对数据进行排序。
下面我们举一个综合示例:将选课表 sc
的数据根据 s_id
进行分组,并计算每组总成绩,然后筛选出 总成绩>100
的分组,输出结果根据总成绩进行降序排列。
SQL> SELECT s_id,sum(grade) AS sum_grade FROM sc GROUP BY s_id HAVING sum(grade)>100 ORDER BY sum(grade) DESC;
S_ID SUM_GRADE
---------- ----------
1002 260
1001 186
AS
是命别名的意思,可以省略。
DESC
代表降序排列。省略的话则是升序排列。
ROWNUM
用来限制查询返回的行数,这是一个伪列,给结果集的每一行编了一个顺序号。和 mysql
中的 limit
作用类似。
例:查询 student
表中的前两行。
SQL> SELECT * FROM student where rownum<3;
S_ID S_NAME S_SEX S_AGE
---------- -------------------- -------------------- ----------
1001 shiyanlou1001 man 10
1002 shiyanlou1002 woman 20
注意:并不能使用使用类似如下两种语句:
select * from student where rownum>2; select * from student where rownum>2 and rownum<5;
子查询又被称为嵌套查询,如下示例:
我们要查询选修了课程的课程号 c_id
为 1
的学生的年龄:
sc
中查询,选修课程号 c_id
为 1
的学生的学号:SQL> SELECT s_id FROM sc WHERE c_id=1;
S_ID
----------
1001
1002
SQL> SELECT s_id,s_age FROM student WHERE s_id IN (1001,1002);
S_ID S_AGE
---------- ----------
1001 10
1002 20
上面的查询过程分为两步,而使用子查询我们只需要一步,如下:
SQL> SELECT s_id,s_age FROM student WHERE s_id IN (SELECT s_id FROM sc WHERE c_id=1);
S_ID S_AGE
---------- ----------
1001 10
1002 20
即将第一步的查询嵌入第二步的操作中,并且将第一步查询的结果用于第二步查询的判断条件中。
类似的操作还有很多,下面给出一个使用子查询的例子,大家可以分析其代表的含义,并且考虑有没有更简单的实现方式:
SELECT * FROM student WHERE s_id IN (SELECT s_id FROM sc WHERE c_id=(SELECT c_id FROM course WHERE c_time=(SELECT max(c_time) FROM course)));
表的连接主要用于多表查询,我们先来看将所有示例表存储在一张表中会是什么样子。
学号 | 课程号 | 学生姓名 | 学生年龄 | 学生性别 | 课程名 | 课时 | 成绩 |
---|---|---|---|---|---|---|---|
1001 | 3 | shiyanlou001 | 10 | man | c | 10 | 70 |
1001 | 1 | shiyanlou001 | 10 | man | java | 13 | 20 |
1001 | 2 | shiyanlou001 | 4 | man | spark | 15 | 90 |
... | ... | ... | ... | ... | ... | ... |
大致如上所示,这里我只给出了简单的几条数据,对比将选课的信息,划分为三张表进行存储,我们不用存储更多重复的信息,明显后者要高效的多。
表的连接基于关系表,可以用来关联多个表。
我们可以使用语句实现这个三表关联的操作:
SQL> SELECT sc.s_id,sc.c_id,s_name,c_name,grade,s_age,s_sex,c_time FROM student,course,sc WHERE student.s_id=sc.s_id AND course.c_id=sc.c_id;
执行过后的显示可能会有点乱,可以使用如下命令先调整显示格式,再执行
SQL> col s_name for a20; SQL> col c_name for a15; SQL> set linesize 500;
让我们简化一些,只从每张表中列出其它表中关键的信息,如下所示,我们可以得到学生比较直观的选课信息:
SQL> SELECT sc.s_id, sc.c_id, s_name, c_name, grade FROM student, course, sc WHERE student.s_id=sc.s_id AND course.c_id=sc.c_id;
S_ID C_ID S_NAME C_NAME GRADE
---------- ---------- --------------- ---------- ----------
1001 3 shiyanlou1001 c 70
1001 1 shiyanlou1001 java 20
1001 4 shiyanlou1001 spark 96
1002 1 shiyanlou1002 java 100
1002 2 shiyanlou1002 python 80
1002 4 shiyanlou1002 spark 80
1003 3 shiyanlou1003 c 75
已选择 7 行。
笛卡儿积连接又叫交叉连接 ,是多个表之间无条件的连接,它所查询出来的结果数量是每个表的记录数量的乘积,所以查询结果非常之大,在实际中要避免笛卡尔积连接。
下面对 student
表和 course
表进行笛卡尔积连接:
SQL> select * from student,sc;
或者
SQL> select * from student cross join sc;
-- 查询结果数量
SQL> select count(*) from student,sc;
从输出结果可以看到一共有 35 条记录,是两表记录数量的乘积。
我们可以对其指定连接条件来避免此种情况:
SQL> select * from student,sc where student.s_id=sc.s_id;
注意:积依然存在,只是不显示了。
内连接(有时称为简单连接)是两个或多个表的连接,它们只返回满足连接条件的那些行。使用 INNER JOIN .... ON
。
例:将 sc
表和 student
表内连接:
SQL> SELECT sc.s_id, sc.c_id, s.s_name, sc.grade FROM sc INNER JOIN student s ON s.s_id=sc.s_id;
S_ID C_ID S_NAME GRADE
---------- ---------- --------------- ----------
1001 3 shiyanlou1001 70
1001 1 shiyanlou1001 20
1002 1 shiyanlou1002 100
1001 4 shiyanlou1001 96
1002 2 shiyanlou1002 80
1003 3 shiyanlou1003 75
1002 4 shiyanlou1002 80
已选择 7 行。
inner
可省略。
也可以使用 using
来连接:
SQL> SELECT s_id, sc.c_id, s.s_name, sc.grade FROM sc JOIN student s using(s_id);
外连接扩展了内连接的结果,将某个连接表中不符合连接条件的记录加入结果集中。外连接分为左外连接、右外连接、全外连接三种。
使用 LEFT JOIN
。会返回 LEFT JOIN
左边表查询的所有行,如果 JOIN
右边的表没有相匹配的行,会返回空。
SQL> SELECT student.s_id,s_name,c_id,grade FROM student LEFT JOIN sc ON student.s_id=sc.s_id;
S_ID S_NAME C_ID GRADE
---------- --------------- ---------- ----------
1001 shiyanlou1001 3 70
1001 shiyanlou1001 1 20
1002 shiyanlou1002 1 100
1001 shiyanlou1001 4 96
1002 shiyanlou1002 2 80
1003 shiyanlou1003 3 75
1002 shiyanlou1002 4 80
1004 shiyanlou1004
1005 shiyanlou1005
已选择 9 行。
使用 RIGHT JOIN
。会返回 RIGHT JOIN
右边表查询的所有行,如果 JOIN
左边的表没有相匹配的行,会返回空。
SQL> SELECT student.s_id,s_name,c_id,grade FROM student RIGHT JOIN sc ON student.s_id=sc.s_id;
S_ID S_NAME C_ID GRADE
---------- --------------- ---------- ----------
1001 shiyanlou1001 3 70
1001 shiyanlou1001 1 20
1001 shiyanlou1001 4 96
1002 shiyanlou1002 1 100
1002 shiyanlou1002 2 80
1002 shiyanlou1002 4 80
1003 shiyanlou1003 3 75
已选择 7 行。
使用 FULL JOIN
,会返回两表所有行,如果不满足连接条件,会返回空值。
SQL> SELECT student.s_id,s_name,c_id,grade FROM student FULL JOIN sc ON student.s_id=sc.s_id;
S_ID S_NAME C_ID GRADE
---------- --------------- ---------- ----------
1001 shiyanlou1001 3 70
1001 shiyanlou1001 1 20
1002 shiyanlou1002 1 100
1001 shiyanlou1001 4 96
1002 shiyanlou1002 2 80
1003 shiyanlou1003 3 75
1002 shiyanlou1002 4 80
1004 shiyanlou1004
1005 shiyanlou1005
已选择 9 行。
自然连接会自动根据两个表中相同数据类型,相同名称的列进行连接。使用 NATURAL JOIN
。
SQL> SELECT * FROM course NATURAL JOIN sc;
C_ID C_NAME C_TIME S_ID GRADE
---------- ---------- ---------- ---------- ----------
3 c 10 1001 70
1 java 13 1001 20
1 java 13 1002 100
4 spark 15 1001 96
2 python 12 1002 80
3 c 10 1003 75
4 spark 15 1002 80
已选择 7 行。
和下面的内连接语句输出结果一样:
SQL> select * from course inner join sc on course.c_id=sc.c_id;
向了解更多有关表连接的内容可以参考 表的连接
在上面的内容中,我们通过使用联结来获取相关的信息。如果我们需要经常使用上述查询的内容,可以通过定义视图来实现。
视图(View)是从一个或多个表(这里的表指基本表和视图)导出的表。为了区分视图和表,所以表有时又被称为“基本表”。
对于视图来说,数据库中只保存有视图的定义,而通过视图获得的数据,都来自与它相关的基本表,视图本身是没有数据的。因此,如果我们对视图的数据进行操作,其实也就是对基本表的数据进行操作,而这种操作也是有一定的限制。
创建视图使用 CREATE VIEW
。例如我们创建一个包含三张表内容的视图 all_info
。
SQL> CREATE VIEW all_info AS SELECT sc.s_id,sc.c_id,s_name,c_name,grade,s_age,s_sex,c_time FROM student,course,sc WHERE student.s_id=sc.s_id AND course.c_id=sc.c_id;
查看 all_info
视图结构以及内容:
SQL> desc all_info;
SQL> select * from all_info;
创建好了过后我们可以在数据字典 user_views
看到它:
SQL> select view_name from user_views where view_name='ALL_INFO';
VIEW_NAME
----------------------
ALL_INFO
想了解更多有关创建视图的内容可参考 创建视图
创建好了这个视图过后,我们可以利用在表中查询数据的操作来对视图内容进行查询。例如查询视图中成绩大于 80 的学生:
SQL> select * from all_info where grade>80;
删除视图使用 DROP VIEW
。如下所示,删除视图 all_info
。
SQL> drop view all_info;
在本节内容中,我们简单介绍了 MySQL 数据库的安装,服务的启动,以及怎么连接到数据库和如何修改配置。
本节实验主要是对 PL/SQL 的内容进行学习,主要会涉及到对变量、运算符、数据类型、流程控制和异常处理相关的知识点。
PL/SQL 简介
变量的声明与使用
运算符
数据类型
流程控制
异常处理
PL/SQL
是 Oracle 对 SQL 的过程化语言扩展,是一种便携式,高性能的事务处理语言。它有变量和流程控制等概念,将 SQL 的数据操作能力与过程语言的处理能力结合起来。(更多有关 PL/SQL 介绍可参考官方文档 )
PL/SQL 的结构通常如下:
DECLARE --声明部分。例如定义常量,变量,引用的函数或过程等。
BEGIN --执行部分。包含变量赋值,过程控制等。
EXCEPTION --处理异常。包含错误处理语句。
END; --结束部分。
/ /*添加这个斜杠来执行 PL/SQL 语句块。*/
上面
--
后面和/* */
包围的内容都是注释。这是 PL/SQL 的两种注释方式。BEGIN ,END 就类似于其他编程语言的
{...}
。
我们先来做几个简单实践大致了解 PL/SQL。
例一:输出 Hello World
。为了方便,后文所述内容除使用 $
特别标识外,均在 SQL
命令行输入。
SET SERVEROUTPUT ON; --默认输出显示是关闭的,需要首先打开才会显示
BEGIN
DBMS_OUTPUT.put_line('Hello World');
END;
/
输出结果如下:
PL/SQL 过程已成功完成。
Hello World
例二:声明一个变量并使用。该语句实现输出 my name is : syl
。
DECLARE
v_name varchar2(20); --定义变量
BEGIN
v_name := 'syl'; --为变量赋值
DBMS_OUTPUT.put_line('my name is : ' || v_name);
END;
/
注意:PL/SQL 中字符串连接用
||
。
例三:从键盘输入学生编号(比如输入 1001
),查询出对应的学生姓名。输出 student's name is : shiyanlou1001
。
DECLARE
v_sid NUMBER; --接收学生编号
v_sname VARCHAR2(20); --接收学生姓名
BEGIN
v_sid := &studentid; --键盘输入数据
SELECT s_name INTO v_sname FROM student WHERE s_id=v_sid; --把查询出来的值赋给变量 v_sname
DBMS_OUTPUT.put_line('student''s name is : ' || v_sname);
END;
/
经过上面的实践,相信大家已经对 PL/SQL 有了一个大概的了解。接下来我们正式进入详细的 PL/SQL 学习。
声明为指定数据类型的值分配存储空间,并命名存储位置以便引用它。
必须先声明对象,然后才能引用它们。声明可以出现在任何块,子程序或包的声明部分。
(此段引用自 PLSQL 声明-官方文档 )
声明的语法:
DECLARE
变量名称 [CONSTANT] 类型 [NOT NULL] [:=value];
- 变量名称必须遵守如下规定:
- 由
字母
,数字
,_
,$
,#
组成。- 以字母开头,不能是 Oracle 中的关键字
- 变量的长度最多为 30 个字符。
CONSTANT
是声明常量。:=value
是设置默认值。
例如:如下的变量名是不符合规定的。
me&you
2user
on/off
student id
select
例一:声明一个名叫 v_syl
的变量。
DECLARE
v_syl VARCHAR2(20);
BEGIN
NULL;
END;
/
例二:声明有默认值的变量。下列程序实现计算 v_a 和 v_b 的和。
DECLARE
v_a NUMBER :=1;
v_b NUMBER;
BEGIN
v_B := 2;
DBMS_OUTPUT.put_line(v_A+v_B);
END;
/
可以发现不区分大小写。
例三:声明一个不为空的变量。
DECLARE
v_sid NUMBER NOT NULL := 1;
BEGIN
NULL;
END;
/
注意:声明不为空的话,一定要设置默认值。不然会报错
PLS-00218: 声明为 NOT NULL 的变量必须有初始化赋值
。
除了可以声明变量,还可以声明常量。常量的初始值是其永久值。如下示例声明了两个常量。
DECLARE
v_num CONSTANT NUMBER := 1;
v_bool CONSTANT BOOLEAN := FALSE;
BEGIN
NULL;
END;
/
有时候我们想要声明与之前声明的变量或指定数据表中的某列相同数据类型的数据项,但是我们并不知道之前声明的变量的类型,这个时候就可以使用 %TYPE
。引用项目会继承如下内容:
注意:
语法:
引用项目名称 被引用项目名称%TYPE;
例:我们改写之前根据学生编号查询学生姓名的代码。让变量 v_sid
和 v_sname
分别引用表 student
的 s_id
和 s_name
的数据类型。
DECLARE
v_sid student.s_id%TYPE; --接收学生编号
v_sname student.s_name%TYPE; --接收学生姓名
BEGIN
v_sid := &studentid; --键盘输入数据
SELECT s_name INTO v_sname FROM student WHERE s_id=v_sid; --把查询出来的值赋给变量 v_sname
DBMS_OUTPUT.put_line('student''s name is : ' || v_sname);
END;
/
输入学生编号 1001
依然可以查询出对应的姓名。
使用 %ROWTYPE
属性可以声明表示数据库表或视图的全部或部分行的记录。记录字段不会继承相应列的约束和初始值。
例:改写上面根据学生编号查询学生姓名的代码。
DECLARE
v_row student%ROWTYPE;
BEGIN
SELECT * INTO v_row FROM student WHERE s_id=1001;
DBMS_OUTPUT.put_line('1001 student is : ' || v_row.s_name || v_row.s_sex || v_row.s_age);
END;
/
直接将符合条件的记录都赋给
v_row
,然后使用v_row.字段名
的方式,就可获得想要的值,很方便。
输出结果如下:
1001 student is : shiyanlou1001man10
全局变量可以在内部语句块中访问,内部变量在语句块外面访问不到。如下示例:
DECLARE
v_a VARCHAR2(50) := 'this is global'; --全局变量
v_b VARCHAR2(50) := 'this is second global';
BEGIN
DECLARE v_a VARCHAR2(50) := 'this is inner'; --内部变量
BEGIN
DBMS_OUTPUT.put_line(v_a);
DBMS_OUTPUT.put_line(v_b);
END;
DBMS_OUTPUT.put_line(v_a);
END;
/
上面程序包含在第一个 BEGIN END 中的语句块就是内部程序块。
运行结果:
this is inner
this is second global
this is global
PL/SQL 中的运算符和 SQL 中的运算符是通用的。
:=
||
-
减,+
加,*
乘,/
除赋值,连接,算术运算符很简单,我们都在前面使用过了。下面直接讲关系运算符和逻辑运算符。
下面做一个简单分类。
分类 | 运算符 | 说明 |
---|---|---|
简单关系运算符 | >, <, >= ,<=, =, !=, <> | 大于,小于,大于等于,小于等于,等于。!= 和 <> 都表示不等于。 |
判断空值 | IS NULL ,IS NOT NULL |
判断某列内容是否是 NULL |
范围查询 | BETWEEN 最小值 AND 最大值 |
在指定的最小值和最大值的范围内查找 |
范围查询 | IN |
指定查询的范围 |
模糊查询 | LIKE |
模糊查询 |
如下实例综合使用了某些关系运算符。
DECLARE
v_a NUMBER :=1;
v_b NUMBER :=2;
v_c NUMBER;
v_d VARCHAR2(20);
BEGIN
IF v_a<v_b THEN --判断 v_a 是否小于 v_b
DBMS_OUTPUT.put_line(v_a || ' < ' || v_b);
END IF;
IF v_c IS NULL THEN --判断 v_c 是否为空
DBMS_OUTPUT.put_line('v_c is null');
END IF;
IF v_b BETWEEN 1 AND 3 THEN --判断 v_b 是否在 1 到 3 之间
DBMS_OUTPUT.put_line('v_b is between 1 and 3');
END IF;
IF v_b IN(1,2,3) THEN --判断 v_b 是否在 (1,2,3)里
DBMS_OUTPUT.put_line('v_b is : ' || v_b);
END IF;
IF v_d LIKE 'shi%' THEN --判断 v_d 是否是 shi 开头
DBMS_OUTPUT.put_line(v_d);
END IF;
END;
/
输出结果:
1 < 2
v_c is null
v_b is between 1 and 3
v_b is : 2
逻辑运算符 AND
,OR
,NOT
。下面是逻辑真值表。
x | y | x AND y | x OR y | NOT x |
---|---|---|---|---|
TRUE |
TRUE |
TRUE |
TRUE |
FALSE |
TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
TRUE |
NULL |
NULL |
TRUE |
FALSE |
FALSE |
TRUE |
FALSE |
TRUE |
TRUE |
FALSE |
FALSE |
FALSE |
FALSE |
TRUE |
FALSE |
NULL |
FALSE |
NULL |
TRUE |
NULL |
TRUE |
NULL |
TRUE |
NULL |
NULL |
FALSE |
FALSE |
NULL |
NULL |
NULL |
NULL |
NULL |
NULL |
NULL |
(此表来自于 逻辑运算符-官方文档 )
如下实例综合使用了逻辑运算符:
DECLARE
v_b1 BOOLEAN := TRUE;
v_b2 BOOLEAN := FALSE;
v_b3 BOOLEAN := TRUE;
BEGIN
IF v_b1 AND v_b3 THEN
DBMS_OUTPUT.put_line('v_b1 AND v_b3 is true');
END IF;
IF NOT v_b2 THEN
DBMS_OUTPUT.put_line('v_2 is false');
END IF;
IF v_b1 OR v_b2 THEN
DBMS_OUTPUT.put_line('v_b1 OR v_b2 is true');
END IF;
END;
/
输出结果:
v_b1 AND v_b3 is true
v_2 is false
v_b1 OR v_b2 is true
每个 PL/SQL 常数,变量,参数,和函数的返回值都具有数据类型,以确定其存储格式以及有效的值和操作。
Oracle 中提供的数据类型有四种:
上面只是介绍了数据类型的种类,而我们常用的是标量类型。主要学习标量类型。
注意:PL/SQL 数据类型包括 SQL 数据类型。丹它们的最大尺寸有所不同。可参见 PL/SQL 数据类型-官方文档 。
NUMBER
BINARY_INTEGER
,PLS_INTEGER
BINARY_DOUBLE
,BINARY_FLOAT
BINARY_INTEGER
和 PLS_INTEGER
对比 NUMBER
:
NUMBER
以十进制存储,计算的时候会先转换成二进制。而 BINARY_INTEGER
和 PLS_INTEGER
以二进制的补码存储。BINARY_INTEGER
和 PLS_INTEGER
是相同的。当操作的数值超出定义的范围会抛出异常。如下示例:
DECLARE
p1 PLS_INTEGER := 2147483647;
p2 PLS_INTEGER := 1;
n NUMBER;
BEGIN
n := p1 + p2;
END;
/
两数想加的结果为 2147483648
超出了 PLS_INTEGER
的范围 -2147483648~2147483647 的整数
。即使我们把结果给了 NUMBER
类型,但仍然会抛出数字溢出
的异常。
将 p2 声明为 INTEGER
类型可以正确计算出结果:
DECLARE
p1 PLS_INTEGER := 2147483647;
p2 INTEGER := 1;
n NUMBER;
BEGIN
n := p1 + p2;
END;
/
同样 BINARY_FLOAT
,BINARY_DOUBLE
的计算性能比 NUMBER
更高。前者是单精度类型,后者是双精度类型。主要用于科学计算。
下面示例输出这两种类型的值。
DECLARE
v_float BINARY_FLOAT := 6666.66F;
v_double BINARY_DOUBLE :=6666.66F;
BEGIN
DBMS_OUTPUT.put_line(v_float);
DBMS_OUTPUT.put_line(v_double);
END;
/
输出结果如下:
6.66666016E+003
6.66666015625E+003
它是用科学记数法的方式存储的。
Oracle 中预定义了一些 BINARY_FLOAT
和 BINARY_DOUBLE
常量,比如无穷大,最小绝对数等等。可参见 表3-2 预定义常量-官方文档 。
例如输出 BINARY_FLOAT
最大值,最小值:
BEGIN
DBMS_OUTPUT.put_line('BINARY_FLOAT_MIN_NORMAL = ' || BINARY_FLOAT_MIN_NORMAL);
DBMS_OUTPUT.put_line('BINARY_FLOAT_MAX_NORMAL = ' || BINARY_FLOAT_MAX_NORMAL);
END;
/
输出结果:
BINARY_FLOAT_MIN_NORMAL = 1.17549435E-038
BINARY_FLOAT_MAX_NORMAL = 3.40282347E+038
CHAR
与 VARCHAR2
NCHAR
与 NVARCHAR2
LONG
与 LONG RAW
ROWID
与 UROWID
CHAR
以定长方式保存字符串。若赋值长度不足其定义长度,会以空格补充。VARCHAR2
变长字符串。若不足定义长度,不会补充内容。如下实例说明了上述区别。
DECLARE
v_char CHAR(5);
v_varchar2 VARCHAR2(5);
BEGIN
v_char := 'SYL';
v_varchar2 := 'SYL';
DBMS_OUTPUT.put_line(v_char || ' length: ' || LENGTH(v_char));
DBMS_OUTPUT.put_line(v_varchar2 || ' length: ' || LENGTH(v_varchar2));
END;
/
输出结果:
SYL length: 5
SYL length: 3
UNICODE
编码。也就是说中文是占一位的。比如我们定义的是 CHAR(3)
,给它赋值为实验楼会报错,因为一个中文占两位。而定义为 NCHAR(3)
,则不会报错。
用如下实例说明上述区别:
DECLARE
v_long LONG;
v_longraw LONG RAW;
BEGIN
v_long := 'SYL';
v_longraw := UTL_RAW.cast_to_raw('SYL'); --转换为二进制
DBMS_OUTPUT.put_line(v_long || ' length: ' || LENGTH(v_long));
DBMS_OUTPUT.put_line(v_longraw || ' length: ' || LENGTH(v_longraw));
DBMS_OUTPUT.put_line(UTL_RAW.cast_to_varchar2(v_longraw) || ' length: ' || LENGTH(v_longraw)); --转换为 varchar2
END;
/
UTL_RAW.cast_to_raw
和UTL_RAW.cast_to_varchar2
是字符转换。
输出结果:
SYL length: 3
53594C length: 6
SYL length: 6
如下实例输出学生编号为 1001
的那条记录的 ROWID
和 UROWID
。
DECLARE
v_rowid ROWID;
v_urowid UROWID;
BEGIN
SELECT ROWID INTO v_rowid FROM student WHERE s_id=1001;
SELECT ROWID INTO v_urowid FROM student WHERE s_id=1001;
DBMS_OUTPUT.put_line('v_rowid = '||v_rowid || ' v_urowid = '||v_urowid);
END;
/
输出结果如下:
v_rowid = AAAR9rAABAAAZAJAAA v_urowid = AAAR9rAABAAAZAJAAA
DATE 类型用于存储日期和时间。
范围:公元前 4712 年 1 月 1 日到公元后 9999 年 12 月 31 日
占据空间:7 字节
形式类似于 2017-1-1 06:06:06
。
可以设置其日期和时间的表示格式,由 NLS_DATE_FORMAT
和 NLS_DATE_LANGUAGE
这两个初始参数控制。
如下实例输出当前系统时间。
DECLARE
v_d1 DATE := SYSDATE;
v_d2 DATE := SYSTIMESTAMP;
BEGIN
DBMS_OUTPUT.put_line(TO_CHAR(v_d1,'yyyy-mm-dd hh12:mi:ss'));
DBMS_OUTPUT.put_line(TO_CHAR(v_d1,'yyyy-mm-dd hh24:mi:ss'));
DBMS_OUTPUT.put_line(SYSDATE);
DBMS_OUTPUT.put_line(SYSTIMESTAMP);
END;
/
输出结果:
2018-01-27 05:17:13
2018-01-27 17:17:13
27-1月 -18
27-1月 -18 05.17.13.447000000 下午 +08:00
如下实例设置初始参数来设置日期和时间。
select * from nls_session_parameters;
NLS_DATE_FORMAT
和 NLS_DATE_LANGUAGE
初始参数设置:SHOW PARAMETER NLS_DATE_LANGUAGE;
SHOW PARAMETER NLS_DATE_FORMAT;
输出结果:
NAME TYPE VALUE
----------------- ------ ------------------
nls_date_language string SIMPLIFIED CHINESE
NAME TYPE VALUE
--------------- ------ ---------
nls_date_format string DD-MON-RR
ALTER SESSION SET NLS_DATE_FORMAT='yyyy-mm-dd hh24:mi:ss';
SELECT SYSDATE FROM DUAL;
输出结果:
2018-01-27 17:46:12
想要设置数据库系统的日期,时间格式,用
ALTER SYSTEM
。
TIMESTMP
表示的时间更精确。用 TIMESTMP
声明的变量要用 SYSTIMESTMP
赋值。
TIMESTAMP
有两个扩展的子类型:
TIME ZONE
:包含与格林威治时间的偏移量。LOCAL TIME ZONE
:使用当前数据库的时区。例:将两种类型的值分别输出,以观察它们的区别。
DECLARE
v_timezone TIMESTAMP WITH TIME ZONE := SYSTIMESTAMP;
v_localtime TIMESTAMP WITH LOCAL TIME ZONE := SYSTIMESTAMP;
BEGIN
DBMS_OUTPUT.PUT_LINE(v_timezone);
DBMS_OUTPUT.PUT_LINE(v_localtime);
END;
/
输出结果:
01-2月 -18 05.57.06.444000 下午 +08:00
01-2月 -18 05.57.06.444000 下午
用来表示两个时间戳之间的时间间隔。它有两种子类型:
INTERVAL YEAR[(年的精度)] TO MONTHS
:默认年的精度为 2INTERVAL DAY[(天的精度)] TO SECOND[(秒的精度)]
:默认天的精度为 2,秒的精度为 6 例一:计算五年零一个月后的日期和时间。
DECLARE
v_interval INTERVAL YEAR(3) TO MONTH := INTERVAL '5-1' YEAR TO MONTH;
BEGIN
DBMS_OUTPUT.PUT_LINE(v_interval); --输出时间间隔
DBMS_OUTPUT.PUT_LINE(v_interval+SYSDATE); --输出计算后的日期和时间
END;
/
输出结果:
+005-01
2023-03-01 18:22:38
例二:计算 5 天 11 小时 11 分钟 11.1111 秒后的日期和时间。
DECLARE
v_interval2 INTERVAL DAY(3) TO SECOND := INTERVAL '5 11:11:11.1111' DAY TO SECOND;
BEGIN
DBMS_OUTPUT.PUT_LINE(v_interval2);
DBMS_OUTPUT.PUT_LINE(v_interval2+SYSDATE);
END;
/
输出结果:
+005 11:11:11.111100
2018-02-07 05:33:49
用 BOOLEAN
表示, 主要用于逻辑判断,可以存储 TRUE
,FALSE
,NULL
。之前已经使用过了。不做过多介绍。
子类型是基于标量类型的,相当于是一个类型的别名,使之简单化。子类型的创建语法如下:
SUBTYPE 子类型名称 IS 父类型名称[(约束)] [NOT NULL]
例:创建一个子类型并使用。
DECLARE
SUBTYPE name_subtype IS VARCHAR2(20) NOT NULL; --定义子类型
v_name name_subtype := 'syl'; --使用子类型
BEGIN
DBMS_OUTPUT.PUT_LINE(v_name);
END;
/
输出结果:
syl
如果满足某种条件,就执行某种操作。IF 语句有如下几种形式
IF ...THEN...
IF ... THEN ... ELSE ...
IF ... THEN ... ELSIF ...
例:实现判断编号为 1003
这个学生的平均成绩是否大于 60,如果大于 60 则输出 pass ,如果大于 30 小于 60 则输出 lost ,如果小于 30 则输出 fail 。
DECLARE
v_grade sc.grade%TYPE;
BEGIN
SELECT AVG(grade) INTO v_grade FROM student s JOIN sc USING(s_id) GROUP BY s_id HAVING s_id=1003;
IF v_grade >= 60 THEN
DBMS_OUTPUT.put_line('pass '||v_grade);
ELSIF v_grade>=30 AND v_grade<60 THEN
DBMS_OUTPUT.put_line('loss '||v_grade);
ELSE
DBMS_OUTPUT.put_line('fail '||v_grade);
END IF;
END;
/
输出结果:
pass 75
多条件判断。语法如下:
CASE selector
WHEN selector_value THEN statements_1;
...
END CASE
例:判断编号为 1003
的学生性别,如果为 man
就输出学生姓名和性别。
DECLARE
v_name student.s_name%TYPE;
v_sex student.s_sex%TYPE;
BEGIN
SELECT s_name,s_sex INTO v_name,v_sex FROM student WHERE s_id=1003;
CASE v_sex
WHEN 'man' THEN
DBMS_OUTPUT.put_line(v_name|| ' is man');
WHEN 'woman' THEN
DBMS_OUTPUT.put_line(v_name ||'is woman');
ELSE
DBMS_OUTPUT.put_line('dont know');
END CASE;
END;
/
输出结果:
shiyanlou1003 is man
语法:
WHILE (循环结束条件)LOOP
循环执行的语句块;
END LOOP;
例:输出1,2,5 。
DECLARE
v_i NUMBER := 1;
BEGIN
WHILE(v_i <= 5) LOOP --当 v_i <=3 时
DBMS_OUTPUT.put_line(v_i);
v_i := v_i+1; --v_i 加1
END LOOP;
END;
/
还可以使用如下语句实现相同效果。
DECLARE
v_i NUMBER := 1;
BEGIN
LOOP
DBMS_OUTPUT.put_line(v_i);
EXIT WHEN v_i>=5; --当 v_i>=3 时退出循环
v_i := v_i+1;
END LOOP;
END;
/
语法:
FOR 循环索引 IN [REVERSE] 循环区域下限 循环区域上限 LOOP
循环执行的语句块;
END LOOP;
例:用 FOR 实现循环输出 1 到 5 。
DECLARE
v_i NUMBER :=1;
BEGIN
FOR v_i IN 1 .. 5 LOOP
DBMS_OUTPUT.put_line(v_i);
END LOOP;
END;
/
EXIT
会直接退出循环。
DECLARE
v_i NUMBER :=1;
BEGIN
FOR v_i IN 1 .. 5 LOOP
IF v_i = 3 THEN
EXIT;
END IF;
DBMS_OUTPUT.put_line(v_i);
END LOOP;
END;
/
输出结果:
1
2
CONTINUE
退出当前语句块。
DECLARE
v_i NUMBER :=1;
BEGIN
FOR v_i IN 1 .. 5 LOOP
IF v_i = 3 THEN
CONTINUE;
END IF;
DBMS_OUTPUT.put_line(v_i);
END LOOP;
END;
/
输出结果:
1
2
4
5
无条件跳转到某个地方。不建议使用。
DECLARE
v_i NUMBER :=1;
BEGIN
FOR v_i IN 1 .. 5 LOOP
IF v_i = 3 THEN
GOTO flag; --GOTO 跳转
END IF;
DBMS_OUTPUT.put_line(v_i);
END LOOP;
<<flag>> --定义了要跳转的地方。
DBMS_OUTPUT.put_line('goto');
END;
/
输出结果:
1
2
goto
当出现异常时,程序会中断执行,如果我们写了异常处理,程序将会捕获异常,在捕获到异常后抛出异常,程序继续执行。
语法:
EXCEPTION
WHEN 异常类型 | 用户自定义异常 | 异常代码 | OTHERS THEN
异常处理语句;
例一:捕获除数为 0 的异常。
DECLARE
v_a NUMBER := 1;
v_b NUMBER := 0;
BEGIN
v_a := v_a/v_b;
EXCEPTION
WHEN ZERO_DIVIDE THEN --捕获除数为 0 的异常
DBMS_OUTPUT.put_line('zero divide');
DBMS_OUTPUT.put_line(SQLCODE); --输出异常编号
DBMS_OUTPUT.put_line(SQLERRM); --输出异常详情
END;
/
上面的
ZERO_DIVIDE
是捕获除数为 0 的异常。这是 Oracle 中预定义的异常名,可以直接使用,还有很多的预定义异常,可参考 表11-3 预定义的异常-官方文档 。
输出结果:
zero divide
-1476
ORA-01476: 除数为 0
例二:从键盘输入学生编号,查询出对应的学生姓名。当我们输入的学生编号在表中不存在时抛出异常。
DECLARE
v_sid NUMBER; --接收学生编号
v_sname VARCHAR2(20); --接收学生姓名
BEGIN
v_sid := &studentid; --键盘输入数据
SELECT s_name INTO v_sname FROM student WHERE s_id=v_sid; --把查询出来的值赋给变量 v_sname
DBMS_OUTPUT.put_line('student''s name is : ' || v_sname);
EXCEPTION
WHEN no_data_found THEN --捕获不存在的异常
DBMS_OUTPUT.put_line('not found the student');
END;
/
当输入 1006
的时候会抛出此异常。
例三:捕获返回行数过多的异常。比如我们在这里查询年龄大于 10 岁的学生,会返回多个记录。
DECLARE
v_sname VARCHAR2(20); --接收学生姓名
BEGIN
SELECT s_name INTO v_sname FROM student WHERE s_age>10; --把查询出来的值赋给变量 v_sname
DBMS_OUTPUT.put_line('student''s name is : ' || v_sname);
EXCEPTION
WHEN TOO_MANY_ROWS THEN --抛出返回行数过多的异常
DBMS_OUTPUT.put_line('too many rows');
END;
/
输出结果:
too many rows
这么多的异常名我们可能很难全部记住,在处理异常的时候可以使用 OTHERS
替代。比如上面的程序我们可以这样写。
DECLARE
v_sname VARCHAR2(20); --接收学生姓名
BEGIN
SELECT s_name INTO v_sname FROM student WHERE s_age>10; --把查询出来的值赋给变量 v_sname
DBMS_OUTPUT.put_line('student''s name is : ' || v_sname);
EXCEPTION
WHEN OTHERS THEN --使用 OTHERS 捕获
DBMS_OUTPUT.put_line(SQLCODE || ':' || SQLERRM);
END;
/
上面都是使用的 Oracle 已经定义好的异常名,除此之外,我们仍可以自定义异常 。
DECLARE
v_a NUMBER := 1;
v_exception EXCEPTION; --声明异常
BEGIN
IF v_a = 1 THEN
RAISE v_exception; --抛出异常
END IF;
EXCEPTION
WHEN v_exception THEN --捕获异常
DBMS_OUTPUT.put_line('exception:is 1');
DBMS_OUTPUT.put_line(SQLCODE);
DBMS_OUTPUT.put_line(SQLERRM);
END;
/
上面的异常捕获也可以使用
WHEN OTHERS THEN
。使用OTHERS
虽然方便,但是会捕获所有的异常一起处理,不能分开处理。
输出结果:
PL/SQL 过程已成功完成。
exception:is 1
1
User-Defined Exception
上面的 SQLCODE
是 1,我们是可以自定义这个 SQLCODE
的。如下示例自定义 SQLCODE
为 -6666 。(定义的编码可以是已经有的编码)
注意:定义的编码必须要范围在 -20000~-20999
。
DECLARE
v_a NUMBER := 1;
v_exception EXCEPTION; --声明异常
PRAGMA EXCEPTION_INIT(v_exception,-20666); --自定义异常代码
BEGIN
IF v_a = 1 THEN
RAISE v_exception; --抛出异常
END IF;
EXCEPTION
WHEN v_exception THEN --捕获异常
DBMS_OUTPUT.put_line('exception:is 1');
DBMS_OUTPUT.put_line(SQLCODE);
DBMS_OUTPUT.put_line(SQLERRM);
END;
/
输出结果:
exception:is 1
-6666
ORA-06666:
直接在程序块中使用 RAISE_APPLICATION_ERROR
抛出对应异常。
DECLARE
v_a NUMBER := 1;
v_exception EXCEPTION; --声明异常
PRAGMA EXCEPTION_INIT(v_exception,-20666);
BEGIN
IF v_a = 1 THEN
RAISE_APPLICATION_ERROR(-20666,'raise application error'); --抛出异常。此编码一定要和定义的编码一样。
END IF;
EXCEPTION
WHEN v_exception THEN --捕获异常。此名字一定要和声明的异常名一样。
DBMS_OUTPUT.put_line(SQLCODE);
DBMS_OUTPUT.put_line(SQLERRM);
END;
/
输出结果:
PL/SQL 过程已成功完成。
-20666
ORA-20666: raise application error
上面的程序实际上可以简化成下面这样,只使用一个 RAISE_APPLICATION_ERROR
,然后用 OTHERS
捕获,省去了声明异常以及设置编号。
DECLARE
v_a NUMBER := 1;
--v_exception EXCEPTION; --声明异常
--PRAGMA EXCEPTION_INIT(v_exception,-20666);
BEGIN
IF v_a = 1 THEN
RAISE_APPLICATION_ERROR(-20666,'raise application error'); --抛出异常
END IF;
EXCEPTION
WHEN OTHERS THEN --捕获异常
DBMS_OUTPUT.put_line(SQLCODE);
DBMS_OUTPUT.put_line(SQLERRM);
END;
/
本节实验主要讲解了对 Oracle 的安全性管理,包括了如何进行用户管理、用户授权管理、以及角色、概要文件的相关管理操作。
用户管理
用户权限管理
角色管理
概要文件管理
我们在之前连接数据库实例的时候,多是使用的 system 这个用户去连接,实际上我们还可以创建其他用户。
创建用户的语法参见 CREATE USER-官方文档
在创建用户的时候通常会指定如下信息:
例一:如下用最简单的方式创建一个用户名为 syl
,密码为 shiyanlou
的用户,也就是只指定用户名和密码:
SQL> create user syl identified by shiyanlou;
用户已创建。
注意:用户名需要遵守的规定:
- 由
字母
,数字
,_
,$
,#
组成。- 以字母开头,不能是 Oracle 中的关键字
- 变量的长度最多为 30 个字符。
- 字母区分大小写,但所有字母自动转换为大写。
查看是否创建成功:
SQL> select * from dba_users where lower(username)='syl';
可以看到
syl
用户的默认表空间是USERS
。使用如下命令可以查看创建时,默认分配的表空间SQL> select property_name,property_value from database_properties where property_name like '%TABLESPACE%';
例二:创建一个验证方式是外部方式验证的用户 syl2
,默认表空间设为 SYLTP1
。
首先新建一个名叫 syltp1
的表空间:
SQL> create tablespace syltp1 datafile 'syltp1.dbf' size 10M;
然后创建用户:
SQL> create user syl2 identified externally
default tablespace SYLTP1
quota 1m on SYLTP1
TEMPORARY TABLESPACE TMP_SP1
PROFILE default;
代码详细解释:
行数 | 说明 |
---|---|
1 | externally 指定验证方式为外部方式,也就是通过操作系统或第三方服务等外部服务进行验证。还有一些其他的验证方式,可参见 CREATE USER-官方文档 |
2 | 默认的表空间是 SYLTP1 。这个表空间是之前在表空间学习中创建的。 |
3 | 用户在 SYLTP1 表空间中使用的磁盘最大为 1MB 。 |
4 | 默认的临时表空间是 TMP_SP1 。这是之前我们在表空间学习中创建的临时表空间。 |
5 | 使用的配置文件是 default 。可以使用命令 select * from dba_profiles 查看有哪些配置文件(也叫概要文件)。 |
查看是否创建成功:
SQL> select * from dba_users where username='SYL2';
修改用户信息跟创建用户差不多,只是更改为 ALTER USER
。
例一:修改名为 syl 用户的密码。
SQL> alter user syl identified by newsyl;
例二:更改 syl 用户的默认表空间为 SYLTP1
。
SQL> alter user syl default tablespace SYLTP1;
使用 DROP USER
即可删除用户,例如删除名为 syl2 的用户:
SQL> drop user syl2 cascade;
cascade
将用户下所有的数据文件也一起删除。
为了保证数据库的数据安全,必然会控制每个用户的权限。权限分为两种:
系统权限有很多,下表列出了几个常用的系统权限,如果想查看更多的系统权限,可参见 表18-1-授权 。
系统权限 | 说明 |
---|---|
CREATE|RESTRICTED SESSION |
允许用户进行连接 | 使用 STARTUP RESTRICT 启动实例后登录。 |
ALTER DATABASE|SYSTEM |
更改数据库 | 允许使用 ALTER SYSTEM 控制参数和内存结构。 |
CREATE TABLESPACE|TABLE |
允许创建表空间 | 允许创建表 |
CREATE|DROP|SELECT|INSERT|UPDATE|DELETE ANY TABLE |
允许对其他用户(除了SYS)的表执行这些 DDL 和 DML 操作。 |
GRANT ANY OBJECT PRIVILEGE |
授予对象所有者被允许授予的任何对象特权。 |
例一:授予 syl 用户 create session
的系统权限。
--授权
SQL> grant create session to syl;
--查看是否授权成功
SQL> select * from dba_sys_privs where grantee='SYL';
例二:授予用户除 SELECT ANY DICTIONARY
, ALTER DATABASE LINK
, ALTER PUBLIC DATABASE LINK
权限以外的所有系统权限。
SQL> grant all privileges to syl;
例三:让 syl 用户具有授予 create session
的权限。
SQL> grant create session to syl with admin option;
指定
with admin option
让用户具有授予某权限的权限。
对象权限也有很多,对象权限是对对象授权,也就是表,视图等。了解全部对象权限可参见表18-2-对象权限 。
例一:授予用户 syl 查询 system 用户的 student 表的权限。
--授权
SQL> grant select on system.student to syl;
--查询
SQL> select * from dba_tab_privs where grantee='SYL';
加上
with grant option
可以使指定用户允许给其他用户对象授权。使用
grant all on
可以授予用户所有的对象权限。上面的
system
实际上是模式名,模式名跟用户名是一样的。
例:撤销 syl 用户的授予的所有系统权限。
SQL> revoke all privileges from syl;
例:撤销 syl 查询 system 用户的 student 表的权限。
SQL> revoke select on system.student from syl;
角色不属于用户,它是独立的。角色实际上是一组权限。
当我们要授予很多用户相同的权限,如果对一个一个用户去授权,工作量就会很大,而直接创建一个包含一组权限的角色,给用户赋予这个角色就显得轻松许多。
另一方面,当我们需要改变一个用户权限的时候,使用直接授权的方式,依然很麻烦。而直接更改用户的角色就很容易。就像为一个员工安排职位一样,不同的职位拥有不同的权限,要更改用户的权限,直接改变员工的职位。
创建角色的语法参见创建角色-官方文档 。下面我们以之前创建的学生选课的三个表为例。我们肯定不希望任何人都能随意更改几张表的数据,随意创建表等,所以我们创建这样的三种角色。对于学生,我们希望他只能查询。对于管理员,他除了能查询以外还可以对表中数据进行增删改。对于超级管理员,他除了能做管理员能做的事之外,还可以创建删除表。
例一:创建一个能连接实例并且只能对 student 表,sc 表,course 表进行查询的角色 user_sc
。
create role user_sc;
grant create session to user_sc;
grant select on system.student to user_sc;
grant select on system.sc to user_sc;
grant select on system.course to user_sc;
在创建的时候可以如同创建用户一样,给角色设置密码验证等验证方式。
例二:创建一个角色 admin_sc
,它能连接实例并且能对 student 表,sc 表,course 表进行增删改查并且能把 user_sc 角色授权给其他角色 。
create role admin_sc; --创建角色,角色名为 admin_sc
--将 user_sc 角色的权限授予 admin_sc 并且 admin_sc 角色可以给 user_sc 角色授予权限。
grant user_sc to admin_sc with admin option;
grant delete,insert,update on system.student to admin_sc;
grant delete,insert,update on system.sc to admin_sc;
grant delete,insert,update on system.course to admin_sc;
例三:创建一个超级角色,它可以控制表中所有内容并且能够创建和删除表。
create role super_sc;
grant admin_sc to super_sc with admin option;
grant create any table,drop any table to super_sc;
grant all on system.student to super_sc;
grant all on system.sc to super_sc;
grant all on system.course to super_sc;
角色创建好后可以使用如下命令查看,这里我们查看用户 system (也就是我们现在登录的这个用户)的角色:
SQL> select granted_role,default_role from dba_role_privs where grantee='SYSTEM';
GRANTED_ROLE DEFAUL
------------------------------ ------
SUPER_SC YES
ADMIN_SC YES
USER_SC YES
AQ_ADMINISTRATOR_ROLE YES
DBA YES
可以看到我们创建的角色默认授予给了我们创建角色的这个用户。
角色创建过后,就可以将角色授予给用户了,并且一个用户可以拥有多个角色。我们下面创建四个用户,分别授予不同的角色。(冒号后面是授予的角色)
创建用户的命令如下:
create user syl_stu1 identified by sylstu1;
create user syl_stu2 identified by sylstu2;
create user syl_admin identified by syladmin;
create user syl_super identified by sylsuper;
--查看
select * from dba_users order by created desc;
授予角色的命令如下:
grant user_sc to syl_stu1;
grant user_sc,admin_sc to syl_stu2;
grant admin_sc to syl_admin;
grant super_sc to syl_super;
--查看
select * from dba_role_privs;
接下来我们登录 syl_stu2 这个用户查询,增加和删除 student 表的数据,看能否成功。
--用 syl_stu2 用户连接实例
SQL> conn syl_stu2/sylstu2
已连接。
--查询 student 表数据
SQL> select * from system.student;
S_ID S_NAME S_SEX S_AGE
---------- -------------------- ---------- ----------
1001 shiyanlou1001 man 10
1002 shiyanlou1002 woman 20
1003 shiyanlou1003 man 18
1004 shiyanlou1004 woman 40
1005 shiyanlou1005 man 17
--插入一条数据
SQL> insert into system.student values(1006,'shiyanlou1006','woman',22);
已创建 1 行。
--查询表中数据可以看到插入了一条数据
SQL> select * from system.student;
S_ID S_NAME S_SEX S_AGE
---------- -------------------- ---------- ----------
1001 shiyanlou1001 man 10
1002 shiyanlou1002 woman 20
1003 shiyanlou1003 man 18
1004 shiyanlou1004 woman 40
1005 shiyanlou1005 man 17
1006 shiyanlou1006 woman 22
已选择 6 行。
--删除刚才插入的数据
SQL> delete from system.student where s_id=1006;
已删除 1 行。
如果我们想让 syl_stu2 这个用户只能查询数据怎么办。这个时候可以设置 syl_stu2 用户只有 user_sc
角色生效。
--设置只 user_sc 生效
SQL> set role user_sc;
--插入数据报错权限不足
SQL> insert into system.student values(1006,'shiyanlou1006','woman',22);
insert into system.student values(1006,'shiyanlou1006','woman',22)
*
第 1 行出现错误:
ORA-01031: 权限不足
设置角色生效和失效还有如下几种方法:
SET ROLE ALL
:设置用户所有角色都生效。SET ROLE ALL EXCEPT <role name>
:除了这个角色以外的所有角色都生效。SET ROLE NONE
:设置所有角色都失效。
修改角色同用户修改类似。首先我们以 system 用户连接实例,因为我们现在的 syl_stu2 没有修改角色的权限。
SQL> conn system/Syl12345
接下来的几个实例将学习如何修改角色。
例一:给角色设置密码以及撤销密码。
--给 user_sc 角色设置密码 usersc,会报错,因为其被授予了其他角色
SQL> alter role user_sc identified by usersc;
--给 super_sc 角色设置密码 supersc。
SQL> alter role super_sc identified by supersc;
Role SUPER_SC已变更。
--撤销 super_sc 的密码
SQL> alter role super_sc not identified;
Role SUPER_SC已变更。
例二:给 user_sc 角色增加向表 student 插入数据的权限。
SQL> grant insert on system.student to user_sc;
--查看 user_sc 角色拥有的对象权限,可以发现多了 insert 权限
SQL> select * from dba_tab_privs where grantee='USER_SC';
例三:撤销 user_sc 角色的 insert 权限。
SQL> revoke insert on system.student from user_sc;
--查看 user_sc 角色拥有的对象权限,可以发现 insert 权限没有了
SQL> select * from dba_tab_privs where grantee='USER_SC';
想了解更多有关设置角色的内容可参考 设置角色-官方文档
删除角色很简单。例如我们要删除 super_sc 这个角色可以执行如下命令:
SQL> drop role super_sc;
--查询 super_sc,可发现已经被删除
SQL> select * from dba_roles where role='SUPER_SC';
在之前创建用户的例子中,我们使用了 profile
指定了用户的概要文件,如果不指定概要文件,默认是 DEFAULT
。那么概要文件是什么?
概要文件其实是对数据库资源的一组限制,如果将配置文件分配给用户,则该用户不能超出这些限制,比如限制消耗 CPU 的时间,限制会话的时间等等。
创建概要文件的语法见 创建概要文件-官方文档 。
很多时候一些软件都会限制输入密码的错误次数,如果超过指定的次数,则会将你的账户冻结一段时间。在 Oracle 中同样有这样的功能,可以使用概要文件实现。比如下面我们创建一个概要文件,限制输入口令的连续错误次数为 3 次,如果错误次数超过这个次数就锁定账户 1 天。
create profile pwd_time --定义概要文件名称为 pwd_time
limit failed_login_attempts 3 --限制连续错误次数
password_lock_time 1; --限制锁定账户天数
--查询概要文件,可以发现已经创建好 pwd_time 这个概要文件
select distinct profile from dba_profiles;
除了可以设置这些限制,还可以设置更多的限制,可参考 创建概要文件
例如修改 pwd_time 概要文件,给它增加一个限制,限制用户的 CPU 会话并发数量为100 个。
SQL> alter profile pwd_time limit sessions_per_user 100;
--查询可以看到已限制为 100
SQL> select * from dba_profiles where profile='PWD_TIME' and resource_name='SESSIONS_PER_USER';
如果设置为
unlimited
则表示用户可以有任何数量的并发会话。
例:删除概要文件 pwd_time
。
SQL> drop profile pwd_time cascade;
cascade
表示如果有用户使用了此概要文件,那么将那个用户使用的概要文件撤销。
本节实验主要介绍了相应的连接实例,包括了如何进行初始化参数文件、数据库的启动关闭操作以及如何查看日志和动态性能视图。
连接实例
初始化参数文件
启动和关闭数据库实例
查看日志
动态性能视图
在进行数据库操作时,首先需要了解的就是怎么连接实例,在前面我们已经使用过很多方式连接过,这里总结一下。
在连接实例时会进行身份验证,普通用户登录需要提供用户名和密码,用数据字典进行身份验证。如下使用 system 用户进行登录:
SQL> connect system/Syl12345
如果以 sysdba 权限或者 sysoper 权限连接数据库,在验证身份时不需要打开数据库,它的验证方式是将用户名和所提供的密码散列和外部密码文件中存储的值进行比较,或者使用操作系统身份进行验证。
例:
首先在终端输入如下命令进入 sqlplus。
$sqlplus /nolog
使用
/nolog
是为了进入 sqlplus 时不提示马上输入用户名和密码连接实例。
然后就可以使用如下几种方式连接实例:
--以 sysdba 权限连接,使用密码文件验证方式
SQL> conn system/Syl12345 as sysdba
--以 sysoper 权限连接,使用密码文件验证方式
SQL> conn system/Syl12345 as sysoper
--以 sysdba 权限连接,使用操作系统身份验证
SQL> conn / as sysdba
--以 sysoper 权限连接,使用操作系统身份验证
SQL> conn / as sysoper
如果最后一句报错,是因为当前操作系统身份没有授予 sysoper 权限。
conn / as sysdba
登入后的用户名为SYS
,可以使用show user
查看当前连接实例的用户名。
conn / as sysoper
登入后的用户名为PUBLIC
。
实例是根据参数文件中的定义(由 SMON 进程读取)在内存中构建的。参数文件有两类:
spfile
文件默认是 <ORACLE_HOME>/dbs/spfile<SID>.ora
。用如下命令可以看到 <ORACLE_HOME>/dbs/
目录下有这个文件。
$ ls $ORACLE_HOME/dbs/
从 v$parameter
和 v$spparameter
里可以查看到参数及其对应的值。
SQL> select name,value from v$parameter;
SQL> select name,value from v$spparameter;
可以看到两者的结果有所区别。
v$parameter
中是当前运行的实例中生效的参数值。v$spparameter
中是spfile
中存储的值。产生这种区别的原因是有些参数可以在实例运行时更改,我们更改了,但并未保存到 spfile 中。
未保存到 spfile 的更改,在实例停止时失效。保存到 spfile 的更改,在下次启动实例时依然生效。
v$parameter
每列的说明可参见 v$parameter
查看有哪些初始化参数可参考 Parameter by Function Category
基本初始化参数是应该为每个数据库使用的参数。如下命令可查看基本参数及其当前值:
SQL> select name,value from v$parameter where isbasic='TRUE';
更多有关基本初始化参数可参考 基本初始化参数-官方文档
更改参数可以使用:
ALTER SYSTEM
系统级别的更改ALTER SESSION
会话几倍的更改ALTER SYSTEM
的语法如下:
ALTER SYSTEM SET <name>=<value> SCOPE=MEMORY | SPFILE | BOTH
SCOPE
用于确定实在哪里进行修改。默认为BOTH
,即应用于运行着的实例并写入 spfile。
MEMORY
:修改内存中此参数的值。这个修改不是永久的,数据库关闭并重新启动,值会变为默认值。SPFILE
:修改 spfile 文件中此参数的值。这个修改是永久的。更改静态参数(也就是启动实例后不可更改,固定下来的参数)必须指定SCOPE=SPFILE
。
参数可以使用哪种方式修改可参考 修改参数-官方文档 。也可以在 v$parameter
中查看
(参见v$parameter )。
例一:更改 sql_trace
参数的值 。
首先查看此参数的当前值。
SQL> select name,value from v$parameter where name='sql_trace';
NAME VALUE
----------- ----------
sql_trace FALSE
然后更改其在内存中的值。
--更改为 TRUE
SQL> alter system set sql_trace=TRUE scope=memory;
--查看其值
SQL> select name,value from v$parameter where name='sql_trace';
--更改为 FALSE
SQL> alter system set sql_trace=FALSE scope=memory;
例二:更改 processes
参数值。
更改其值为 400 。
SQL> alter system set processes=400;
会报错
ORA-02095: 无法修改指定的初始化参数
。因为processes
是静态参数。必须指定SCOPE=SPFILE
。
如下是正确修改方式:
--修改为 400
SQL> alter system set processes=400 scope=spfile;
--查看其值
SQL> select name,value from v$spparameter where name='processes';
--修改为 300
SQL> alter system set processes=300 scope=spfile;
例三:修改当前会话中 sql_trace
的值。
--查看其值
SQL> show parameter sql_trace;
NAME TYPE VALUE
------------ -------- ---------
sql_trace boolean FALSE
--修改其值为 TRUE
SQL> alter session set sql_trace=TRUE;
--修改其值为 FALSE
SQL> alter session set sql_trace=FALSE;
SHUTDOWN
:关闭与数据库相关的所有文件,实例不存在。NOMOUNT
:根据参数文件的定义在内存中构建实例。实例启动。MOUNT
:读取控制文件(由 control_files
参数定位)。此时数据库是关闭的。OPEN
:打开数据库。关闭的过程与启动相反。有关数据库启动和关闭更多内容可参见 Overview of Database Instance Startup and Shutdown
下面来实际操作数据库实例的启动和关闭。
SQL> conn system/Syl12345 as sysdba;
SQL> shutdown immediate;
SQL> startup nomount;
ORACLE 例程已经启动。
Total System Global Area 3841982464 bytes
Fixed Size 8753768 bytes
Variable Size 872418712 bytes
Database Buffers 2952790016 bytes
Redo Buffers 8019968 bytes
SQL> alter database mount;
数据库已更改。
SQL> alter database open;
数据库已更改。
此时,数据库已经打开。我们可以使用如下命令确认数据库是否已经打开:
SQL> select open_mode from v$database;
OPEN_MODE
----------------------------------------
READ WRITE
READ WRITE
说明已经打开。
以上所有步骤可以使用 startup force
一步完成。上述分阶段启动的好处是可以对不同阶段进行控制,比如控制文件损坏,我们可以停留在 NOMOUNT 阶段,把控制文件修复过后,再继续启动。
警报日志由 DIAGNOSTIC_DEST
参数确定,它提供数据库消息和错误信息(按时间排序)。下面命令查看警报日志的位置:
SQL> select name,value from v$spparameter where name='diagnostic_dest';
警报日志所在默认目录为
DIAGNOSTIC_DEST/diag/rdms/<dbname>/<instancename>/trace
。
DDL 日志记录的是 DDL 命令。DDL 日志默认是停止的,首先要设置 enable_ddl_logging
参数值以启用:
--启用
SQL> alter system set enable_ddl_logging=TRUE;
--查看其值
SQL> select name,value from v$parameter where name='enable_ddl_logging';
DDL 日志所在的默认目录为
DIAGNOSTIC_DEST/diag/rdbms/<dbname>/<instancename>/log
下。
Oracle 包含一组由数据库服务器维护并可供数据库管理员用户 SYS 访问的基础视图。这些视图被称为动态性能视图,因为它们在数据库处于打开和使用状态时不断更新,其内容主要与性能有关。这些视图提供有关内部磁盘结构和内存结构的数据。我们可以从这些视图中进行查询,但不能更新或更改它们。
(此段参考自 About Dynamic Performance Views-官方文档 )
我们在之前用到了很多 v$
为前缀的东西去查询一些信息,可能你会以为它是视图,实际上它并不是视图,而是某个视图的同义词。比如 v$parameter
实际上是 v_$parameter
视图的同义词。
Oracle 中有很多动态性能视图,可以参考 动态性能视图-官方文档
也可以用如下命令查看有哪些以 v$
开头的动态性能视图:
SQL> select * from v$fixed_table where name like 'V$%';
例一:用数据字典和动态性能视图查询数据库包含的表空间和数据文件。
--使用数据字典查询
SQL> select tablespace_name from dba_tablespaces;
--使用动态性能图查询
SQL> select name from v$tablespace;
例二:查询控制文件位置。
SQL> select * from v$parameter where name='control_files';
或者
SQL> select * from v$controlfile;
作业自动化
实验介绍
实验内容
在使用数据库的过程中,通常会有一些重复的事情,比如备份操作,检查错误,启动数据库等等。如果每次都手动操作,不仅有可能忘记而且很麻烦,如果创建一个作业,让系统自动处理会很方便。本节实验主要介绍两个用来实现作业自动化的程序:
Oracle Scheduler
:这个是 Oracle 提供的。cron
:这个是 Linux 系统本身就有的。实验知识点
Oracle Scheduler
这里以定期备份表空间 system 为例。
创建脚本
创建一个 bash 脚本,执行作业的时候会执行这个脚本,以实现备份。
输入如下内容:
连接实例
创建作业
创建作业需要用到
DBMS_SCHEDULER
软件包的CREATE_JOB
过程,它需要的参数可以使用desc DBMS_SCHEDULER
查看到,参数详情可以参阅 DBMS_SCHEDULER 。下面就创建一个作业,实现在每周的星期五备份表空间 system。
注:下面命令在 SQL 命令行输入
参数解释:
EXECUTABLE
。它还有其他值,可以调用 sql 脚本,plsql 语句块等等。backup_system.sh
这个 bash 脚本。创建好了作业,我们可以通过如下命令查询到作业:
有关创建作业更多内容可参考 CREATE_JOB 。
查看作业执行的历史记录
作业执行时,会生成一条执行作业的历史记录,我们可以通过如下命令查询:
日志纪录的默认保留天数是 30 天。这个时间是可以修改,例如把时间修改为 29 天:
修改作业
可以调用
DBMS_SCHEDULER
包的一些过程实现修改作业,启动,暂停,停止作业等操作。例:把作业的执行时间更改为每天一次。
删除作业
例如删除我们新建的
BACKUP_SYSTEM
这个作业。crontab
crontab
命令常见于 Unix 和类 Unix 的操作系统之中(Linux 就属于类 Unix 操作系统),用于设置周期性被执行的指令。它通过守护进程cron
使得任务能够按照固定的时间间隔在后台自动运行。cron
利用的是一个被称为 “cron 表”(cron table)的文件,这个文件中存储了需要执行的脚本或命令的调度列表以及执行时间。当使用者使用
crontab
后,该项工作会被记录到/var/spool/cron/
里。不同用户执行的任务记录在不同用户的文件中。通过
crontab
命令,我们可以在固定的间隔时间或指定时间执行指定的系统指令或脚本。时间间隔的单位可以是分钟、小时、日、月、周的任意组合。这里我们看一看
crontab
的格式其中特殊字符的意义:
0 7,9 * * * command
代表7:00和9:0030 7-9 * * * command
代表7点到9点之间每小时的30分*/10 * * * *
每10分钟crontab 准备
crontab
在本实验环境中需要做一些特殊的准备,首先我们会启动rsyslog
,以便我们可以通过日志中的信息来了解我们的任务是否真正的被执行了(在本实验环境中需要手动启动,而在自己本地中 Ubuntu 会默认自行启动不需要手动启动)在本实验环境中
crontab
也是不被默认启动的,同时不能在后台由upstart
来管理,所以需要我们手动启动它(同样在本实验环境中需要手动启动,自己的本地 Ubuntu 的环境中也不需要手动启动)crontab 使用
使用 crontab 的基本语法如下:
其常用的参数有:
-u
-e
-l
-r
我们这里还是以定期备份表空间为例。首先执行如下命令以添加一个任务计划:
第一次启动会出现这样一个画面,这是让我们选择编辑的工具,选择第一个基本的 vim 就可以了
选择后我们会进入一个添加计划的界面,按
i
键便可编辑文档,在文档的最后一行加上这样一行命令,实现每周日 9 点执行备份操作。输入完成后按
esc
再输入:wq
保存并退出。添加成功后我们会得到
installing new crontab
的一个提示 。为了确保我们任务添加的正确与否,我们会查看添加的任务详情:
虽然我们添加了任务,但是如果
cron
的守护进程并没有启动,当然也就不会帮我们执行,我们可以通过以下 2 种方式来确定我们的 cron 是否成功的在后台启动,若是没有则需要启动一次。另外,可以通过如下命令查看执行任务命令之后在日志中的信息反馈:
当我们并不需要某个任务的时候我们可以通过
-e
参数去配置文件中删除相关命令,若是我们需要清除所有的计划任务,我们可以使用这么一个命令去删除任务:总结