数据库学习(四)

六、基本的数据库性能分析方式:explain+慢SQL分析

使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是 如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。
使用方式:Explain+SQL语句
执行计划包含的信息:
±—±------------±------±-----±--------------±-----±--------±-----±-----±------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
±—±------------±------±-----±--------------±-----±--------±-----±-----±------+

1、id

SELECT查询的序列号,包含一组数字,表示查询中执行SELECT语句或操作表的顺序
包含三种情况:
1.id相同,执行顺序由上至下
2.id不同,如果是子查询,id序号会递增,id值越大优先级越高,越先被执行
3.id既有相同的,又有不同的。id如果相同认为是一组,执行顺序由上至下; 在所有组中,id值越大优先级越高,越先执 行。

2、select_type

SIMPLE:简单SELECT查询,查询中不包含子查询或者UNION
PRIMARY:查询中包含任何复杂的子部分,最外层的查询
SUBQUERY:SELECT或WHERE中包含的子查询部分
DERIVED:在FROM中包含的子查询被标记为DERIVER(衍生), MySQL会递归执行这些子查询,把结果放到临时表中
UNION:若第二个SELECT出现UNION,则被标记为UNION, 若UNION包含在FROM子句的子查询中,外层子查询将被标记为DERIVED
UNION RESULT:从UNION表获取结果的SELECT

3、table

显示这一行数据是关于哪张表的

4、type

type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:
system>const>eq_ref>ref>fulltext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL
一般来说,得保证查询至少达到range级别,最好能达到ref。

system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现
const:如果通过索引依次就找到了,const用于比较主键索引或者unique索引。 因为只能匹配一行数据,所以很快。如果将主键置于where列表中,MySQL就能将该查询转换为一个常量
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配 某个单独值的行,然而它可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体
range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引,一般就是在你的where语句中出现between、<、>、in等的查询,这种范围扫描索引比全表扫描要好,因为只需要开始于缩印的某一点,而结束于另一点,不用扫描全部索引
index:Full Index Scan ,index与ALL的区别为index类型只遍历索引树,这通常比ALL快,因为索引文件通常比数据文件小。 (也就是说虽然ALL和index都是读全表, 但index是从索引中读取的,而ALL是从硬盘读取的)
all:Full Table Scan,遍历全表获得匹配的行

5、possible_keys

显示可能应用在这张表中的索引,一个或多个。 查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用

6、key

实际使用的索引。如果为NULL,则没有使用索引。
查询中若出现了覆盖索引,则该索引仅出现在key列表中。

7、key_len

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精度的情况下,长度越短越好。
key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。

8、ref

显示索引的哪一列被使用了,哪些列或常量被用于查找索引列上的值。

9、rows

根据表统计信息及索引选用情况,大致估算出找到所需记录多需要读取的行数。

10、Extra

包含不适合在其他列中显示但十分重要的额外信息:
1、Using filesort: 说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序”
2、Using temporary: 使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序order by和分组查询group by
3、Using index: 表示相应的SELECT操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错。 如果同时出现using where,表明索引被用来执行索引键值的查找; 如果没有同时出现using where,表明索引用来读取数据而非执行查找动作 覆盖索引(Covering Index): 理解方式1:SELECT的数据列只需要从索引中就能读取到,不需要读取数据行,MySQL可以利用索引返回SELECT列表中 的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖 理解方式2:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此他不必读取整个行。 毕竟索引叶子节点存储了他们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了,一个索引 包含了(覆盖)满足查询结果的数据就叫做覆盖索引 注意: 如果要使用覆盖索引,一定要注意SELECT列表中只取出需要的列,不可SELECT , 因为如果所有字段一起做索引会导致索引文件过大查询性能下降
6、impossible where: WHERE子句的值总是false,不能用来获取任何元组
7、select tables optimized away: 在没有GROUP BY子句的情况下基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(
)操作, 不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化
8、distinct: 优化distinct操作,在找到第一匹配的元祖后即停止找同样值的操作

