ORACLE学习记录

以下全属个人学习oracle的学习记录,仅供参考,有不对的地方欢迎交流。

我的学习oracle的目录大致如下:

一:Oracle基础

1.sql/plus用法详解

2.基本增删改查语句

3.列类型与建表语句

4.正确的查询模型

5.查询子句详解

6.约束

7.连接查询

8.子查询

9.视图

二:Oracle进阶

1.索引

2.序列

3.同义词

4.事务

5.函数

6.触发器

7.pl/sql编程

                 •  匿名块

                 •  控制结构

                 •  过程与函数

                 •  存储过程与存储函数

                 •  游标

三:实际操作

1.sql/plus连接服务器

连接并登陆

sqlplus "用户名/密码[@主机名 as 身份]"

先连接再登陆

sqlplus/nolog

conn 用户名/密码[@主机名 as 身份]

退出:

quit

如图所示:

2.sqlplus做最简单的查询

select 3+2 from dual;   

注意dual是Oracle中的一个伪表,利用这个伪表可以设置或查看序列,或者是调用一些内置的函数,方便操作。

3.sqlplus的缓冲区

我们输入的sql命令,会被sqlplus储存在缓冲区里,

熟练利用缓冲区,可以提高我们书写sql的效率

因为缓冲区的内容 可以 修改/删除,再执行

用list/l 查看缓冲区内容

4.列类型

5.建表语句

create table 表名 (

列名 列类型,

...

);

create table 表名

as

select 列1,列2...列N from othoretable;

create table 表名

as

select 列1 as 新列1, 列2 as 新列2 ...列N as 新列N

from othertable

6.修改表的语句-add 列

alter table cxb add(

address varchar2(60)

);

7.修改表的语句-drop列

作用:删除表中的列

语法: alter table 表名 drop column 列名

关于删除列的注意事项:

原则上,列都可以删

但,如果一个列作为主键出现

或者作为另1张表的外键,则不能删除

8.修改表的语句-modify 列

作用:修改表中的列

语法: alter table 表名 modify (

列1 新数据类型 [非空属性]

..

);

关于修改列的注意事项:

如果表中没有数据,则列的长度可增加或减小

如果已有数据,则只能增大,不能减小.

9.修改表的语句-修改列名

作用:修改表中的列名

语法: alter table 表名 rename column 旧列名 to 新列

10.关于表的其他DDL语句

删除表: drop table 表名

改表名: rename table 旧名 to 新名

清空表: truncate table 表名

truncate清空表数据,且不可恢复,慎重.

11.约束

约束概念:

约束是加在表上的一种强制性的规则 ,是保证数据完整性的一种重要手段 .

当向表中插入或修改数据时,必须满足约束所规定的条件.

如性别必须是"男/女",部门号必须是已存在的部门号等等.

一般而言,保证数据完整性大致有3种方法

程序代码,触发器,约束.

约束类型:

NOT NULL 非空约束

UNIQUE 唯一性约束

PRIMARY KEY 主键约束

FOREIGN KEY 外键约束

CHECK       检查约束

11.1:约束的创建(1)

建表时创建约束

create table 表名 (

列1 数据类型 constraint 约束名1 约束类型,

列2 数据类型 constraint 约束名2 约束类型,

...

);

11.2:约束的创建(2)

11.3:外键的声明

外键的声明稍复杂一点,因为牵涉到另一张表

