数据库

数据库系统相关概念

数据:符号序列,是计算机能够识别、存储、处理的描述。

信息:数据的含义

数据处理:数据—>信息的过程

数据库:长期存储在计算机内,有组织,可共享的大量数据的集合

数据库管理系统(DBMS):用户与数据库之间的管理软件。它提供了:

数据定义功能(数据定义语言DDL)

数据操纵功能(数据操纵语言DML)

数据控制语言(数据控制语言DCL)

数据组织、存储、管理功能

数据库建立维护功能

数据库系统的组成

硬件系统

数据库集合

系统软件

数据库管理员

用户

数据库系统有如下功能:

定义数据库

查询数据库

更新数据库

多用户访问

存储大量数据

数据库系统特点

数据结构不是面向单一应用,而是面向全组织

数据冗余小、易扩充

数据独立程序

统一的数据管理功能

  • 数据安全控制

  • 数据完整性控制

  • 数据的并发控制(解决多用户情况)

数据库管理阶段

人工管理阶段

文件系统阶段

数据库系统阶段

模型

概念模型: 按照用户需求对数据库和信息进行建模,涉及实体、属性、联系等概念

  • 实体:客观存在且可相互区分的事物

  • 属性:实体具有的特性

  • 联系:实体之间的关联关系。联系涉及的概念:

    1.度:参与联系的实体的个数。

    2.角色名称:实体在联系中扮演的角色

    3.递归关系:同一实体以不同的角色名称参与到同一联系中

    4.基数比:一对一、一对多、多对多

    5.联系约束:约束参与联系的实体的集合,分为:

    • 基数比
    • 参与约束

6.参与约束:每个实体所能参与联系的最小个数

  • 完全参与约束也称存在依赖,每个实体都要遵循
  • 部分参与约束

数据模型

数据模型的基本要素

  • 数据结构
  • 数据操作
  • 数据完整性约束

数据的逻辑模型,省略数据存储的细节,主要用于DBMS的实现,分以下类别:

  • 层次模型(实体之间联系用形结构表示)

  • 网状模型(实体之间的联系用有向图表示)

  • 关系模型(实体之间的联系由来表示)
  • 面向对象模型(把实体表示为类,对象中包含了对象内元素的联系和对象之间的联系)

物理模型

  • 数据在计算机中存储的具体细节,能够由DBMS理解的模式

三层模式结构

  • 外部层含外模式(视图)

    外部层包含很多外模式,描述特定的数据库

  • 概念层含概念模式(关系)

    概念层有一个概念模式也称模式,描述数据逻辑,实体、数据类型、用户操作、约束、安全性和完整性信息

  • 内部层含内模式(索引)

    内部层有一个内模式,描述数据库物理储存结构

  • 数据库语言

    • 数据查询语言(DQL): 数据查询

    • 数据定义语言(DDL): 定义数据结构,创建、修改、删除数据库

    • 数据操纵语言(DML): 追加、插入、删除、修改、检索数据

    • 数据控制语言(DCL): 控制数据库权限、事务发生时间、监视数据库

SQL:关系数据库的标准语言,非过程化。

数据库管理员(DBA)的职责

  • 定义并存储数据库内容
  • 监督和控制数据库使用
  • 负责数据库的日常维护
  • 必要时重组、改进数据库

关系数据模型

关系数据模型主要由:关系数据结构、关系数据操作、关系完整性约束组成。

关系需要满足的条件、性质:

  • 每一列都不可再分(原子性)
  • 属性名不可重复
  • 表中元组的顺序可以调换
  • 同一属性名下的属性应该在同一域中,是同一数据类型
  • 元组不能重复

关系的类型:

  • 基本关系:实际存在的表,是数据的逻辑表示
  • 查询表:查询结果对应的表
  • 视图表:是基本表或其他视图表导出的虚表,不存储数据

元组:关系的一行

属性:关系的一列

域:属性的取值范围,是一组具有相同数据类型的值的集合

基数:域中包含值的个数

度:表长

分量:元组中的一个属性值

键:唯一区分不同元组的属性或属性的集合

候选键:唯一区分不同元组的属性或属性的集合

主键:候选键中选取的一个作为关系的主键

主键唯一

每个关系必须有且只有一个主键

外键:属性(属性组)F不是关系R的键,但F却是关系S的主键,则属性F是关系R的外键。