七、基本的SQL语句

一、DDL—数据定义语言(CREATE,ALTER,DROP,DECLARE)
二、DML—数据操纵语言(SELECT,DELETE,UPDATE,INSERT)
三、DCL—数据控制语言(GRANT,REVOKE)

四、下半部分内容(主要是PL/SQL:函数,存储过程,事务等)

说明:本文档的使用对象是对SQL有一些了解的软件测试人员,我只是把我知道的知识结合网上的资料进行二次总结,不正之处望多请教。本文档配置附有SQL范例脚本。

一、DDL数据定义语言

首先,简要介绍基础语句,作为测试人员一般测试时,已经由数据库设计师建好了数据库,数据库设计师可能也不用语句的方式来建表,但我们应该能看懂各语句的使用格式,语句的含义,有兴趣再作深入了解。

1、创建数据库
CREATE DATABASE [database-name]

2、删除数据库
DROP DATABASE dbname1,dbname2…

3、备份数据库
— 创建 备份数据的 device
USE master
EXEC sp_addumpdevice ‘disk’, ‘testBack’, ‘c:/mssql7backup/MyNwind_1.dat’
— 开始 备份
BACKUP DATABASE pubs TO testBack

4、创建表
create table tabname(col1 type1 [not null] [primary key],col2 type2 [not null],…)

例如: CREATE TABLE S

(SNO CHAR(10) NOT NULL ,

SN VARCHAR(20),

AGE INT,

SEX CHAR(2) DEFAULT ‘男’ ,

DEPT VARCHAR(20));

根据已有的表创建新表:
A:create table tab_new like tab_old (使用旧表创建新表)
B:create table tab_new as select col1,col2… from tab_old definition only

5、删除表

drop table tabname

6、增加字段
Alter table tabname

ADD <列名><数据类型>[NULL|NOT NULL]

7、修改字段

ALTER TABLE<表名>

ALTER COLUMN <列名><数据类型>[NULL|NOT NULL]

8、删除字段

ALTER TABLE<表名>

DROP COLUMN <列名><数据类型>[NULL|NOT NULL]

9、添加主键

Alter table tabname add primary key(col)

10、删除主键

Alter table tabname drop primary key(col)

11、创建索引

create [unique] index idxname on tabname(col….)

12、删除索引

drop index idxname
注:索引是不可更改的,想更改必须删除重新建。

13、创建视图

create view viewname as [select statement ]

14、删除视图

drop view viewname

二、DML—数据操纵语言

1、数据查询

数据查询是数据库中最常见的操作。在本文档里将作重点介绍。SQL语言提供SELECT语句,通过查询操作可得到所需的信息。

SELECT语句的一般格式为:

SELECT〈列名〉[{,〈列名〉}]

FROM〈表名或视图名〉[{,〈表名或视图名〉}]

[WHERE〈检索条件〉]

[GROUP BY <列名1>[HAVING <条件表达式>]]

[ORDER BY <列名2>[ASC|DESC]];

SELECT语句的执行过程是:

根据WHERE子句的检索条件,从FROM子句指定的基本表或视图中选取满足条件的元组,再按照SELECT子句中指定的列,投影得到结果表。

如果有GROUP子句,则将查询结果按照<列名1>相同的值进行分组。

如果GROUP子句后有HAVING短语,则只输出满足HAVING条件的元组。

如果有ORDER子句,查询结果还要按照<列名2>的值进行排序。

1.1、查询指定列

SELECT <列名> FROM <表名或视图名>

1.2、查询全部列

SELECT * FROM <表名或视图名>

或SELECT <全部列名> FROM <表名或视图名>

1.3、取消相同取值的行

在查询结果中有可能出现取值完全相同的行了。

SELECT DISTINCT <列名> FROM <表名或视图名>

1.4、比较大小

比较运算符有 =,>,>=,<=,<,<>,!>,!<

NOT+上述比较运算符

SELECT <列名> FROM <表名或视图名> WHERE <列名> [比较运算符] <比较的值>