create table 表名 (

列1 列类型

contraint 约束名 foreign key (列名) references 其他表(列名)

注意: 另一张表被引用的列需是主键或Unique

11.4:建表后添加约束

在需要批量导入数据时,约束会影响导入速度,

可以先不要约束,导入完毕后,再添加约束.

alter table 表名 add (

constraint 约束名 约束类型(列名),

constraint 约束名 约束类型(列名)

);

alter table 表名 modify (

列名 constraint 约束名 not null

) ; // 因为not null 类型必须声明在列上,无法声明在表上,所以必须用modify方式来写

11.5:约束的删除与禁用

想改一个约束类型,只能先删除约束再添加新的约束

语法:

alter table 表名 drop constraint 约束名;

如 alter table student drop constraint gen_check

注意:如果删除主键约束时,该主键是另一表的外键,则该主键不能直接删除.

除非连带把外键约束也删除.

如 alter table dept drop constraint pk_dept cascade;

也可以临时禁用1个约束

alter table 表名 disable consstaint 约束名

注意:如果禁用约束后加了一些非法数据,再开启约束是会失败的

12.序列 sequence

序列是一种数据库对象,用来自动生成一组唯一的序号.

序列是一种共享式的对象,多个用户可以共同使用序列中的序号.

一般将序列应用于表的主键列,这样,当向表中插入数据时,主键列就使用了序列的序号,从而保证主键不会重复.

用序列来产生主键,可以获得可靠的主键值.

一句话: 序列就是序号生成器!

12.1:序列 sequence 的创建

create sequence 序列名 increment by n

start with n

maxvalue n | nomaxvalue

minvalue n  | nominvalue

cycle | nocycle

cache n | nocache

上图创建了名称为mm的序列,每次增长1,从1开始,没有最大值,没有最小值,不循环,系统先缓存20个序列。

12.2:序列 sequence 的使用

序列的作用就是为我们提供序号,

序列提供了2个伪列, nextval, currval

很明显,分别是"下个值", "当前值"

select seq1.nextval from dual;

insert into xx表(col1,col2) values (seq1.nextavl,yy);

12.3:序列 sequence 的修改和删除

alter sequence 序列名

选项 新值

alter sqquence 序列名

maxvalue 5

注意:

1:不能修改开始值

2:修改只影响新产生的值,不影响已产生的值

drop sequence 序列名

13.同义词

同义词就是别名,外号

create [public] synonym 同义词 for 用户名.对象名

drop synonym 同义词

public 是所有用户可用的同义词,一般由DBA创建

注:scott用户默认没有创建synonym的权限

需要授权:

grant create synonym to scott

14.增删改查

14.1:select 子句介绍

Where 条件查询

group by 分组

having 筛选

order by 排序

14.2:select 子句 之group与统计函数

max : 求最大

min : 求最小

sum : 求总和

avg : 求平均

count:求总行数

14.3:select 子句 之group介绍

group by

作用:把行 按 字段 分组

语法:group by col1,col2,...colN

运用场合
常见于统计场合,如按栏目计算帖子数,

统计每个人的平均成绩等.

14.4:select 子句 之having介绍

14.5:子查询

子查询就是在原有的查询语句中,

嵌入新的查询,来得到我们想要的结果集。

一般根据子查询的嵌入位置分为,

where型子查询,from型子查询

14.5.1:where型子查询

where型子查询即是:把内层sql语句查询的结果作为外层sql查询的条件

典型语法:

select * from tableName

where colName = (select colName from tbName where ....)

{where colName in (select colName from tbName where ..)}

14.5.2:exists型子查询

exists即:外层sql查询所查到的行代入内层sql查询,要使内层查询能够成立

查询可以与in型子查询互换,但效率要高.

典型语法:

select * from tablename

where exists(select * from tableName where ...)

14.5.3:from型子查询

from型子查询即:把内层sql语句查询的结果作为临时表供外层sql语句再次查询.

典型语法:

select * from (select * from tableName where ...) where....

15.连接查询 之连接查询语法

左连接的语法.

Select Ltable.* ,Rtable.* from

 Ltable left join Rltable

 on Ltable.colName = Rtable.colName

典型的例子:学生表,科目表,学生选科目id学习,查询学生的时候联查科目表,学生表里存的有科目表的id,查询出学生信息带科目的信息。如图:

内连接的语法.

Select Ltable.* ,Rtable.* from

Ltable inner join Rltable

on Ltable.colName = Rtable.colName

16.视图 view

视图是一种虚拟表,本身不保存数据,而是从表中取得的数据。

可以理解为表的映射,或更简单的理解为一个查询结果。

比如,我们频繁的查询如下语句:

select empno,ename,sal from emp where sal > (select avg(sal) from emp);

如果把这个语句定义为视图,并从视图查询数据

16.1:视图 view的用途

1:视图可以帮我们简化查询

如上页中的复杂查询,如果不用视图,则需要子查询才能达到效果

2:视图可以帮我更精细的控制权限

比如一张表,有工资列,密码列,等,

我们可以选择列生成视图,开放给不同的用户,

达到列级的权限控制

16.2:视图 view的操作语法

创建视图:

Create or replace view 视图名

As select  语句

With read only  --是否只读

With check option –是否执行约束检查

删除视图:

Drop view 视图名

16.3:复杂视图

如果select 语句中只针对单表进行列的查询,且没有对列进行表达式运算或函数运算,

这种称为简单视图。

如果对多个表进行查询,或列经过运算,或分组等,这种称为复杂视图。

复杂视图不能进行DML操作

本质区别:数学上是否一一对应。

即任意一行视图的记录,能对应表中唯一的一行,就是简单视图

17.索引 index

索引就像字典前的“按拼音/偏旁查询目录”,

可以提高查询效率,降低了增删改的效率。

*数据库内部常用哈希索引,和btree索引

17.1:索引 index 的创建与查询

create [unique] index 索引名

on 表名(1,列2...)

1个列上称为单列索引

否则称复合索引

索引信息存放在 user_indexes ,user_ind_columns

删除索引

Drop index 索引名

17.2:索引 index 的注意事项

where子句中经常使用的列上创建索引

大量重复的值加索引意义不大

具有唯一值的列是建索引的好的选择,但具体还要看是否经常用他查询。

如果where经常用某N个列一些查询,考虑建复合索引

索引是有代价的--降低了增删改的速度,并不是加的越多越好。

18.事务

原子性(Atomicity):原子意为最小的粒子,或者说不能再分的事物。

数据库事务的不可再分的原则即为原子性。

组成事务的所有查询必须:

要么全部执行,要么全部取消(就像上面的银行例子)。

一致性(Consistency):指数据的规则,在事务前/后应保持一致

隔离性(Isolation):简单点说,某个事务的操作对其他事务不可见的.

持久性(Durability):当事务完成后,其影响应该保留下来,不能撤消

18.1:事务 之事务的用法

开启事务(第1条dml语句即进入事务)

执行sql操作(普通sql操作)

设置保存点(savepoint 保存点)

提交/回滚(commit/rollback)

部分回滚(rollback to 保存点)

18.2:事务的隐式提交

事务可以显式的提交,也可以隐式的提交

显式:commit

隐式:   遇到DDLDCL语句,或退出系统时

          会隐式提交

四:PL/sql编程

1.PL/sql概念

Procedural  language

sql是一种标准的数据库访问语言,但无法编程,

PL/SQLOracle公司开发的“方言”,允许编程,

是对SQL的一种补充。

2.PL/sql的结构

declare

 变量声明部分

begin

 执行部分

exception

 异常处理部分

End

*:declare exception部分是可选的

默认:调用一个匿名块/存储过程后,只执行不输出

学习调试时: set serveroutput on

3.PL/sql中变量的定义

变量 的定义有2种格式

变量名 变量类型 [约束] default 默认值

变量名 变量类型 [约束] [:=初始值]

-------

变量名 表名%rowtype

变量名 表名.%type

变量名 另一变量%type

4.PL/sql块中流程控制

5.PL/sql访问数据库

PL/SQL的主要目的是对数据库进行操作,

因此,PL/SQL块中可以包含select语句,DML语句,

还可以包含DCL语句.

但不能包含DDL语句.

通过SQL语句及流程控制,可以编写复杂的PL/SQL,

对数据库进行复杂的访问.

注意,PL/SQL一般是在应用程序中调用.

6.附录:PL/sql预定义异常

NO_DATA_FOUND   在使用SELECT INTO 结构,并且语句返回NULL值的时候;访问嵌套表中已经删除的表或者是访问INDEX BY表(联合数组)中的未初始化元素就会出现该异常

TOO_MANY_ROWS   常见错误,在使用SELECT INTO 并且查询返回多个行时引发。如果子查询返回多行,而比较运算符为相等的时候也会引发该异常。

ZERO_DIVIDE 将某个数字除以0的时候,会发生该异常

ACCESS_INTO_NULL  试图访问未初始化对象的时候出现

CASE_NOT_FOUND  如果定义了一个没有ELSE子句的CASE语句,而且没有CASE语句满足运行时条件时出现该异常

COLLECTION_IS_NULL  当程序去访问一个没有进行初始化的NESTED TABLE或者是VARRAY的时候,会出现该异常

CURSOR_ALREADY_OPEN  游标已经被OPEN,如果再次尝试打开该游标的时候,会出现该异常

DUP_VAL_ON_INDEX  如果插入一列被唯一索引约束的重复值的时候,就会引发该异常(该值被INDEX认定为冲突的)

INVALID_CURSOR  不允许的游标操作,比如关闭一个已经被关闭的游标,就会引发

INVALID_NUMBER  给数字值赋非数字值的时候,该异常就会发生,这个异常也会发生在批读取时候LIMIT子句返回非正数的时候

LOGIN_DENIED  程序中,使用错误的用户名和密码登录的时候,就会抛出这个异常

NOT_LOGGED_ON  当程序发出数据库调用,但是没有连接的时候(通常,在实际与会话断开连接之后)

PROGRAM_ERROR  Oracle还未正式捕获的错误发生时常会发生,这是因为数据库大量的Object功能而发生

ROWTYPE_MISMATCH  如果游标结构不适合PL/SQL游标变量或者是实际的游标参数不同于游标形参的时候发生该异常

SELF_IS_NULL  调用一个对象类型非静态成员方法(其中没有初始化对象类型实例)的时候发生该异常

STORAGE_ERROR  当内存不够分配SGA的足够配额或者是被破坏的时候,引发该异常

SUBSCRIPT_BEYOND_COUNT  当分配给NESTED TABLE或者VARRAY的空间小于使用的下标的时候,发生该异常(类似于javaArrayIndexOutOfBoundsException

SUBSCRIPT_OUTSIDE_LIMIT  使用非法的索引值来访问NESTED TABLE或者VARRAY的时候引发

SYS_INVALID_ROWID  将无效的字符串转化为ROWID的时候引发

TIMEOUT_ON_RESOURCE  当数据库不能安全锁定资源的时候引发

USERENV_COMMITSCN_ERROR  只可使用函数USERENV('COMMITSCN')作为INSERT语句的VALUES子句中的顶级表达式或者作为UPDATE语句的SET子句中的右操作数

VALUE_ERROR  将一个变量赋给另一个不能容纳该变量的变量时引发

7.存储过程与存储函数

前面用到的过程和函数都是写在PL/SQL块中,

放在缓冲区里.没有进行保存,

下次要使用了再次声明,调用.

我们可以把过程和函数建立并保存在数据库中,形成一个对象.

这种存储后的过程和函数,称为:

存储过程 存储函数

7.1:存储过程与存储函数创建语法

create or replace procedure 名称[(参数)]

authid current_user|definer --以定义者还是调用者的身份运行

is[不要加declare]

变量声明部分

begin

主体部分

exception

异常部分

end;

7.2:存储过程与存储函数的删除

drop procedure 存储过程名

drop function 函数名

8.游标-cursor

游标是一种私有的工作区,用于保存sql语句的执行结果.

在执行一条sql语句时,数据库服务区工作区,

这里保存了sql语句执行的相关信息

工作区有2种形式的游标,隐式的和显式的.

隐式游标由数据库自动定义,显示游标由用户自己定义.

游标-cursor 简单例子:

begin

delete from student where sid=9;

if SQL%ROWCOUNT>0 then

dbms_output.put_line('影响了');

else

dbms_output.put_line('没影响');

end if;

end;

9.触发器

个人粗暴的解释,就是当某个动作发生之后或者之前触发执行。

9.1:触发器创建语法

u创建触发器的语法

create trigger 触发器名称

after/before/instead of(触发时间)

insert/update/delete [of列名] (监视事件)

on  表名 (监视地址)

[for each row [when条件]]

begin

sql1;

..

sqlN;

end

9.2:触发器的删除

drop trigger triggerName

10.创建用户与删除用户

 create user 用户名 identified by “密码"

 default tablespace users

 temporary tablespace temp

quota 20M on users

password expire

account unlock;

drop user 用户名

如果用户名已经有表,视图等,则不允许删除。

11.修改用户密码

后续附上个人练习一些片段,瞌睡来兮了,偷个懒,很多都是截图了,嘻嘻。。。。。。


Microsoft Windows [版本 10.0.17134.165]
(c) 2018 Microsoft Corporation。保留所有权利。

C:\Users\81046>sqlplus

SQL*Plus: Release 11.2.0.1.0 Production on Thu Aug 16 13:53:36 2018

Copyright (c) 1982, 2010, Oracle.  All rights reserved.

Enter user-name:
ERROR:
ORA-01017: invalid username/password; logon denied


Enter user-name: sys
Enter password:
ERROR:
ORA-28009: connection as SYS should be as SYSDBA or SYSOPER


Enter user-name: system
Enter password:

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

SQL> select 3
  2  select 3 from dual;
select 3 from dual
*
ERROR at line 2:
ORA-00923: FROM keyword not found where expected


SQL> select 3 from dual;

         3
----------
         3

SQL> create table student(
  2  sid number not null,
  3  sname char(10)
  4  );

Table created.

SQL> insert into student values (1,'wangwu');

1 row created.

SQL> select * from student;

       SID SNAME
---------- ------------------------------
         1 wangwu

SQL> alert table student add(
SP2-0734: unknown command beginning "alert tabl..." - rest of line ignored.
SQL> alter table student add(
  2  area varchar2(20),
  3  age number(3,1),
  4  height number(3,2)
  5  );

Table altered.

SQL> desc student;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 SID                                       NOT NULL NUMBER
 SNAME                                              CHAR(10)
 AREA                                               VARCHAR2(20)
 AGE                                                NUMBER(3,1)
 HEIGHT                                             NUMBER(3,2)

SQL> alter table drop column height;
alter table drop column height
            *
ERROR at line 1:
ORA-00903: invalid table name


SQL> alter table student  drop column height;

Table altered.

SQL> alter table student modify age number(5,0);

Table altered.

SQL> des

declare
  i int :=9;
  begin
  i:=i*2;
  dbms_output.put_line('now i is:'||i);
  end;
  /

declare
    age number default 90;
    height number := 175;
    gender char(2) := 'mile';
begin
    if gender='mile' then
        dbms_output.put_line('you can mary with femile');
    end if;

    if height>170 then
        dbms_output.put_line('can play basketebool');
    else 
        dbms_output.put_line('can play footbool');
    end if;

    if age<20 then
        dbms_output.put_line('younth');
    elsif age <= 50 then
        dbms_output.put_line('seccuss');
    elsif age <=70 then
        dbms_output.put_line('happniess');
    else  
        dbms_output.put_line('subject');
    end if;

end;


存储过程一个没有返回值的函数

 create or replace procedure  p1(dpnum number) is
 cnt number;
 sals number;
 begin
select count(*),sum(sal) into cnt,sals from emp where deptno=dpnum;
dbms_output.put_line('has '||cnt||'worker,sals is'||sals);
 end;
 /

 create or replace procedure  p1(dpnum number) is
 cnt number;
 sals number;
dn varchar2(14);
dl varchar2(13);
 begin
select dname,loc into dn,dl from dept where deptno=dpnum;
select count(*),sum(sal) into cnt,sals from emp where deptno=dpnum;
dbms_output.put_line('localtion:'||dl||'company:'||dn||'has '||cnt||'worker,sals is'||sals);
 end;
 /

 create or replace procedure  p1(dpnum number) is
 cnt number;
 sals number;
dn varchar2(14);
dl varchar2(13);
 begin
select dname,loc into dn,dl from dept where deptno=dpnum;
select count(*),sum(sal) into cnt,sals from emp where deptno=dpnum;
dbms_output.put_line('localtion:'||dl||'  company:'||dn||'has '||cnt||'worker,sals is'||sals);
 end;
 /

create or replace procedure p12(dpnum int) is
type dpinfo is record 
(
dname varchar2(14),
loc varchar2(13),
cnt int,
sal number
);
dp dpinfo;
begin

select dname,loc into dp.dname,dp.loc from dept where deptno=dpnum;

select count(*),sum(sal)  into dp.cnt,dp.sal from emp where deptno=dpnum;

dbms_output.put_line(dp.dname||'--'||dp.loc||'--'||dp.cnt||'--'||dp.sal);
end;
/

create or replace procedure p12(dpnum int) is
type dpinfo is record 
(
dname varchar2(14),
loc varchar2(13),
cnt int,
sal number,
);
dp dpinfo;
begin

select dname, loc into dp.dname, dp.loc from dept where deptno=dpnum;

select count(*), sum(sal)  into dp.cnt, dp.sal from emp where deptno=dpnum;

dbms_output.put_line(dp.dname||'--'||dp.loc||'--'||dp.cnt||'--'||dp.sal);

end;
/

create or replace procedure p12(dpnum int) is
type dpinfo is record 
(
dname varchar2(14),
loc varchar2(13),
cnt int,
sal number,
);
dp dpinfo;
begin

select dname, loc into dp.dname, dp.loc from dept where deptno=dpnum;

select count(*), sum(sal)  into dp.cnt, dp.sal from emp where deptno=dpnum;

dbms_output.put_line(dp.dname||'__'||dp.loc||'__'||dp.cnt||'__'||dp.sal);

end;
/

 create or replace procedure p13(dpnum int) is
   dp dept%rowtype;
   dn dept.dname%type;
  begin
   select deptno,dname,loc into dp.deptno,dn,dp.loc from dept
   where deptno=dpnum;
   dbms_output.put_line(dp.deptno||'__'||dn||'__'||dp.loc);
exception
when NO_DATA_FOUND then
dbms_output.put_line('sorry NO_DATA_FOUND');
    end;
    /

以上是表级触发器

监听到表里的数据有更改则触发

 create trigger tg1
  after insert
    on  o
   begin
    dbms_output.put_line('has somone pay goods');
   end;
    /

 create or replace trigger tg2
  after insert
    on  o for each row
   begin
    update g set cnt=cnt-:new.much where gid=:new.gid;
   end;
    /

create or replace trigger tg2
  after insert
    on  o for each row
   begin
    update g set cnt=cnt-:new.much where gid=:new.gid;
dbms_output.put_line('update number');
   end;
    /

create or replace trigger tg3
before insert on o
for each row
declare
curr number;
begin
select cnt into curr from g where gid=:new.gid;
if curr<:new.much then
:new.much :=curr;
end if;
update g set cnt=cnt-:new.much where gid=:new.gid;
end;
/

create or replace trigger tg3
before insert on o
for each row
declare
curr number;
begin
select cnt into curr from g where gid=:new.gid;
if curr<:new.much then
:new.much :=curr;
end if;
update g set cnt=cnt-:new.much where gid=:new.gid;
end;
/

Microsoft Windows [版本 10.0.17134.228]
(c) 2018 Microsoft Corporation。保留所有权利。

C:\Users\81046>sqlplus sys/root123456 as sysdba;

SQL*Plus: Release 11.2.0.1.0 Production on Fri Aug 17 09:00:03 2018

Copyright (c) 1982, 2010, Oracle.  All rights reserved.


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

SQL> conn scott/tiger;
Connected.
SQL> show user
USER is "SCOTT"
SQL> select seq1.nextval from dual;

   NEXTVAL
----------
        21

SQL> create view newgoods as select * from goods where doods_id>1;
create view newgoods as select * from goods where doods_id>1
                                                  *
ERROR at line 1:
ORA-00904: "DOODS_ID": invalid identifier


SQL> select * from goods;

  GOODS_ID     CAT_ID
---------- ----------
GOODS_NAME
--------------------------------------------------------------------------------
         1          8
nokia

         7          8
fengtian

         8          8
food8


  GOODS_ID     CAT_ID
---------- ----------
GOODS_NAME
--------------------------------------------------------------------------------
         9          5
book9

        10          5
book10

        11          5
book11


6 rows selected.

SQL> create view newgoods as select * from goods where goods_id>1;

View created.

SQL> select * from newgoods;

  GOODS_ID     CAT_ID
---------- ----------
GOODS_NAME
--------------------------------------------------------------------------------
         7          8
fengtian

         8          8
food8

         9          5
book9


  GOODS_ID     CAT_ID
---------- ----------
GOODS_NAME
--------------------------------------------------------------------------------
        10          5
book10

        11          5
book11


SQL> set linesize 2000
SQL> select * from newgoods;

  GOODS_ID     CAT_ID GOODS_NAME
---------- ---------- ------------------------------------------------------------------------------------------
         7  

猜你喜欢

转载自blog.csdn.net/qq_33371766/article/details/81781437