关系R:参照关系

关系S:被参照关系

R和S可以是同一个关系

S的主键和F必须在同一域上

关系

笛卡尔积有意义的子集,是一张2维表

关系模式:一般表示为:

R(U,D,DOM,F)

R:关系名

U:属性名

D:U中属性的域

DOM:属性向域的映射集合

F:数据依赖关系的集合

关系完整性:

  • 实体完整性:主键唯一且非空
  • 参照完整性:外键要么为空,要么是另一个关系的主键
  • 用户自定义完整性、
  • (空值)

数据操作(为集合操作的方式)

关系代数:代数方式

关系演算:逻辑方式

关系代数(抽象 )

关系运算符:

集合运算符

比较运算符

关系运算符

逻辑运算符

关系代数的操作:

集合运算:并、差、交、笛卡尔积

关系运算:投影、选择、连接、除、更名

扩展的关系运算:广义投影、聚集函数、分组、递归闭包

专门的关系运算

选择运算($\sigma$):选择符合条件的元组组成新的关系。取R中符合条件的元组:

$\sigma_{条件}(R)$

投影运算($\pi$):选择符合条件的属性组成新的关系。取关系R的第i、j列:

$\pi_{i,j}(R)$

连接运算($\infin$)

自然连接:$R_1\infin R_2$

  • 计算关系间的笛卡尔积
    • 选择在两个关系中属性相等的元组
  • 删除重复属性

    外连接

    条件连接:$R|\times|S_{条件}$

  • $\sigma_{条件}(R\times S)$

除运算($\div$)

$A(x, y)\div B(y,z)$

  • 找出x的域,对应每一个x取值,求出对应的象集t
  • 求出投影$\pi_y(R_2)$
  • 找出t中包含投影的值,即为除运算结果

更名运算($\rho$)

$\rho_{新表名(新属性名)(R)}$

SQL

sql提供的功能

  • 数据定义功能(DDL)
  • 数据操纵功能(DML)
  • 数据控制功能(DCL)
  • 数据查询功能(DQL)

sql的特点

  • 非过程化语言
  • 统一的语言
  • 面向集合的操作方式
  • 可独立使用又可嵌入主语言使用
操作对象 创建 修改 删除
数据库 create database alter database drop database
create table alter table drop table
视图 create view drop view
索引 create index drop index

在表中插入元组:

1
2
INSERT INTO <table>[(属性列表)] VALUES(属性值列表);
INSERT INTO <table>[(属性列表)] 子查询;

更新表数据:

1
2
3
UPDATE <table>
SET <属性列>=<表达式>[,<属性列2>=<>, ...]
WHERE <条件>

删除表元组

1
DELETE FROM <table> [WHERE <元组选择条件>]

修改表的命令:

1
2
3
4
5
6
7
ALETR TABLE table_name
[ADD <新列名> <数据类型> <约束>
|ALTER COLUMN <列名> <数据类型>
|DROP COLUMN <列名>
|ADD CONSTRAINT <约束名> <约束定义>
|MODIFY CONSTRAINT <列名> <约束定义>
|DROP CONSTRAINT <约束名>];

查询表的命令:

1
2
3
4
5
6
7
SELECT [DISTINCT|ALL] <*|属性列[AS 新的列名]>
<FROM> <表名|视图名|> [别名]
[INTO <新表名>]
[WHERE <条件>]
[GROUP BY <分组依据>]
[HAVING <分组选择条件>]
[ORDER BY <排序依据>[ASC|DESC]];

WHERE后接判断:

属性 [NOT] BETWEEN constant1 AND constant2

属性 [NOT] LIKE 字符串或通配符

属性 IS [NOT] NULL

属性|常量 [NOT] IN (SELECT 子句)|元组

[NOT] EXITS (SELECT 子句)

LIKE 匹配的通配符

字符 含义
_ 匹配任意一个字符
% 匹配任意长度字符
[] 查询一定范围字符(类似正则表达式)
[]的非
字符串本身含有’_’、’%’ 使用ESCAPE ‘string’或 ‘\’ 转义

聚集函数(只能出现在select、having中,不能在where中出现)

出现在分组依据中的属性名不能出现在聚集函数中