1.5、多重条件查询

当WHERE子句需要指定一个以上的查询条件时,则需要使用逻辑运算符AND、OR和NOT将其连结成复合的逻辑表达式。

其优先级由高到低为:NOT、AND、OR,用户可以使用括号改变优先级。

SELECT <列名> FROM <表名或视图名> WHERE <条件1> AND <条件1> OR <条件1>…

1.6、确认范围查询

用于确定范围运算符有:BETWEEN…AND…和NOT BETWEEN…AND…

SELECT <列名> FROM <表名或视图名> WHERE <列名> [NOT] BETWEEN 值1 AND 值2

这与下等价

SELECT <列名> FROM <表名或视图名> WHERE <列名>>=值1 AND <列名><=值2

SELECT <列名> FROM <表名或视图名> WHERE <列名><值1 OR <列名>>值2

1.7、确认集合

确定集合符号:IN,NOT IN

SELECT <列名> FROM <表名或视图名> WHERE <列名>[NOT] IN (常量1,常量2,…,常量n)

1.8字符匹配查询

字符匹配查询符号:LIKE,NOT LIKE

SQL Server 2000支持如下四种通配符:

_(下划线):匹配任意一个字符;

%(百分号): 匹配O个或多个字符;

[ ]:匹配[ ]中的任意一个字符。如[acdg]表示匹配a或c或d或g,如果[ ]中的字符是有序的,则可以使用连字符一来简化[ ]中的内容,例如[abcde]可简写为:[a-e];

[^]:不匹配[ ]中的任意一个字符。如[acdg]表示不匹配a、c、d、g,如果[]中的字符是有序的,也可以使用简化形式例如[abcde]可简写为:[a-e]。

SELECT <列名> FROM <表名或视图名> WHERE <列名> [NOT] LIKE <匹配字符串>

1.9空值查询

空值不同于零和空格,它不占任何存储空间。

判断某个值是否为NULL值,不能使用普通的比较运算符(一、!一等),而只能使用专门的判断NULL值的子句来完成。

SELECT <列名> FROM <表名或视图名> WHERE <列名> IS [NOT] NULL

1.10常用库函数及统计汇总查询

常用的库函数

AVG: 按列计算平均值

SUM:按列计算值的总和

MAX:求一列中的最大值

MIN:求一列中的最小值

COUNT:按列值计算个数

总数:select count(field1) as totalcount from table1
求和:select sum(field1) as sumvalue from table1
平均:select avg(field1) as avgvalue from table1
最大:select max(field1) as maxvalue from table1
最小:select min(field1) as minvalue from table1

注1:SQL规定,当使用计算函数时,列名不能与计算函数一起使用(除非他们出现在其他集合中)。

例如查询年龄最大的学生的姓名和年龄,如下写法是错误的:

SELECT 姓名,MAX(年龄)FROM Student

注2:计算函数不能出现在WHERE子句中。 .

例如查询年龄最大的学生的姓名如下写法是错误的:

SELECT 姓名 FROM Student WHERE 年龄=MAX(年龄)

正确的命令应为:

SELECT 姓名,年龄 FROM Student

Where 年龄=(select max(年龄) from student)

1.11分组查询

SELECT <列名> FROM <表名或视图名>

GROUP BY<分组依据列>[,…n]

[HAVING<组提取条件>]

注1:分组依据列不能是text、ntext、image和bit类型的列。

注2:有分组时,查询列表中的列只能取自分组依据列(计算函数中的列除外)

1.12对查询结果进行排序

SELECT <列名> FROM <表名或视图名>

ORDER BY<列名>[ASC l DESC][,…n]

1.13数据表连接查询

A、 INNER JOIN:

这是最普通的联接类型。只要在这两个表的公共字段之中有相符值,内部联接将组合两个表中的记录。

SELECT fields
FROM table1 INNER JOIN table2
ON table1.field1 compopr table2.field1 AND
ON table1.field2 compopr table2.field2) OR
ON table1.field3 compopr table2.field3)];
B、left outer join:
左外连接(左连接):结果集包括连接表的匹配行,也包括左连接表的所有行。
SQL: select a.a, a.b, a.c, b.c, b.d, b.f from a LEFT OUT JOIN b ON a.a = b.c
C:right outer join:
右外连接(右连接):结果集包括连接表的匹配连接行,也包括右连接表的所有行。
D:full outer join:
全外连接:不仅包括符号连接表的匹配行,还包括两个连接表中的所有记录。

1.14使用TOP限制结果集

使用TOP谓词时注意最好与ORDER BY子句一起使用,因为这样的前几名才有意义。但当使用WITH TIES时,要求必须使用ORDER BY子句。

TOP谓词写在SELECT单词的后边,查询列表的前边。

使用TOP谓词的格式为:

TOP n[percent]with ties]

其中:n为非负整数。

TOP n:表示取查询结果的前n行;

TOP n percent:表示取查询结果的前n% 行;  

With ties:表示包括并列的结果。

1.15将查询结果存入表中

INTO子句的语法格式为:

INTO 新表名

INTO子句跟在SELECT子句之后、FROM子句之前。SELECT <列名> INTO 新表名 FROM。

新表名是要存放查询结果的表名,SELECT INTO语句包含两个操作:首先按查询列表创建新表,然后执行查询语句,并将结果保存到新表中。

用INTO子句创建的新表可以是永久表,也可以是临时表。临时表又分为两种:局部临时表和全局临时表。局部临时表要在表名前加#,它只能用在当前的连接中;全局临时表要在表名前加##,它的生存期为创建全局临时表的连接的生存期

1.16合并查询

使用UNION的格式为:

SELECT 语句1

UNION

SELECT 语句2

UNION [ALL]

SELECT 语句n

使用UNION的两个基本规则是:

A、所有查询语句中的列个数和列的顺序必须相同。

B、所有查语句中的对应列的数据类型必须兼容。

1.17子查询

A、使用子查询进行比较测试

使用子查询进行比较测试时,通过比较运算符(=、!=、<、>、<=、>=),将一个表达式的值与子查询返回的单值进行比较。如果比较运算的结果为True,则比较测试也返回True。

使用子查询进行的比较测试要求子查询语句必须是返回单值的查询语句。

例1:查询修了"c02"课程的且成绩高于此课程的平均成绩的学生的学号和成绩。

SELECT 学号,成绩 FROM SC

WHERE 课程号=‘c02’

and 成绩>( SELECT AVG(成绩) from SC

WHERE 课程号=‘c02’)

B、使用子查询基于集合的测试

使用子查询进行基于集合的测试时,通过运算符IN和NOT IN,将一个表达式的值与子查询返回的结果集进行比较。这同前边在WHERE子句中使用的IN作用完全相同。使用IN运算符时,如果该表达式的值与集合中的某个值相等,则此测试为True;如果该表达式与集合中的所有值均不相等,则返回False。

注意:使用子查询进行基于集合的测试时,由该子查询返回的结果集是仅包含单个列的一个列表,该列必须与测试表达式的数据类型相同。当子查询返回结果之后,外层查询将使用这些结果。  

C、 使用子查询进行存在性测试

使用子查询进行存在性测试时,往往使用EXISTS谓词。带EXISTS谓词的子查询不返回查询的数据,只产生逻辑真值和逻辑假值。

例6:查询选修了‘‘c01”号课程的学生姓名。

SELECT 姓名 FROM Student  

WHERE EXISTS

(SELECT * FROM SC

WHERE 学号=Student.学号

AND 课程号=‘c01’)

注1:带EXISTS谓词的查询是先执行外层查询,然后再执行内层查询。由外层查询 的值决定内层查询的结果;内层查询的执行次数由外层查询的结果数决定。

上述查询语句的处理过程为:

(1)找外层表Student表的第一行,根据其学号的值处理内层查询;

(2)用外层的值与内层的结果比较,由此决定外层条件的真、假值;如果为真,则此记录为符合条件的结果;