聚集函数使用 意义
count(*) 统计属性元组个数
count([DISTINCT] 属性名) 统计属性名下[不重复]的非空分量个数
sum([DISTINCT] 属性名) 对属性名下所有[不重复]分量求和
avg([DISTINCT] 属性名) 对属性名下所有[不重复]分量均值
max(属性名) 求属性列下最大值
min(属性名) 求属性列下最小值

分组

除聚集函数之外,select中出现的属性必须来自group by中的属性列

连接查询

连接操作执行的方法:

  • 循环嵌套法
  • 排序合并法
  • 索引连接法

连接方式

  • 内连接

    1. FROM table1 JOIN table2 ON <连接条件>
    2. WHERE <连接条件>
    3. 自然连接
  • 自连接

    同一张表内子表之间的连接查询

  • 外连接

    1. SQL SERVER

      1
      2
      3
      4
      5
      6
      SELECT <查询列表>
      [INTO <新表名>]
      FROM <table1|view1> [AS new_name1]
      <LEFT|RIGHT|FULL>[OUTER] JOIN>
      <table2|view2> [AS new_name2]
      ON <连接条件>
    2. WHERE A (+)= B #ORACLE数据库左外连接

    3. WHERE A =(+) B #ORACLE数据库右外连接

    • LEFT OUTER JOIN: 左外连接

      输出左表所有记录列值,右表输出与左表向匹配的记录,没有输出NULL

    • RIGHT OUTER JOIN:右外连接

      输出右表所有记录列值,左表输出与右表向匹配的记录,没有输出NULL

    • FULL OUTER JOIN:全连接

      $left_outer_join \cup right_outer_join$

    TOP限制结果集

    1
    SELECT TOP n [WITH TIES] <column_name>
    • 使用 WITH TIES会将并列值捆绑,同时输出,并算作同一排名

    ANY ALL

    ANY: 至少一个值要满足条件

    ALL:所有值都满足条件

    • eg.找出年龄大于所有女性的男性客户
    1
    2
    3
    select name, age
    from user
    where sex='male' and age > ALL(select age from user where sex='female');

视图

定义

1
2
3
CREATE VIEW <view_name> [(view list)]
AS <子查询>
[<WITH CHECK OPTION>];

以下情况需要指定view list:

  • 子查询中有聚集函数或者列表达式
  • 多表连接出现相同属性名
  • 需要给属性更名

子查询限制

不能含有ORDER BY

不能含有DISTINCT

删除

DROP VIEW view_name [CASCADE];

  • 加CASCADE后会联级删除该视图与其导出的所有视图

查询

与关系的查询相同

更新

只有满足以下所有条件的视图才能对基表进行更新操作:

  • 视图只定义在一个基表上
  • 子查询不含 GROUP BY、DISTINCT、聚集函数
  • 视图列不含计算表达式
  • 视图列包含所有基表中NOT NULL的列

作用

  • 视图能简化用户操作
  • 视图对重构的数据库提供一定程度的逻辑独立性
  • 视图为用户提供多个视角看同一数据
  • 提高数据安全性
  • 保证数据完整性

索引

特点:空间换时间(hash)

分类

  • 聚簇索引

    索引与数据储存在相邻等待数据空间,并使行的物理顺序和索引的顺序一致

    对经常搜索范围值的列非常有效

  • 非聚簇索引

    数据和索引分开存储,索引以指针指向数据

    精确匹配查询最佳方法

  • 唯一索引

    保证索引列不含重复值

    创建PRIMARY KEYUNIQUE约束会自动在表上场景唯一索引

创建

1
2
3
4
5
6
7
8
9
10
#NONCLUSTERED:非聚簇索引
#CLUSTERED:聚簇索引
#UNIQUE:唯一索引

CREATE [UNIQUE][CLUSTERED][NONCLUSTERED] INDEX <索引名>
ON <表名|视图名> (列名[ASC|DESC]);

#eg.按ID和姓名创建用户的唯一索引
CREATE UNIQUE INDEX idIndex
ON web_user(user_id ASC, user_name DESC);

删除

1
DROP INDEX [user_name] <index_name>;

索引建立原则

  • 选择数据量大的表建立索引
  • 索引建立数量要适量
  • 选择合适时机建立索引
  • 优先选择主键建立索引
  • 为支撑连接操作,考虑在外码上建立非聚簇索引
  • 最好选择包含大量非重复值的列建立非聚簇索引

SQL server的三类操作系统文件

  • 主文件 .mdf: 数据库启动信息数据信息,每个数据库只有1个
  • 次要文件 .ndf:主文件以外的所有信息, 可以有
  • 事务日志 .ldf:恢复数据库的日志信息,每个数据库至少有1个

完整性约束

分类

  • 类型约束
  • 属性约束
  • 关系变量约束
  • 数据库约束
约束 含义 条件
PRIMARY KEY
UNIQUE
NOT NULL
CHECK 约束属性范围
FOREIGN KEY

数据库安全

授权

授予权限

GRANT 权限 ON TABLE_NAME TO USERNAME [WITH GRANT OPTION]

1
GRANT UPDATE(age) ON table TO kkyou WITH GRANT OPTION; 

表示将更新table表的age属性的权限授予kkyou用户, 并且kkyou用户可以将自己的权限授予他人

角色

创建角色

1
2
3
4
GRANT ROLE Adam; #创建角色Adam
GRANT SELECT ON user TO Adam; #将查询user表的权限分配给Adam
GRANT Adam TO kkyou; #将角色Adam授予用户kkyou
GRANT Adam TO qiao; #将角色Adam授予用户qiao

收回权限

1
REVOKE [WITH GRANT OPTION FOR] 权限 ON TABLE_NAME FROM USER_NAME;

数据库恢复

事务

定义

事务是用户定义的一个数据库操作序列,每个事务都是不可分割的整体

事务相关的命令

BEGIN TRANSACTION #开始事务

ROLLBACK #执行错误,回滚,撤销所有操作

COMMIT #执行成功,提交事务

END TRANSACTION #结束事务

事务的ACID特性

  • 原子性Atomic: 事务操作要么全部执行,要么都不执行,不可分割

  • 一致性Consistent: 事务单独运行时(没有并发运行的其它事务),应保证数据库的一致性

  • 隔离性Isolation: 多事务并发执行,每个事务之间不可相互干扰

  • 持续性Duration: 事务一旦提交,它对数据库的数据改变应该是永久的

数据库恢复

需要解决的问题

  • 如何建立冗余数据
  • 如何利用冗余数据恢复数据库
日志

定义

日志是DBMS用来记录事务对数据库进行更新操作的文件

描述内容

  • 事务标识符:事务的唯一标识符
  • 数据项标识符:事务操作对象的唯一标识符
  • 前像(BI):更新前数据的旧值
  • 后像(AI):更新后数据的新值

日志存储

先存入缓冲区,再存入稳定的储存器

记录形式

  • \ 事务T已经开始
  • \ 事务T成功提交
  • \ 事务T不能成功完成,已终止
  • \ 事务T对数据项X进行操作,前像为V1,后像为V2

先写日志规则

  • 在主存中的数据块输出到数据库之前,所有与该数据库有关的日志记录必须已输出到稳定的储存器上(先更新日志,再更新数据库)
  • 事务要进入提交状态,必须是事务的日志,包括COMMIT记录都写入稳定存储器

保持事务原子性的三种方案

  • 后像(BI)在事务提交之后再写入数据库(恢复将数据更新为新值)

    缺点:增加事务平均缓冲的次数

    日志记录: \, V为后像,不储存前像。