(3)顺序处理外层表Student表中的第2、3、…行。

注2:由于EXISTS的子查询只能返回真或假值,因此在这里给出列名无意义。所以在有EXISTS的子查询中,其目标列表达式通常都用“*”。

2.数据更新

SQL语言的数据更新语句DML主要包括插入数据、修改数据和删除数据三种语句。

2.1插入一行新记录

INSERT INTO <表名>[(<列名1>[,<列名2>…])] VALUES(<值>)

2.2插入一行的部分数据值

只写上部分列名,没有写上的列名值自动为空,如果列是NOT NULL则必需赋值。

2.3插入多行记录

INSERT INTO <表名> [(<列名1>[,<列名2>…])] 子查询

2.4修改数据

UPDATE <表名>

SET <列名>=<表达式> [,<列名>=<表达式>]…

[WHERE <条件>]

2.5删除记录

DELETE

FROM<表名>

[WHERE <条件>]

三、DCL—数据控制语言

1、权限与角色

在SQL SERVER中,权限可分为系统权限和对象权限。

系统权限由数据库管理员授予其他用户,是指数据库用户能够对数据库系统进行某种特定的操作的权力。创建一个基本表(CREATE TABLE)

对象权限由创建基本表、视图等数据库对象的用户授予其他用户,是指数据库用户在指定的数据库对象上进行某种特定的操作的权力。如查询(SELECT)、插入(INSERT)、修改(UPDATE)和删除(DELETE)等操作。

角色是多种权限的集合,可以把角色授予用户或其他角色。当要为某一用户同时授予或收回多项权限时,则可以把这些权限定义为一个角色,对此角色进行操作。这样就避免了许多重复性的工作,简化了管理数据库用户权限的工作。

2、系统权限与角色的授予

SQL语言使用GRANT语句为用户授予系统权限,其语法格式为:

GRANT <系统权限>|<角色> [,<系统权限>|<角色>]…

TO <用户名>|<角色>|PUBLIC[,<用户名>|<角色>]…

[WITH ADMIN OPTION]

其语义为:将指定的系统权限授予指定的用户或角色。

其中:PULBIC代表数据库中的全部用户。WITH ADMIN OPTION为可选项,指定后则允许被授权的用户将指定的系统特权或角色再授予其他用户或角色。

例1: 为用户张三授予CREATE TABLE的系统权限。

GRANT CREATE TABLE

TO 张三

3、系统权限与角色的收回

数据库管理员可以使用REVOKE语句收回系统权限,其语法格式为:

REVOKE <系统权限>|<角色> [,<系统权限>|<角色>]…

FROM <用户名>|<角色>|PUBLIC[,<用户名>|<角色>]…

例2: 收回用户张三所拥有的CREATE TABLE的系统权限。

REVOKE CREATE TABLE

FROM 张三

4、对象权限与角色的授予

SQL语言使用GRANT语句为用户授予对象权限,其语法格式为:

GRANT ALL|<对象权限>[(列名[,列名]…)][,<对象权限>]…ON <对象名>

TO <用户名>|<角色>|PUBLIC[,<用户名>|<角色>]…

[WITH GRANT OPTION]

其语义为:将指定的操作对象的对象权限授予指定的用户或角色。

其中:

ALL代表所有的对象权限。

列名用于指定要授权的数据库对象的一列或多列。如果不指定列名,被授权的用户将在数据库对象的所有列上均拥有指定的特权。

实际上,只有当授予INSERT、UPDATE权限时才需指定列名。

ON子句用于指定要授予对象权限的数据库对象名,可以是基本表名、视图名等。

WITH ADMIN OPTION为可选项,指定后则允许被授权的用户将权限再授予其他用户或角色。

例3: 将对Sc表和student表的所有对象权限授予USER1和USER2。

GRANT ALL

ON Sc,student

TO USER1,USER2

例4: 将对Course表的查询权限授予所有用户。

GRANT SELECT

ON Course

TO PUBLIC