动作 日志
READ(data1 \
change data1
WRITE(data1) \
$\dots$
\
将数据写入磁盘(数据库)

恢复步骤(从前向后,改为最后的新值)

  • 从后向前扫描日志文件,将COMMIT的事务放入redo-list中
    • 从前向后扫描日志文件,对每个\记录:
    • 若T不是redo-list中的事务,则什么都不做
    • 若T是redo-list中的事务,则将新值V写入磁盘
  • 对未完成的事务,在日志文件中写入\记录并刷新日志
  • 后像(AI)在事务提交前完全写入数据库(恢复将数据改回旧值)

    缺点:增加磁盘I/O次数

    日志记录: \ V为前像,不储存后像。

    要保证系统故障后恢复,需满足:

    • 记录数据变化的日志记录要在数据项写入磁盘前写入稳定的存储器
    • COMMIT日志记录要在数据项写入磁盘后再写入稳定的存储器
动作 日志
\
READ(data1)
change data1
WRITE(data1) \
$\dots$
更新数据到磁盘
\
**恢复步骤** (从后向前,改为最初的旧值)
  • 对日志文件从后向前扫描,将的事务放入redo-list中
  • 对每一个,若T不在redo-list中,则什么都不做
  • 对每一个,若T在redo-list中,则将X置为旧值V
  • 后像(AI)在事务提交前后写入数据库

    日志记录:\

    恢复步骤(已提交记录设为新值,未提交记录回滚为旧值)

    • 对日志文件从后向前扫描,对有COMMIT记录和没有的事务分别放两个队列:redo-list、undo-list
    • 从前向后扫描日志文件,重新执行redo-list中的事务
    • 从后向前扫描日志文件,撤销执行undo-list中的事务

创建检查点的方法

  • 提交一致性检查点

    • 新的事务不能开始直到检查点完成
    • 现有的事务继续执行到提交或中止,并将相关日志都写入稳定存储器
    • 将当前日志缓冲区中的日志写回稳定存储器中的日志文件
    • 将日志记录\写入存储器

检查点之前的事务都已经完成,因此无需恢复之前的数据。

恢复时,从后向前扫描日志,\后的记录都要恢复

  • 高速缓存一致性检查点

    • 新的事务不能开始直到检查点完成
    • 已存在的事务不允许进行更新操作
    • 将当前日志缓冲区中的日志写回稳定存储器中的日志文件
    • 将数据缓冲区的所有数据写入磁盘
    • 将日志记录\写入存储器,L是所有活动事务的列表

检查点前:事务已提交,不处理

检查点中:事务处于活动状态,若事务提交,执行REDO,否则执行UNDO

若后像在事务提交后才写入数据库,没有提交记录时,不必UNDO

若后像在事务提交前写入数据库,有提交记录,不必执行REDO

  • 模糊一致性检查点

数据转储

备份级别

  • 完全转储:备份整个数据库
  • 增量备份:只备份上次备份后改变的数据

转储分类

  • 静态转储:转储期间不允许有事务进行
  • 动态转储:转储期间允许事务对数据库进行更新(需要用日志记录纠正数据库不一致状态)

故障类型

  • 事务故障

    • 逻辑错误(错误输入、找不到数据、溢出等)
    • 系统错误(系统进入不良状态,如死锁)

      恢复:使用之前的三种方案中的一种即可

  • 系统故障:硬件、软件故障

    恢复:使用之前的三种方案中的一种即可

  • 介质故障:数据传输过程中发生的内容错误

    恢复:完成转储备份工作

RAID(冗余磁盘阵列)

目的:应对介质故障带来的严重影响

优点

  • 成本低、功耗小、传输效率高
  • 可提供容错功能
  • 具备数据校验功能

事务的并发控制

优点

  • 改善系统资源利用
  • 简短事务等待时间

调度:事务按时间排序的序列

串行调度:事务是一个个执行的(n个事务进行串行调度,有n!种方式)

交叉调度可能引起的问题

  • 读脏数据

    脏数据:未提交事务所写的数据(读了ROLLBACK之前的数据)

  • 不可重复读(两次读到不同的数据)

  • 丢失修改(两个事务同时修改数据,其中一个事务的修改丢失)

可串行化

若多个事务交叉调度和某一串行调度的结果相同时,该调度为可串行化的

冲突可串行化

多事务交叉调度发生冲突的条件

  • 不同事务对同一数据操作
  • 不同事务操作指令中含W操作

冲突可串行化判断方法:

交换调度S不冲突的指令,得到新的调度S’。S和S’是冲突等价的,若一个调度冲突等价于一个串行调度,则该调度室冲突可串行化的。

视图可串行化

对同一事务集,若事务在任何时候都读到相同的值,写入数据库的最终状态也是相同的,则称事务视图等价

可串行化判断

  • 调度冲突等价于串行调度
  • 前驱图(点为事务,产生冲突则连线,入度点为先执行的,若产生环,则不可串行调度)

可恢复条件

不读脏数据(Ti读了Tj修改的数据,则Tj事务先提交,Ti后提交)

基于锁的并发控制协议

加锁方式(S和X锁之间都是不相容的)

  • 共享锁(S):可读数据,不可写(申请队列为空,数据项没有排它锁,则可授予)SLOCK(Q)
  • 排他锁(X):可以读写数据(申请队列为空,数据项没有锁,则可授予)XLOCK(Q)
  • ULOCK(Q):释放数据项Q的锁,事务提交或中止即可释放锁

两段锁协议(2PL)

保证冲突可串行化的充要条件,不能预防死锁

  • 增长阶段:读写数据前,应首先获得数据的锁
  • 收缩阶段:释放锁后,事务不再申请获得其他任何锁

活锁、死锁

活锁

其他事务一直抢先得到锁,使得一个事务一直得不到锁

死锁

事务互相等待,形成死锁

死锁的预防

  • 顺序封锁法:事务对锁的申请按顺序进行

  • 一次封锁法:事务执行前获得所有锁,若不能获得所需的所有锁,则事务中止

    缺点:事务开始前很难知道所需的所有锁、一次性获得所有锁,但有的锁会在很久之后使用,降低系统并发度

  • 用时间戳的死锁预防:早运行的事务优先级高

    • Wait-die机制:申请锁时,若事务相较冲突的事务优先级高,则等待,否则中止
    • Wound-wait机制:申请锁时,若事务相较冲突的事务优先级高,则中止正在使用锁的事务,否则事务等待

死锁的检测预防

  • 等待图(点为事务,当事务A等待B释放锁时,边由A连向B)

  • 死锁解除:撤销事务

    需要解决的问题:

    • 撤销事务的选择
    • 撤销事务的程度(全部回滚|部分回滚)

数据库设计理论

设计的不好的数据库可能出现的问题:

  • 数据冗余
  • 操作异常
    • 更新异常
    • 删除异常
    • 插入异常

模式分解是解决数据冗余的主要方法

数据依赖包括

  • 函数依赖
  • 多值依赖
  • 连接依赖

函数依赖

关系模式R(U)中,$X\subseteq U, Y\subseteq U$,若t[X] = s[X],则有t[Y] = s[Y],则称属性或属性组Y函数依赖于X

FD X$\to$Y (X:决定因子、Y:依赖因子)

  • 平凡函数依赖

    若$X\subseteq Y, 则X\to Y$

  • 完全函数依赖

  • 部分函数依赖

  • 传递函数依赖

    $X \to Y, Y \nrightarrow X, Y\rightarrow Z,则 Z对X传递函数依赖$

    $X \to Y, Y \nrightarrow X, Y\rightarrow Z \models X\rightarrow Z$

F的闭包:F和F所蕴含的依赖的全体

属性集的闭包

关系无损分解的判断方法

  • 追逐法

  • 若关系被分解为两个关系,则无损分解的充要条件是:

    $(U_1\cap U_2)\rightarrow (U_1 - U_2)$或

    $(U_1\cap U_2)\rightarrow (U_2-U_1)$

  • $X\to Y 在R上成立,且X\cap Y=\phi,则{R-Y, XY}是无损分解$

保持函数依赖的分解的判断方法

执行以下算法:

Z=X

while Z 改变 do{

​ for i=1 to k{

​ $Z = Z\cup ((Z \cap U_i)^+\cap U_i)$

​ }

}

若最终所有的Z包含正确的$Z^+$,则分解保持函数依赖

规范化

1NF

每个属性值都是原子值

2NF

每个非主属性都完全函数依赖于候选码

3NF

每个非主属性都不传递依赖于候选码,每个非主属性都完全函数依赖于候选码

$2NF\subset 3NF$

BCNF

$R\in$ 1NF,且每个属性都不传递依赖于R的候选码,则R是BCNF模式

数据库设计

数据库设计方法

  • 直观设计法
  • 规范化设计法
  • 面向对象设计法
  • 计算机辅助方法

数据库设计的基本过程

  • 需求分析阶段

    需求调研

    需求分析

    编写需求分析说明书

    需求分析说明书验证

  • 概念设计阶段

  • 逻辑设计阶段

  • 物理设计阶段

  • 实现阶段

  • 运行和维护阶段

ER图

全局视图集成的重要工作:

  • 冲突消除
  • 冗余消除与优化

局部模式间的冲突:

  • 属性冲突
  • 命名冲突
  • 结构冲突

数据字典

  • 数据项
  • 数据结构
  • 数据流
  • 数据存储
  • 数据处理

存取方法设计

  • 聚簇
  • 索引
  • hash

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!