例5: 将查询student表和修改学生年龄的权限授予USER3,并允许将此权限授予其他用户。

GRANT SELECT,UPDATE(PROF)

ON studentT

TO USER3

WITH ADMIN OPTION

USER3具有此对象权限,并可使用GRANT命令给其他用户授权,如下例,USER3将此权限授予USER4:

GRANT SELECT,UPDATE(年龄)

ON student

TO USER4

5、对象权限与角色的回收

所有授予出去的权力在必要时都可以由数据库管理员和授权者收回,收回对象权限仍然使用REVOKE语句,其语法格式为:

REVOKE <对象权限>|<角色> [,<对象权限>|<角色>]…

FROM <用户名>|<角色>|PUBLIC[,<用户名>|<角色>]…

例6: 收回用户USER1对Course表的查询权限。

REVOKE SELECT

ON C

FROM USER1

其次,大家来看一些不错的sql语句
1、说明:复制表(只复制结构,源表名:a 新表名:b) (Access可用)
法一:select * into b from a where 1 <>1
法二:select top 0 * into b from a

2、说明:拷贝表(拷贝数据,源表名:a 目标表名:b) (Access可用)
insert into b(a, b, c) select d,e,f from b;

3、说明:跨数据库之间表的拷贝(具体数据使用绝对路径) (Access可用)
insert into b(a, b, c) select d,e,f from b in ‘具体数据库’ where 条件
例子:…from b in ‘"&Server.MapPath(".")&"/data.mdb" &"’ where…

4、说明:两张关联表,删除主表中已经在副表中没有的信息

delete from table1 where not exists ( select * from table2 where table1.field1=table2.field1 )

5、说明:四表联查问题:
select * from a left inner join b on a.a=b.b right inner join c on a.a=c.c inner join d on a.a=d.d where …

6、说明:日程安排提前五分钟提醒
select * from 日程安排 where datediff(‘minute’,f开始时间,getdate())>5

7、说明:一条sql 语句搞定数据库分页
select top 10 b.* from (select top 20 主键字段,排序字段 from 表名 order by 排序字段 desc) a,表名 b where b.主键字段 = a.主键字段 order by a.排序字段

8、说明:前10条记录
select top 10 * FROM table1 where 范围

9、说明:选择在每一组b值相同的数据中对应的a最大的记录的所有信息(类似这样的用法可以用于论坛每月排行榜,每月热销产品分析,按科目成绩排名,等等.)
select a,b,c from tablename ta where a=(select max(a) from tablename tb where tb.b=ta.b)

10、说明:包括所有在 TableA 中但不在 TableB和TableC 中的行并消除所有重复行而派生出一个结果表
(select a from tableA ) except (select a from tableB) except (select a from tableC)

11、说明:随机取出10条数据
select top 10 * from tablename order by newid()

12、说明:随机选择记录
select newid()

13、说明:删除重复记录
Delete from tablename where id not in (select max(id) from tablename group by col1,col2,…)

14、说明:列出数据库里所有的表名
select name from sysobjects where type=‘U’

15、说明:列出表里的所有的
select name from syscolumns where id=object_id(‘TableName’)

16、说明:列示type、vender、pcs字段,以type字段排列,case可以方便地实现多重选择,类似select 中的case。
select type,sum(case vender when ‘A’ then pcs else 0 end),sum(case vender when ‘C’ then pcs else 0 end),sum(case vender when ‘B’ then pcs else 0 end) FROM tablename group by type
显示结果:
type vender pcs
电脑 A 1
电脑 A 1
光盘 B 2
光盘 A 2
手机 B 3
手机 C 3

17、说明:初始化表table1
TRUNCATE TABLE table1

18、说明:选择从10到15的记录
select top 5 * from (select top 15 * from table order by id asc) table_别名 order by id desc
  
19、查询时字符串连接(用+号)

select YHDM+’[’+YHMC+’]’ YH from yonghu

猜你喜欢

转载自blog.csdn.net/adzn1/article/details/86550841