Taogen's Blog

Stay hungry stay foolish.

本篇将介绍关系型数据库,数据库查询语言(SQL 语言)和形式关系查询语言。你将了解关系型数据库的详细内容,SQL 语句的使用,以及形式关系查询语言的用法。

介绍关系型模型

关系型模型是如今商业的数据处理应用程序的主要数据模型。关系型模型因其简单易用的特点被广泛使用。

关系型数据库的结构

关系型数据库是一组(Table)组成,每一个表分配一个唯一的名称。

关系型模型中,术语关系(Relation)用于参考数据库的表(Table),术语元组(Tuple)用于参考数据库的行(Row),术语属性(Attribute)参考数据库的列(Column)。术语关系实例(Relation Instance)参考数据的实例。一个关系由多个元组构成。一个元组由多个属性值构成。每一个属性的取值范围称为(Domain)。所有的属性的域是原子的、不可分的。null 值是一个特殊的值,它表示未知或者不存在。

数据库模式

数据库的表现形式可以分为逻辑的和物理的。数据库模式(Database Schema)是数据库的逻辑设计。数据库实例(Database Instance)表示数据库某个时刻的数据的快照。数据库模式由一组属性和对应的域组成。

我们必须有一种方法去区分一个关系中的不同的元组。Superkey 是一个或多个属性组合,它可以唯一的标识关系中的一个元组。如,在关系型数据库中我们常用 ID 来标识一个行记录。没有子集 Superkey 的 Superkey ,这种最小的 Superkey 称为 候选键(Candidate Key)。在数据库中我们使用术语主键(Primary Key)来表示数据库设计人员选择的候选键。在一个关系中的任何两个元组不允许出现相同的 key 属性值。

一个关系 r1 的属性是另一个关系 r2 的主键,这个属性称为外键(Foreign Key)。参考完整性约束(Referential Integrity Constraint)表示一个外键属性值出现在参考关系中,它也必须出现在被参考的关系中。

模式图

数据库的主键和外键依赖可以通过模式图(Schema Diagram)来表示。每个关系用一个矩形表示,主键属性添加下划线表示,外键依赖表示为从外键属性出发的带箭头的线指向被参考的关系的主键属性。一个数据库模式图的例子,如下图所示。

数据库查询语言

数据库查询语言包括过程和非过程的。

非过程查询语言(Nonprocedural Language)是指用户描述希望得到的信息,但没有给出一个获得信息的具体的过程。常见的非过程查询语言,如 SQL (Structured Query Language),关系演算(relational calculus)。

过程查询语言(Procedural Language)是指用户指定了执行一系列的操作计算得到希望的结果。常见的过程查询语言,如关系代数(relational algebra)。

本篇接下来会详细的介绍以上数据库查询语言的用法。

关系型操作

过程的关系型查询语言提供了一组操作应用在一个关系或者多个关系中。关系查询的结果本身是一个关系,因此关系运算可以应用于查询结果以及给定的一组关系。具体的关系型操作的表达取决于具体的查询语言。

常见的操作有,选择(select),投影(project),连接(join),笛卡尔积(Cartesian Product),组合(Union),相交(Intersect)等等。这些操作会在下面的关系代数查询语言中详细介绍。

介绍 SQL

有大量的数据库查询语言可以使用。SQL 是目前最广泛使用的一种查询语言。SQL 不仅能查询数据库,它也可以定义和修改数据库。

SQL 语言概述

IBM 开发了初始版本的 SQL,原名为 Sequel。Sequel 语言经过发展,它的名称变成了 SQL (Structured Query Language)。1986年 国际标准组织(ISO)发布了一个 SQL 标准,称为 SQL-86。后续陆续发布了很多个 SQL 标准版本,最近的一个版本为 SQL:2008。

SQL 语言由多个部分组成:

  • 数据定义语言(Data-Definition Language, DDL),它可以定义、删除和修改关系模式(relation schema)。
  • 数据操纵语言(Data-Manipulation Language, DML)它可以查询,插入,删除和修改数据库的元组。
  • 完整性(Integrity)。DDL 包含语句可以指定数据的完整性约束。
  • 视图定义。DDL 包含语句可以定义视图。
  • 事务控制。SQL 包含命令可以指定事务的开始和结束。
  • 嵌入的 SQL 或动态的 SQL。它可以嵌入在编程语言中如 C, C++, Java 进行操作数据库。
  • 授权(Authorization)。DDL 包含语句可以指定关系和视图的访问权限。

每个数据库系统实现的 SQL 与标准的 SQL 有一些小差异,但大部分是标准的,SQL 的功能组成也大致一样。特定的数据库系统中的不标准的 SQL 可以查阅数据库系统的用户手册。

SQL 数据定义

数据库中的一组关系必须通过数据定义语言(DDL)来指定给数据库系统。SQL DDL 可以定义:

  • 每个关系的模式(Schema)
  • 每个属性的类型。
  • 完整性约束。
  • 每个关系的一组索引。
  • 每个关系的安全和授权信息。
  • 每个关系的物理存储结构。

基本类型

SQL 标准支持大量的内置类型,包括:

  • char(n):固定长度 n 的字符串。
  • varchar(n):可变长度的字符串,最大长度为n。
  • int:一个整数。与硬件相关的整数的有限子集。
  • smallint:一个小的整数。
  • numeric(p, d):指定精度和固定的小数部分的数。该数据有p位数字(加一个符号)组成,p 位数字中的 d 位在小数据的右边。如,numeric(3, 1) 可以表示 44.5,不可以表示 444.5,4.45 等。
  • real, double precision:浮点数和双精度浮点数。具体精度与硬件相关。
  • float(n):一个浮点数,精度至少为 n 位。

每一种类型可以包含一个特殊的值称为 null。null 表示未知或者不存在。

char 数据类型存储固定长度的字符串,当字符串长度不够时,自动在字符串后面加空格。

char 和 varchar 等类型的字符串之间比较时,先转换为相同的长度,再进行比较。

SQL 也支持 nvarchar 类型,它是字符串类型,它存储的是 Unicode 编码或者多语言数据。而 varchar存储的是 ASCII 数据。nvarchar 一个字符使用 2 个字节,varchar 一个字符使用 1 个字节。然而,很多数据库允许 Unicode (UTF-8)的数据存储在 varchar 类型中。

基本的模式定义

SQL 使用 create table 语句创建一个数据库的关系。如

create table department
(dept_name varchar(20),
building varchar(15) not null,
budget numeric(12,2),
primary key (dept_name));

SQL 可以指定完整性约束:

  • primary key(Aj1, Aj2…)
  • foreign key(Ak1, Ak2…)
  • not null

插入元组语句:

insert into instructor
values (10211, ’Smith’, ’Biology’, 66000);

删除元组语句:

delete from student;

删除表语句:

drop table r;

修改表语句。如,添加属性和删除属性。

alter table r add A D;
alter table r drop A;

基本的 SQL 查询操作

基本的 SQL 查询语句有三个子句(Clause):select,from 和 where。

一个关系中的查询

使用 select from where 三个基本的子句进行单表查询

select name
from instructor
where salary > 70000;

使用关键字 distinct 去除重复的记录,关键字 all 允许重复的结果。

select distinct dept_name
from instructor;

select 子句支持算术表达式

select ID, name, dept_name, salary * 1.1
from instructor;

where 子句支持逻辑连词and, or, not

select name
from instructor
where dept_name = ’Comp. Sci.’ and salary > 70000;

多个关系的查询

典型的多个关系的 SQL 查询语句

select A1, A2,..., An
from r1, r2,...,rm
where P;

多个关系的查询使用 from 子句指定查询哪些关系。

from 子句多表查询的过程:

  1. 生成一个 from 子句中的所有关系的笛卡尔乘积(Cartesian Product)。

  2. 基于步骤1产生的结果,使用 where 子句的断言进行筛选元组。

  3. 基于步骤2产生的结果,输出 select 子句指定的属性。

多个关系的连接查询

natural join:创建一个连接基于两个关系中相同名称的属性具有相同的属性值。连接后的关系中相同的属性只显示一次。natural join 一般默认是 inner join。

joinfrom 多表查询可以得到相同的结果,但 join 写法更简洁。natural join 语句如下

select A1, A2,..., An
from r1 natural join r2 natural join ... natural join rm
where P;

join 的实现是通过将两个关系按特定属性排序后进行连接的,不会生成笛卡尔积,但是需要对关系表进行排序。Cartesian product 一个是空间消耗,join 一个是时间消耗。

其它基本的 SQL 查询操作

重命名

as 关键字可以重命名结果关系中的属性名称,以及重命名 SQL 语句中的关系名称,如 old-name as new-name。新的名称可以称为别名 (alias)。

select T.name as instructor_name, S.course_id
from instructor as T, teaches as S
where T.ID= S.ID;

字符串操作

比较操作:SQL 标准中字符串比较是区分大小写的,但是常见的数据库系统,如 MySQL 和 SQL Server 中不区分大小写。

字符串函数:SQL 支持大量的字符串函数,如:转换大小写 upper(s), lower(s),去除开始和结尾的空格 trim(s)。每个数据库系统中的字符串函数不一样,具体参照特定的数据库系统手册。

模式匹配:可以使用 like 操作符进行字符串的模式匹配。字符串中的 % 匹配一个或多个任意字符,_ 匹配任何一个字符。

select dept_name
from department
where building like ’%Watson%’;

转义符:SQL 使用反斜杠 \ 进行转义字符(Escape Character)。

正则表达式:SQL 标准支持比 like 操作更强大的模式匹配,在 SQL 中可以使用正则表达式。

select 子句中的属性规范

符号 *select 子句中表示一个关系的所有属性。

select instructor.*
from instructor, teaches
where instructor.ID= teaches.ID;

排序

SQL 支持对一个关系中的元组进行排序,使用 order by 子句可以对查询结果的元组进行排序。

select name
from instructor
where dept name = ’Physics’
order by name;

where 子句的断言

SQL 中的 where 子句支持 between 操作符进行比较断言。between 可以代替 算术操作符 >=, <=

select name
from instructor
where salary between 90000 and 100000;

比较运算符可以支持元组,即多个属性按顺序比较。如, (a1, a2) < (b1, b2), (a1, a2) = (b1, b2)

集合操作

常见的集合操作(Set Operations)如:组合 union,相交intersect 和排除 except。集合操作默认会去除重复的记录,添加 all 关键字,可以允许重复的记录。如 union allintersect allexcept all 等。一个集合操作的 SQL 语句例子如下:

(select course_id
from section
where semester = ’Fall’ and year= 2009)
union
(select course_id
from section
where semester = ’Spring’ and year= 2010);

Null 值

Null 值在关系型操作中是一个特殊问题,Null 值表示的是未知或不存在。Null 值在算术操作,比较操作,和逻辑操作视为特殊情况。可以把 Null 值理解为一个未知的值。

算术操作

算术操作中包含 null 的表达式,返回结果为 null。如 null + 5 // result is null. 表示的是: 未知 + 5,结果是未知。

比较操作

比较操作包含 null 时,结果既不是 true 也不是 false,而是未知。返回结果为 null。如 1 < null //result is null。表示的是:1 是否小于未知,结果是未知。

逻辑操作

where 子句中的逻辑连接是出现 null。

  • and. true and null is null, false and null is false, null and null is null
  • or. true or null is true, false or null is null, null or null is null
  • not. not null is null

可以使用 select 子句去测试结果,如

select true and null; // return null
select true or null; //return true

测试属性值是否为空

使用 is nullis not null 来测试值是否为空。不能用等号来测试属性属性是否为空,因为 null = null // result is null。一个 SQL 例子如下:

select name
from instructor
where salary is null;

聚合函数

聚合函数(Aggregate Function)是一个函数把一组值作为输入,返回一个值。SQL 提供5个内置聚合函数:

  • 平均值:avg
  • 最小值:min
  • 最大值:max
  • 总和:sum
  • 数量:count

sum 和 avg 函数的输入必须是数字。其它的函数输入的可以是数字和非数字,如字符串。

基本的聚合

select avg (salary)
from instructor;
select count (distinct ID)
from teaches

count(*) 表示查询一个关系的元组的数量。

select count (*)
from course;

分组聚合

聚合函数不仅可以使用在单个元组集合中,也可以使用在多个元组集合中。SQL 使用 group by 子句进行分组。它根据 group by 子句指定的所有属性的相同属性值进行分组。一个分组聚合的 SQL 例子:

select dept_name, avg (salary) as avg_salary
from instructor
group by dept_name;

注意事项:使用分组查询时,select 子句中出现的属性必须是 group by 中出现的属性。其它属性不能直接出现在 select 子句中,可以出现在聚合函数里。如,上面的 SQL,group by dept_name,所以 select 子句只能出现 dept_name 属性和聚合函数 select dept_name, avg (salary)

原因:对某些属性进行分组,目的只能是计算这些分组的聚合函数结果。在分组查询的结果中,分组的属性和未分组的属性是一对多的关系,无法在一行中表示,所以 select 子句中不允许出现不是 group by 的属性。

Having 子句

having 子句使用聚合函数对分组进行过滤。SQL 语句例子如下:

select dept_name, avg (salary) as avg_salary
from instructor
group by dept_name
having avg(salary) > 42000;

一个分组查询包含 having 子句的查询处理过程:

  • 根据 from 子句得到关系的所有元组。
  • 如果 where 子句存在,则根据 where 子句的断言,过滤 from 子句中的元组。
  • 根据 group by 子句对元组进行分组。
  • 根据 having 子句过滤分组。
  • 根据 select 子句中的聚合函数,得出每个分组的查询结果。

聚合函数中的 Null 和 Boolean 值

Null 值

除了 count() 函数之外的所有聚合函数,都忽略 null 值的输入。

select sum(salary)
from instructor;

上面 SQL 语句中的 sum(salary) ,如果分组中某个元组的 salary 属性值为 null,这个元组将被忽略。即 null 值不参与聚合函数的计算。

嵌套子查询

嵌套的子查询可以嵌套在 select 子句,from 子句,where 子句和 having 子句。

集合成员(Set Membership)

in 关键字可以测试一个值是否是一个集合的成员。这个集合是由 select 子句产生的。测试不是一个集合成员使用 not in关键字。

select distinct course_id
from section
where course_id in (select course id
from section
where semester = ’Spring’ and year= 2010);

集合对比(Set Comparison)

测试一个值至少大于一个集合中的一个值使用 > some 关键字,测试大于一个集合中的所有值使用 > all 关键字。这个集合同样是 select 子句产生的。

select name
from instructor
where salary > some (select salary
from instructor
where dept_name = ’Biology’);

测试空关系

使用 exists 关键字测试一个子查询的结果是否由存在元组。如果子查询结果为空,则返回 false,不为空则返回 true。

select course_id
from section as S
where semester = ’Fall’ and year= 2009 and
exists (select *
from section as T
where semester = ’Spring’ and year= 2010 and
S.course_id= T.course_id);

测试是否存在重复元组

使用 unique 关键字可以测试子查询是否存在重复的元组。存在重复返回 true,不存在则返回 false。not unique 测试是否不存在重复。

select T.course_id
from course as T
where not unique (select R.course_id
from section as R
where T.course_id= R.course_id and
R.year = 2009);

子查询在 from 子句

select dept_name, avg_salary
from (select dept_name, avg (salary) as avg_salary
from instructor
group by dept_name)
where avg_salary > 42000;

with 子句

with 子句可以定义一个临时的关系。

with max_budget (value) as
(select max(budget)
from department)
select budget
from department, max_budget
where department.budget = max_budget.value;

标量子查询

SQL 允许子查询嵌入 select 子句中,子查询必须返回一个元组中的一个属性,这个子查询称为标量子查询( Scalar Subqueries)

select dept_name,
(select count(*)
from instructor
where department.dept_name = instructor.dept_name)
as num_instructors
from department;

修改数据库操作

删除操作

delete from r
where P;

插入操作

insert into course
values (’CS-437’, ’Database Systems’, ’Comp. Sci.’, 4);

插入元组基于一个查询的结果

insert into instructor (name, dept_name)
select name, dept_name
from student
where dept_name = ’Music’;

更新操作

update instructor
set salary= salary * 1.05;

中级的 SQL

这部分主要介绍更复杂的 SQL 包括:SQL 查询,视图定义,事务,完整性约束,数据定义和授权。

join 表达式

上面介绍的 natural join 是根据相同的属性名称自动 inner join。SQL 支持明确指定 join 的断言,即指定 join 的属性。除了 inner join 还有 outer join。如果没有指定是 inner 还是 outer,一般 join 是指 inner join。

join 条件

使用 on 关键字可以指定 join 要关联的属性。

select *
from student join takes on student.ID= takes.ID;

outer join

inner join 取的是两个关系表属性的交集,outer join 可以取某一个关系表的全部元组,或者两个关系表的全部元组,没有关联的元组其它属性用 null 表示。

outer join 分为:

  • left outer join:保留 join 语句前面的那个关系表的所有元组。
  • right outer join:保留 join 语句后面的关系表的所有元组。
  • full outer join:保留两个关系的所有元组。

outer join SQL 语句:

select *
from student natural left outer join takes;

join 用在子查询中:

select *
from (select *
from student
where dept_name= ’Comp. Sci’)
natural full outer join
(select *
from takes
where semester = ’Spring’ and year = 2009);

使用 where 子句代替 on 的断言

select *
from student left outer join takes on true
where student.ID= takes.ID;

视图

有时我们不希望用户看到全部的逻辑模型。出于安全考虑我们需要对用户隐藏一些数据。我们可以创建一个个性化的关系集合给用户查询,而不用整个逻辑模型。SQL 允许一个通过一个查询定义一个虚拟关系表。这个关系包含查询的结果。虚拟关系不是预先计算和存储的,而是每当使用虚拟关系时通过执行查询计算的。视图(View)不是逻辑模型的一部分,而是一个虚拟关系使用户可见的。

视图的定义

使用 create view 命令定义视图。表达式语法格式如下:

create view v as <query expression>

视图定义的例子:

create view faculty as
select ID, name, dept_name
from instructor;

使用视图

一旦我们定义了一个视图,我们可以使用视图名称作为一个虚拟关系。查询视图和查询正常的关系表是一样的。

select dept_name
from faculty
where name= ’Watson’;

物化视图

一些数据库系统允许视图关系被存储,当实际的关系改变时,视图是更新的,这样的视图称为物化的视图(Materialized View)。保持物化的视图更新的过程称为物化视图维护(Materialized View Maintenance),简称视图维护

视图维护可以是及时的,关系改变时视图立刻更新;也可以是懒惰的,当视图访问的时候更新视图;另外也可以周期的更新视图。

如果用户频繁的使用视图,物化视图是有益的。视图可以快速的响应查询,避免读取大量的关系。物化视图可以带来好处,但也需要考虑它的不利因素,如存储空间的花费和视图更新操作需要的性能消耗。

更新视图

直接修改视图关系一般是不允许的。有些数据库允许更新视图。

事务

事务是由一系列 SQL 语句组成的一个逻辑单元。事务具有原子性。一个事务通过一个语句表示开始,通过一个语句表示结束。结束一个事务有两种操作,一个是提交,一个是回滚,其中某个操作必须发生且仅有一个操作发生。事务执行中没有任何错误发生,最终会执行提交。事务执行中出现错误,会执行回滚操作,数据库会回滚到事务开始时最初的状态。

当事务由多个 SQL 语句组成,单个 SQL 语句的自动提交需要关闭,使用应用程序接口,如 JDBC 或 ODBC 可以定义事务和关闭事务自动提交。

完整性约束

完整性约束(Integrity Constraint)确保数据库的改变不会丢失数据的一致性。常见的完整性约束,如名称不能为空。不能存在相同的 ID。预算金额必须大于0等等。

完整性约束通常是数据库模式(Schema)设计过程的一部分。完整性约束的声明是 create table 创建关系的一部分。完整性约束可以使用 alter table <table-name> add constraint 语句添加到已存在的关系中,当 alter 命令执行时,系统第一次会验证当前关系是否满足这个约束,不满足则拒绝这个添加约束命令。

单表的完整性约束

单表的完整性约束有:

  • not null
  • unique
  • check
name varchar(20) not null
phone varchar(15) not null unique
age int check (age > 18)

参考完整性约束

参考完整性(Referential Integrity)它确保出现在一个关系中指定的属性值,也出现在另一个关系中。

foreign key 子句可以定义在 create table 语句中,定义个参考完整性约束。如:

foreign key (dept_name) references department

在 SQL 中,外键一般时参考另一个关系的主键。然而,只要参考候选键(Candidate Key)就行,如主键属性,或者唯一约束属性。

当一个参考完整性约束违反时,这个语句时被拒绝执行的。然而,foreign key 子句可以定义在参考的关系违反约束时指定一个删除或者更新操作,系统可以改变参考关系恢复约束,而不是拒绝执行。on delete cascade 子句在外键中声明,可以删除对应的参考关系元组,来满足约束。

foreign key (dept_name) references department 
on delete cascade
on update cascade

SQL 也允许 foreign key 子句指定其它非 cascade 的动作。如使用 set null, set default 代替 cascade。

如果存在跨多个关系的一串外键依赖关系,则该链一端的删除或更新会在整个链中传播。

外键属性允许 Null 值,null 值自动满足参考完整性约束。

复杂的检查条件和断言

SQL 支持子查询在 check 子句中,如

check (time slot id in (select time slot id from time slot))

复杂的检查条件是有用的,但是也是有性能消耗的。每次插入或更新都要进行检查。

断言(Assertion) 是一个断言条件,数据库总是满足的。域约束和参考完整性约束是一个特殊形式的断言。断言的 SQL 形式如下:

create assertion <assertion-name> check <predicate>;

复杂的断言是耗费巨大的,使用时要谨慎。

当前,没有一种广泛使用的数据库系统支持 check 子句中的子查询或断言。然而,相同的功能可以通过触发器实现。

SQL 数据类型和 Schema

除了整数,浮点数和字符类型,SQL 还支持其它的内置数据类型。

SQL 中的日期和时间类型

  • date:包含年月日等信息。
  • time:包含时分秒。
  • timestamp:date 和 time 的结合。

日期和时间的值具体格式如下:

date ’2001-04-25’
time ’09:30:00’
timestamp ’2001-04-25 10:29:01.45’

SQL 支持提取时间或日期中的单独的字段。如,提取日期中的年或月。

SQL 定义了一些函数取获取当前日期和时间。如,current_date(),current_time()。具体参考相关的数据库系统的实现。

SQL 允许时间日期数据使用算术和比较操作。如 select x - y , wher x < y

默认值

SQL 允许指定一个属性的默认值,通过 default 关键字进行指定。当插入语句没有给一个属性赋值时,则使用指定的默认值。

age int default 18

创建索引

索引(Index)是一个数据结构允许高效地查找属性值,而不用扫描一个关系中的所有元组。一个广泛使用的索引类型为 B+树索引。SQL 语言没有正式的定义索引的语法,大多数数据库系统支持索引创建语言如下:

create index studentID_index on student(ID);

大对象类型

很多当前的数据库应用需要存储特别大的属性,如一张图片,视频剪辑等等。SQL 提供大对象数据类型: clob (character data)和 blob (binary data),其中字母 lob 表示 Large Object。

book review clob(10KB)
image blob(10MB)
movie blob(2GB)

取出一个完整的大对象到内存中是不高效的或不实际的。SQL query 将取出大对象的 locator(定位器),通过 locator 处理对象。

用户自定义的类型

SQL 支持创建用户自定义类型。有两种格式的类型:distinct types 和 structured data types。使用 create type 子句定义一个类型:

create type Dollars as numeric(12,2) final;

创建表扩展

SQL 支持创建一个与某个表有相同的 schema 的表,使用 create table ... like 语句。

create table temp_instructor like instructor;

创建一个表包含一个查询的结果,使用 create table ... as 语句。如:

create table t1 as
(select *
from instructor
where dept name= ’Music’)
with data;

with data 表示包含数据,然而很多数据库实现默认包含数据,with data 子句可以忽略。

Schema, Catalogs, and Environments

数据库系统提供了一个三层等级的命名关系。顶层是 catalog,它包含下一层的 schema,每个 schema 包含多个 SQL 对象如关系表,视图等。

每个用户有一个默认的 catalog 和 schema。默认的 catalog 和 schema 为每个连接设置 SQL 环境的一部分,该环境还包括用户标识符(也称授权标识符)。所有常见的 SQL 语句都是在一个 schema 上下文中运行的。

授权

SQL 标准包含的权限(Privilege)有:select,insert,update和delete。所有权限使用 all privileges 表示。当一个用户执行一个 查询或更新时,系统授权检查该用户是否有这个权限。如果没有则拒绝执行。最终的权限是通过数据库管理员给定的。

权限的授与和撤销

grant 语句可以用来授予权限,权限可以授予给用户和角色。grant 语句格式如下:

grant <privilege list>
on <relation name or view name>
to <user/role list>;

授权的例子:

grant select on department to Amit, Satoshi;
grant update (budget) on department to Amit, Satoshi;

revoke 语句可以用来撤销权限,revoke 语句的语法格式和 grant 类似,撤销权限的例子:

revoke select on department from Amit, Satoshi;
revoke update (budget) on department from Amit, Satoshi;

角色

在现实生活中,一些用户有相同的权限。管理源需要重复的为某些用户授予权限。为了解决这个问题可以使用角色来给用户授权。先把权限授予给角色,然后把角色授予给用户。这样可以方便的管理用户权限。

创建一个角色语句的例子:

create role instructor;

给角色授予权限的例子:

grant select on takes
to instructor;

把角色授权给用户的例子:

grant instructor to dean;

权限的传递

一个用户的权限授予给另一个用户。使用 with grant option 子句,如

grant select on department to Amit with grant option;

高级的 SQL

编程语言执行 SQL

SQL 是一个强大的声明式查询语言。SQL 不是图灵完备的,所以数据库应用不能使用 SQL 开发。应用程序一般用 C, C++ 或 Java 实现,通过嵌入 SQL 来访问数据库。

应用程序访问数据库的两种方式:

  • 动态的 SQL(Dynamic SQL)。程序在运行时构造一个 SQL 语句。通过应用程序接口提交 SQL 语句给数据库服务器,得到返回结果。常见的应用程序接口,如 JDBC 是 Java 语言连接数据库的应用程序接口。ODBC 是 C 语言的应用程序接口。
  • 嵌入的 SQL(Embedded SQL)。在程序编译之前,使用预处理器将 SQL 语句给数据库系统预编译和优化,将程序中的 SQL 语句代替合适的代码。

JDBC

JDBC (Java Database Connectivity)标准定义了一个应用程序接口(API),让 Java 程序能够连接数据库服务器。不同的数据库系统有不同的 JDBC 实现。

JDBC 主要的操作:

  • 与数据库建立连接。
  • 传送 SQL 语句给数据库系统。
  • 取出 SQL 执行的结果。
  • 调用方法和存储过程。
  • 获取数据库元数据。
  • 执行事务。自动提交。

方法和存储过程

存储过程(Procedure)和方法(Function)允许一系列 SQL 操作存储在数据库,通过 SQL 语句调用执行。

它的优点:

  • 允许多个应用程序访问。

  • 一处修改多处使用,存储过程的逻辑改变,不需要修改其它应用程序。

  • 应用程序调用存储过程,而不是直接的修改数据库关系。

SQL 标准定义了存储过程的语法标准,但是大部分数据库的实现是不标准的版本。

声明和调用方法和存储过程

SQL 标准的方法和存储过程例子:

create function instructors_of (dept_name varchar(20))
returns table (
ID varchar (5),
name varchar (20),
dept_name varchar (20),
salary numeric (8,2))
return table
(select ID, name, dept_name, salary
from instructor
where instructor.dept_name = instructor of.dept_name);
create procedure dept_count_proc(in dept name varchar(20), out d_count integer)
begin
select count(*) into d_count
from instructor
where instructor.dept_name= dept_count_proc.dept_name
end

调用存储过程的 SQL 语句。

declare d_count integer;
call dept_count_proc(’Physics’, d_count);

存储过程和方法的不同:

  • 存储过程是预编译的对象,而方法每次调用都要编译。
  • 存储过程有输入参数和输出参数,而方法只有输入参数。
  • 存储过程不包含 return 语句,可以返回 0 个或多个值。而方法有 return 语句,必须且仅有一个返回值,返回值可以是基本类或者组合类型。
  • 方法可以调用存储过程,反之不能。

触发器

触发器(Trigger)是一个语句在触发事件时自动执行的。

触发器的用途:

  • 实现一些完整性约束,当 SQL 的约束机制不能实现的。
  • 启动一些自动任务。

定义触发器的 SQL 例子:

create trigger timeslot_check1 after insert on section
referencing new row as nrow
for each row
when (nrow.time_slot_id not in (
select time_slot_id
from time_slot))
begin
rollback
end;

大部分数据库触发器实现的语法是不标准的。具体的情况,需要参考具体数据库的用法。

触发器可以设置启用或关闭。

触发器应该小心使用,因为运行时检测到触发器错误会导致出发该触发器的操作语句执行失败。触发器是有用的,但是当存在替代方案时最好避免使用触发器。

形式关系查询语言

关系型查询语言(如 SQL 语言)是基于形式模型的。常见的形式关系查询语言(Formal Relational Query Language)如下:

  • 关系代数(Relational Algebra)
  • 元组关系演算(Tuple Relational Calculus)
  • 域关系演算(Domain Relational Calculus)

关系代数

关系代数(Relational Algebra)是一个过程的查询语言。它由一组操作组成,把一个和多个关系作为输入,产生一个新的关系作为结果。

基本的操作有:select,project,union,set difference,Cartesian product,rename。

其它操作:set intersection,natural join,assignment。

关系代数表达式的例子:

select

project

set union

set intersection

set difference

Cartesian product

rename

natural join

assignment

left/right/full outer join

arithmetic operations

aggregate functions

元组关系演算

元组关系演算(Tuple Relational Calculus)是一个非过程查询语言。一个元组关系演算的查询表示形式如下:

{t | P(t)}

t:表示所有的元组

P:表示断言。

t[A]:表示元组t中的属性A。

t ∈ A:表示元组 t 在关系 r 中。

元组关系演算查询表达式的使用例子:

{t | t ∈ instructor ∧ t[salary] > 80000}
{t | ∃ s ∈ instructor (t[ID] = s[ID] ∧ s[salary] > 80000)}
{t | ∃ r ∈ student (r[ID] = t[ID]) ∧
( ∀ u ∈ cour se (u[dept name] = “ Biology” ⇒
∃ s ∈ takes (t[ID] = s[ID]
∧ s[course id] = u[course id]))}

域关系演算

域关系演算(Domain Relational Calculus)是另一种关系演算的查询语言。它的表达形式如下:

{< x1, x2,..., xn > | P(x1, x2,..., xn)}

x1, x2,…, xn 表示域变量,P 表示操作公式。

使用例子:

{< i, n, d,s > | < i, n, d,s > ∈ instructor ∧ s > 80000}
{< n > | ∃ i, d,s (< i, n, d,s > ∈ instructor ∧ s > 80000)}

无论是关系型代数,元组关系演算,还是域关系演算,每一个表达式都是由若干个基本操作组成的,所以,这三种形式关系查询表达式,可以互相转换。

在数据库系统中,一个查询的 SQL 语句,必须先转换为关系代数查询表达式,然后执行相关查询操作,因为关系代数查询表达式是过程的,它明确了执行哪些基本的操作,而 SQL 没有指定执行哪些基本操作。

小结

SQL 的细节实在是太多了,我们来小结一下。

SQL 内置的数据类型

基本的类型 时间类型 大对象类型
char(n)
varchar(n)
int
smallint
numeric(p, d)
real, double precision
float(n)
date
time
timestamp
clob
blob

SQL DDL

  • 定义表:定义表的属性和数据类型,定义完整性约束,定义参考完整性约束。
  • 定义索引。
  • 修改表:添加/删除表字段、添加/删除表的约束。
  • 删除表。
  • 授权。
  • 定义/删除:视图、存储过程、方法和触发器。

常见的完整性约束

  • not null
  • unique
  • check
  • referential Integrity

SQL DML

  • 插入,修改,删除和查询元组。

SQL DML 的查询操作

  • 基本的单表查询。select … from … where,去重(distinct, all),算术表达式,逻辑连词(and, or, not)。
  • 基本的多表查询。from Cartesian product,natural join,outer join
  • 其它基本的 SQL 查询操作。as(rename),like(string),order by,limit
  • 集合操作。union,intersection,except
  • 聚合函数和 having 子句。
  • 嵌套子查询。

SQL DML 的查询总结

子句 关键字 聚合函数 字符串函数
select
from
where
group by
having
order by
as
distinct, all
natural join, inner join, left join, right join, full join
union, intersect, except
between, in
like
and, or, not
limit
avg()
min()
max()
sum()
count()
length()
concat(str1, str2, …)
concat_ws(separator, str1, str2, …)
trim()
locate()
substring()
upper()
lower()
repeat()
reverse()
replace()
ascii()
bin()
hex()

References

[1] Database System Concept (6th) by Avi Silberschatz, Henry F. Korth, and S. Sudarshan

本文将带你进入数据库系统的世界,带你了解数据库系统的核心概念,一览数据库系统的全貌。通过本文你将了解数据库系统是什么,它有哪些功能,它的组成结构,它是如何实现的,以及其它和数据库相关的知识。

介绍数据库系统

数据库系统是什么

数据库管理系统(Database-Management System,DBMS)是一组相互关联的数据的集合和一组用于访问这些数据的程序。数据库管理系统也可以简称为数据库系统。这些被管理的数据的集合称为数据库。数据库系统的主要目的是为了提供一种既方便又高效地存储和检索数据库信息的方法。

数据库系统是为了管理大量信息而设计的。管理数据的工作主要涉及:定义信息的结构;提供一个操作信息的机制;以及保证信息的安全,如防止未授权访问。

数据库系统的应用

数据库的使用是十分广泛的。常见的应用场景如:企业日常信息,银行和金融,大学,航空公司,和通信等。这些企业和机构需要利用数据库来存储和检索大量的数据信息。数据库已成为当今每个企业必不可少的组成部分。

20世纪90年代,网络的快速发展,大量的在线服务和应用被人们广泛的使用,这些应用的数据几乎都是存在数据库系统中。虽然应用接口隐藏了访问数据库的细节,大部分人在使用网络应用时没有意识到自己在操作数据库,实际上,访问数据库已成为每个人生活中不可缺少的一部分。

为什么需要数据库系统

在数据库系统出现之前,人们使用文件处理系统(File-Processing System)来存储和检索信息。文件处理系统简单地用一个文件和一个程序来管理一个实体的数据,它不适应数据结构的灵活改变和处理数据之间的关联关系。文件处理系统主要的缺点如下:

  • 数据冗余(Data Redundancy)和不一致性(Inconsistency)。一个数据项出现在多个文件中,因为不同的文件由不同的程序处理的,所以出现数据冗余和不同文件的数据不一致。
  • 访问数据很困难。程序不能实现复杂的信息检索功能。
  • 数据隔离(Data Isolation)问题。数据分散在不同的文件,可能不同的格式。
  • 完整性(Integrity)问题。很难实现数据的完整性约束条件。如账户余额不能少于零。
  • 原子性(Atomicity)问题。很难实现原子操作,很难恢复和保证数据一致性。
  • 并发访问异常。没有并发控制,导致并发访问数据异常。
  • 安全问题。不能实现安全访问约束。

文件处理系统存在以上大量的问题,它不能满足企业对大量数据高效快速处理的需求,人们迫切需要一个更高效、便捷、安全的数据管理系统。所以需要开发一个数据库管理系统来解决这些问题。

数据的表现形式

数据库系统给用户提供了数据的抽象视图,它隐藏了数据是如何存储和如何维护的细节。

数据抽象

数据库系统需要高效的检索数据,这往往需要设计一个复杂的数据结构去表示数据。为了简化用户与数据库系统的交互,通常将数据抽象成不同的层级,如下所示:

  • 物理层(Physical Level)。物理层是最低的抽象层描述了数据的实际存储方式,以及详细地描述了复杂的底层数据结构。
  • 逻辑层(Logical Level)。逻辑层描述了哪些数据存储在数据库中,数据之间存在什么关系。
  • 视图层(View Level)。简化用户与系统的数据交互,仅提供一部分重要的信息给用户,隐藏一些次要的信息。

物理层,它通过编译器向数据库程序员隐藏了底层存储的细节。逻辑层,它通过编程语言向程序员隐藏了数据存储的细节。视图层,它通过应用程序向用户隐藏了数据类型的细节。

不同的用户在不同的抽象层与数据库进行交互,使得每一个用户都可以方便和高效的管理数据。

实例和模式

实例(Instances)是某一个时刻存储在数据库中的信息的集合。

模式(Schema)是全部的数据库的设计。

数据模型

数据模型(Data Models)是数据库结构和基础。它是一个用于描述数据、数据关系、数据语义和一致性约束的概念性工具的集合。数据模型提供了一种方式去描述数据库在物理层、逻辑层、视图层的设计。

常见的数据模型:

  • 关系型模型(Relational Model)。使用一组表(Table)来表示数据和数据之间的关系。每个表有多个列(Columns)每个列有唯一的名称。表也称为关系(Relation)。
  • 实体关系模型(Entity-Relationship Model)
  • 基于对象的数据模型(Object-Based Data Model)
  • 半结构化的数据模型(Semistructured Data Model)

以上数据模型中,关系型模型应用最为广泛,大部分数据库系统是采用关系型模型实现的。

数据库语言

数据库系统提供了数据定义语言去指定数据库的模式(Schema),以及数据操纵语言去表示数据库查询和更新操作。数据定义语言和数据操纵语言不是两种单独的语言,它们都是数据库语言的组成部分。被广泛使用的数据库语言,如 SQL(Structured Query Language)语言。

数据操纵语言

数据操纵语言(Data-Manipulation Language, DML)能够访问和操作数据。基本的操作类型为:查询,插入,删除和修改。

数据操纵语言可以分为两种类型:程序式的(Procedural),声明式的(Declarative)。声明式的更简单,不需要指定如何获取数据。

数据定义语言

数据定义语言(Data-Definition Language, DDL)指定了数据库的模式,也指定了数据的属性。

数据存储在数据库中必须要满足某些一致性约束(Consistency Constraints)。如,账户余额不能少于0。数据定义语言(DDL)可以指定数据的完整性约束(Integrity Constraints),完整性约束是一致性约束的一部分,它防止不符合规范的数据进入数据库。常见的完整性约束如下:

  • 域约束(Domain Constraints)。约束数据的取值范围和精度等。
  • 参考约束(Referential Constraints)。关系 A 的一个属性参考关系 B 的属性,参考的属性必须出现在被参考的关系中。
  • 断言(Assertions)。数据需要满足断言的约束条件。

数据定义语言的输出结果保存在数据库系统的数据字典(Data Dictionary)中,它包含了数据库的元数据(Metadata)即描述数据的数据。

关系型数据库

关系型数据库(Relational Database)是基于关系模型(Relational Model)的和使用一系列的表(Table)来表示数据和数据之间的关系。

一张(Table)包含多个列,每个列有唯一的名称。一个表的结构示例,如下图所示。

数据库的表是由一些类型固定的记录(Record)构成的,一行数据称为一个记录。每个记录有固定数量的字段(Field)或属性(Attribute)。表的每一列与记录的每一个字段是相对应的。

表的操作

我们可以使用 SQL 语言来对数据库进行操作。如创建一张表:

create table department
(dept_name char(20),
building char(15),
budget numeric(12,2));

查询数据

select department.dept_name
from department
where department.budget > 95000;

应用程序访问数据库

SQL 语言不是图灵完备的,一些计算不能通过 SQL 语言完成,但是操作数据库必须使用数据库语言如 SQL 语言。由于应用程序不能通过 SQL 语言来实现,一般使用如 C, C++ 或者 Java 来实现,应用程序访问数据库一般是通过嵌入 SQL 语句来与数据库进行交互的。

应用程序访问数据库的两种方式:

  • 应用程序接口。通过它发送 DML 和 DDL SQL 语句给数据库,和取回执行结果。
  • 嵌入 DML 调用。

数据库设计

数据库设计主要任务是数据库模式(Schema)的设计。

设计过程

  • 需求分析。与用户和领域专家进行交流,设计如何让数据库的结构满足用户的数据需求。收集需求,用文字的形式描述数据结构,最后得出一个用户需求规格说明书。

  • 概念设计(Conceptual Design)。选择一个数据模型,将用户需求转换为数据库概念模式。这个阶段主要是专注于描述数据和数据之间的关系。用专业的概念的数据模型去描述数据结构,主要使用 entity-relationship model 和 normalization 方式去设计。

  • 逻辑设计(Logical Design)。将抽象的数据模型转换为数据库的实现。将概念模型转换为数据库系统使用的模型,如将 entity-relationship model 转换为 relational model。

  • 物理设计(Physical Design)。设计物理存储的细节。包括文件组织的形式和内部的存储结构等。

实体关系模型

Entity-Relationship (E-R) 数据模型使用一组对象称为实体(Entity)和对象之间的关系(Relationship)来表示数据和它们之间的关系。每个实体由一组属性组成。E-R 模型可以表示数据约束。一个实体类型的所有实例称为实体集(Entity Set),两个实体类型的所有的实例之间的关系称为关系集(Relationship Set)。

E-R 模型中一个重要的约束是映射基数(Mapping Cardinality)它表示一个实体关联另一个实体的数量。

标准化

标准化(Normalization)可以让我的数据减少不必要的冗余。它使用范式(Normal Form)来设计数据库的模式(Schema)。常见的范式有:First normal form (1NF), Second normal form (2NF), Third normal form (3NF), Boyce-Codd normal form (BCNF).

数据的存储和查询

数据库系统分为多个模块,它们分别处理不同的任务。数据库系统的功能可以大致分为存储管理器(Storage Manager)和查询处理器(Query Processor)。存储管理器负责管理数据的物理存储空间,查询处理器负责简化和优化数据的访问。

存储管理器

存储管理器是数据库的一个组件,它提供了底层存储和应用程序之间的接口。它与操作系统的文件系统进行交互。它将 SQL 语句转换为底层的文件系统的命令。存储管理器的主要职责是:存储、取出和更新数据库中的数据。

存储管理器的组件包括:

  • 授权和完整性管理器(Authorization and Integrity Manager)

  • 事务管理器(Transaction Manager)

  • 文件管理器(File Manager)

  • 缓存管理器(Buffer Manager)

存储管理器实现的物理结构

  • 数据文件(Data File)。存储数据库的数据的文件。
  • 数据字典(Data Dictionary)。存储元数据即数据库的结构或模式。
  • 索引(Index)。提供快速访问数据库数据的一种数据结构。

查询处理器

查询处理器(Query Processor)的组件包括:

  • DDL 解释器(DDL Interpreter)
  • DML 编译器(DML Compiler)
  • 查询评估引擎(Query Evaluation Engine)

事务管理

真实场景中的数据操作往往需要具备一些基本的特性。如:

  • 原子性(Atomicity)。一组数据操作组成一个逻辑的单元,这些操作必须全部完成或者全部失败。
  • 一致性(Consistency)。数据的操作必须是正确的。如 A 转账给 B,A 和 B 的总金额必须是不变的。
  • 持久性(Durability)。数据可以长久的保存。

事务(Transaction)是在数据库应用程序中执行单个逻辑功能的操作的集合。事务是一个原子性和一致性的单元。事务管理器由并发控制管理器(Concurrency-Control Manager)和恢复管理器(Recovery Manager)组成。其中,并发控制管理器负责的是:当多个事务同时修改数据库时,保证每个事务都是正确的。恢复管理器负责的是:当事务发生错误时恢复原始数据状态。

为了确保数据的一致性,程序员有责任去定义合适的事务。确保数据的原子性和持久性则是数据库系统本身的职责。

数据库的架构

数据库系统由多个组件构成,它的系统结构如下图所示:

数据库的体系结构可以是中心化的 client-server 结构,也可以是平行的、分布式的结构。

数据库应用程序通常划分为两层或者三层结构。如下图所示:

其它

数据挖掘和信息检索

数据挖掘(Data Mining)是指半自动分析大型数据库,从中找到有用的模式的过程。数据挖掘不同于机器学习,机器学习是处理大量的磁盘中的数据文件,而数据挖掘指的是处理数据库中的数据。由于很多查询是十分复杂的,很难使用 SQL 进行查询,所以需要使用数据挖掘技术。数据挖掘可以从数据中分析出有价值的规律,它可以帮助企业执行更好的决策。

数据仓库(Data Warehouse)可以在一个站点中一统一的模式收集不同来源的数据。提供为用户提供了一个统一的数据接口。

信息检索(Information Retrieval)是指查询无结构的文本数据。

其它特性(非关系型)数据库

在某些数据库系统的应用领域,关系型数据模型是受限制的。我们需要其它的数据模型来处理这些应用领域。常见的非关系型模型,如

  • 对象基于的数据模型(Object-Based Data Model)。它可以便捷的实现面向对象编程的数据存储。

  • 半结构化的数据模型(Semistructured Data Model)。它的特点是相同类型的单个数据项可能具有不同的属性集。

  • 键值数据模型(Key-value Data Model)。它是使用 map 或字典实现的。每一个 key 关联一个集合。键值数据模型没有查询语言,它使用简单的 get, put, 和 delete 命令去操作数据。它具有快速存取,可扩展性和可靠性等特点。

数据库的用户和管理员

与数据库系统进行交互的人群可以以分为:数据库用户和数据库管理员。

不同类型的数据库用户的不同交互方式为:

  • 普通的用户。通过应用程序与数据库系统进行功能。
  • 应用开发人员。通过编程语言与数据库系统交互,开发应用程序,提供用户接口。
  • 专业人员。直接使用数据库查询语言操作数据库系统。

数据库管理员的作用:

  • 定义模式。通过执行 DDL 来创建数据库模式(Schema)。
  • 定义存储结构和访问方式。
  • 修改模式和物理组织。
  • 授权数据的访问。
  • 日常维护。周期性的备份数据库。确保有充足的磁盘空间。监控数据库系统的运行状态等等。

小结

  • 首先认识了数据库系统,知道了它是什么和为什么,以及了解数据的表现形式、数据库语言等基本的数据库系统相关的概念。

  • 然后介绍了具体类型的数据库,了解了关系型数据库。以及如何设计数据库。

  • 接着介绍了数据库系统的内部结构,了解了存储,查询和事务等核心的数据库系统的组件。

  • 最后讲了一些和数据库系统相关的其它概念。

References

[1] Database System Concept (6th) by Avi Silberschatz, Henry F. Korth, and S. Sudarshan

操作系统安全

操作系统安全问题

  • 系统帐号密码攻击。用户帐号密码被破解导致:1. 破坏系统文件。2. 安装恶意软件(间谍软件、病毒软件)。3. 比特币勒索。
  • 借助第三方软件服务攻击系统。1. 利用 Redis 将 SSH 密钥写入到 authorized_keys config set dir /root/.ssh, config set dbfilename authorized_keys。2. 利用 Redis 设置挖矿定时任务 config set dir /etc, config set dbfilename crontab

操作系统防护措施

  • 系统和软件包更新,防止低版本漏洞攻击。保持操作系统打上最新的补丁,保证使用最新版本的软件。
    • 手动定期更新,或者设置自动更新(Unattended Upgrades)。
  • 设置系统防火墙
    • 仅开放必要端口,如:22 for SSH,80 for HTTP,443 for HTTPS,VPS 端口等。其它端口设置 IP 白名单。
  • 系统帐号保护
    • 设置高强度 root 密码。
    • 定期更新 root 密码。
    • 不使用 root 帐号登录。
  • 远程登录保护
    • 修改 SSH 默认端口。
    • SSH 登录使用公钥登录。定期更换公钥。
    • 关闭 root 登录。
    • 关闭密码登录功能。
    • 系统防火墙设置 SSH 登录 IP 白名单。
  • 数据保护
    • 定期对操作系统进行镜像备份。
  • 防止第三方软件服务漏洞攻击(虽然有防火墙,但不能保证防火墙被同事或其他人修改。可以在防火墙的基础上,再做一些防护)
    • 使用 Docker 以 Non-Root User (Rootless mode) 身份运行软件服务(如:Redis)。

数据库安全

数据库安全问题

  • 登录帐号攻击。用户帐号被破解导致:1. 用户隐私信息泄露。2. 数据库被删除或篡改。3. 比特币勒索。

数据库防护措施

  • 一般保护
    • 修改数据库默认端口。
  • 数据保护
    • 开启MySQL 的 binlog 和 redo log。
    • 定期手动(或定时脚本自动)进行异地备份数据库(或增量同步到备份数据库系统)。
    • 重要的隐私信息加密存储。
  • 帐号保护
    • 设置高强度 root 密码。
    • 不使用 root 帐号登录数据库。
    • 不同数据库设置不同的用户帐号。
    • 不要把帐号密码明文写在项目配置文件中。
  • 远程登录保护
    • 设置数据库远程登录 IP 白名单。
    • 远程登陆使用公钥登录。
    • 使用 MySQL 代理。写一个 LUA 检查脚本,错误登录频率限制,三次错误登录 IP 加入黑名单。
    • 不使用远程登录数据库。通过 SSH 登录服务器,从而本地访问数据库。

应用系统安全

应用系统安全问题

  • 登录帐号攻击。1. 字典攻击或暴力攻击,获取用户的帐号密码。从而获取用户隐私,执行恶意操作。
  • 网络通信安全攻击。1. 监听网络通信数据。2. 篡改网络数据。3. 网站伪造。钓鱼网站。
  • XSS 攻击。1. 页面注入恶意脚本<script>,获取用户的 Cookie 信息,从而登录用户帐号。2. 让原始用户执行不当操作。如 <img src="http://mybank.com/transfermoney?amount=1000&toaccount=14512">
  • SQL 注入攻击。1. 执行未授权的恶意 SQL。如';<some SQL statement>;--2. 跳过登录密码验证。
  • DOS / DDoS 攻击。1. 拒绝服务攻击,大量请求服务器,导致正常用户无法使用网站应用。

应用系统防护措施

  • 一般保护
    • 使用加密的域名解析服务,不暴露服务器 IP 地址。
    • 使用 Cloudflare proxied DNS 或者 Cloudflare Tunnel,不暴露服务器 IP 地址。
    • 使用 Nginx 代理,隐藏应用端口。
    • 应用端口仅能被代理服务器访问,系统防火墙设置应用端口访问的 IP 白名单。
    • 定期备份应用的数据文件。
  • 登录保护
    • 错误登录频率限制。要求输入验证码。
    • 使用 Tow-Factor 认证,如:短信验证码。
    • 异地登录邮件提醒。
    • 禁止弱口令。需要设置高强度用户密码。
  • 网络通信保护
    • 使用 HTTPS 协议通信。
    • 客户端用户密码加密和加盐传输。
  • XSS 攻击防护
    • 不允许用户输入的文本显示为 HTML 标签。
    • 阻止 XSS 攻击运行在其它网站。1. HTTP 请求头的 referer 检查。2. 不仅仅通过 cookie 中的session 来识别用户,判断用户 session 需要检查是否为原始用户认证通过的 IP 地址。
    • 添加和修改操作使用 HTTP POST 请求。
  • SQL 注入防护
    • Java 数据库操作使用 PreparedStatement 对象。
  • DoS / DDoS 防护
    • 异常高频率访问 IP ,使用 Fail2ban 进行限制和加入黑名单。
    • Cloudflare DDoS Protection and Web Application Firewall (WAF).
  • 使用审计追踪用户行为。检查、分析和阻止恶意行为。
  • 部署入侵侦测系统。记录和分析恶意攻击行为。
  • 部署蜜罐系统。检测未知的攻击,识别潜在风险。

References

[1] 5 Linux SSH Security Best Practices To Secure Your Systems

[2] Preventing brute-force attacks on MySQL? - serverfault

[3] Database System Concepts - Section 9.7: Application Security

算法分析方法

  • 渐进分析(Asymptotic Analysis)

常见的算法设计策略

  • 动态规划(Dynamic Programming)
  • 分治(Divide and Conquer)
  • 贪心(Greedy Algorithm)
  • 回溯(Backtracking)

常见的排序算法

  • 插入排序(Insertion Sort)
  • 冒泡排序(Bubble Sort)
  • 选择排序(Selection Sort)
  • 希尔排序(Shellsort)
  • 归并排序(Mergesort)
  • 快速排序(Quicksort)
  • 堆排序(Heapsort)
  • 基数排序(Radix Sort)

常见的搜索算法

  • 二分查找(Binary Search)

常见的数据结构操作算法

  • 二叉树的遍历(递归和循环实现方式)
    • 先序遍历(Pre-order Traversal)
    • 中序遍历(In-order Traversal)
    • 后序遍历(Post-order Traversal)
    • 层级遍历(Level-order Traversal)
  • 图的搜索
    • 深度优先搜索(Depth-first Search, DFS)
    • 广度优先搜索(Breadth-first Search, BFS)

算法策略

算法策略是指设计和解决一类问题的通用方法或模板。也可以称为算法范式(Algorithm Paradigm)。常见的算法策略,如动态规划、分治、贪心和回溯等。每个算法策略有它的设计思路,解题步骤。算法策略往往提供了优化算法的功能,使用算法策略比直接法(枚举法,穷举法)有更好的效率。

动态规划

介绍动态规划

动态规划(Dynamic Programming)是一种数学优化方法和一种算法策略。这个方法是 Richard Bellman 在1950 年发明的。它指的是通过递归的方式,将问题分解为更简单的子问题,从而简化一个复杂的问题。

动态规划问题必须具备两个属性:最优子结构和重叠的子问题。如果一个问题有最优子结构,但没有重叠的子问题,那么它是一个分治问题。

最优子结构(Optimal Substructure)。如果一个问题可以通过“将问题分解为子问题和递归地找到子问题的最优解”来最优地解决,那么称它有最优子结构。也可以说,得到一个问题的最优解可以通过子问题的最优解来实现。如果一个问题具有最优子结构,较大问题和它的子问题之间的存在关联,这种关系称为 Bellman 方程。

重叠的子问题(Overlapping Sub-Problem)。求解过程中递归地求解相同的子问题,没有产生新的子问题。也可以说,问题和子问题存在一个固定的关系表达式,如 F(n) = F(n-1) + F(n-2) ,F(n) = F(n-1) * 2 + 1。所有的子问题可以用一个固定的算法代码去实现。

解决动态规划问题的两种方法:

  • 自顶向下方法(Top-down Approach)。将子问题的结果存储在列表中,当我们要解决新的子问题时,我们会首先检查是否已经解决。如果结果已经记录在列表中,可以直接使用它,否则我们解决子问题并将其结果添加到列表中。如:我们可以通过 F(n) = F(n-1) + F(n-2) 得到问题的结果,先检查子问题结果列表中有没有子问题 F(n-1) 和 F(n-2) 的结果,如果有就直接使用,没有就求解这两个子问题。依次往下递归,每个子问题只计算一次。
  • 自底向上方法(Bottom-up Approach)。一旦按照子问题递归地制定了问题地解决方案,我们可以尝试自底向上地去解决问题。自底向上地方法大部分不需要记录每个子问题的结果,只需要替换记录几个需要地子问题结果。具体过程:通过使用较小的子问题的结果迭代生成越来越大的子问题结果。如,已知子问题 F40 和 F41 的结果,可以直接计算出 F42 的结果。

解题步骤

1. 判断一个问题是否是动态规划问题。

判断问题是否具有最优子结构和重叠的子问题等特性。

2. 构造出问题与子问题之间的关系表达式

常见的动态规划问题的子问题关系表达式,见附录一

3. 使用自顶向下或者自底向上求解。

例子

求第 n 个斐波那契数。

直接法简单的解决方法。T(n) = O(2 ^ n), S(n) = O(1)

int fib(int n)
{
if (n <= 1)
return n;
return fib(n-1) + fib(n-2);
}

动态规划自顶向下方法。T(n) = O(n), S(n) = O(n)

int fib(int n, auto &lookup)
{
if (n <= 1)
return n;
if (lookup.find(n) == lookup.end())
lookup[n] = fib(n-1, lookup) + fib(n-2, lookup);
return lookup[n];
}

动态规划自底向上方法。T(n) = O(n), S(n) = O(1)

int fib(int n)
{
if (n < 1)
return n;
int prev = 0, curr = 1;
for (int i = 0; i < n-1; i++)
{
int newFib = prev + curr;
prev = curr;
curr = newFib;
}
return curr;
}

分治

介绍分治

分治(Divide and Conquer)是一个基于多分支递归的算法设计范式。它将一个问题递归的分解为两个或多个子问题,直到它们足够简单能够直接求解。

解题步骤

分析问题得到分解策略。

根据分解策略,递归地求解。

例子

参考快速排序、归并排序算法。

贪心

介绍贪心算法

贪心算法(Greedy Algorithm)是一个算法设计范式。它在每个阶段做出局部最优选择,以寻求全局最优。

解题步骤

根据贪心策略一小段一小段的求解,最后得到问题的最优解。

回溯

介绍回溯

回溯(Backtracking)是一种算法范式,用于查找某些计算问题的所有结果。它逐步的构建候选对象,一旦确定候选对象无法得到有效的结果立刻将其放弃。

回溯是解决约束满足问题的重要工具。它可以快速测试是否能得出满足条件的有效结果。如填字游戏,数独,棋盘等问题。回溯算法通常比对所有对象的暴力枚举(Brute Force Enumeration)更有效率,因为它通过一个测试减少了很多候选对象。

常见类型:

  • 路径选择。如:最短路径,是否存在路径,打印所有存在路径。这类问题的背景:迷宫,国际象棋,二维数组,图。

解题步骤

1. 判断是否是一个回溯问题。

2. 设定一个开始点,进行循环遍历。

3. 当前结果是否满足条件。如果是,打印结果。如果不是,循环递归所有的有效的下一个候选对象。

例子

N Queens problem.

#include <iostream>
#include <cstring>
using namespace std;

#define N 8

int count = 0;

bool isSafe(char mat[][N], int row, int col)
{
for (int i = 0; i < row; i++)
{
if (mat[i][col] == 'Q')
{
return false;
}
}

for (int i = row, j = col; i >= 0 && j >= 0; i--, j--)
{
if (mat[i][j] == 'Q')
{
return false;
}
}

for (int i = row, j = col; i >= 0 && j < N; i--, j++)
{
if (mat[i][j] == 'Q')
{
return false;
}
}

return true;
}

void NQueen(char mat[][N], int row)
{
if (row == N)
{
cout << ++count << endl;
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
cout << mat[i][j] << " ";
}
cout << endl;
}
cout << endl;
return;
}

// place Queen at every square in current row r
// and recur for each valid movement
for (int k = 0; k < N; k++)
{
if (isSafe(mat, row, k))
{
// place queen on current square
mat[row][k] = 'Q';
// recur for next row
NQueen(mat, row+1);
// backtrack and remove queen from current square
mat[row][k] = '-';
}
}
return;
}

int main()
{
// mat[][] keeps track of position of Queens in current configuration
char mat[N][N];

// initalize mat[][] by '-'
memset(mat, '-', sizeof mat);

int row = 0;
NQueen(mat, row); // N=8 has 92 possible N-Queen result
return 0;
}

关于算法设计的思路

1. 思考问题的直接法的解决方法。

2. 考虑优化。是否存在大量重复的操作?是否存在不必要的操作?

3. 考虑算法设计策略。

问题的局部最优解能不能得到全局最优。如果能,考虑使用贪心算法。

问题能不能递归地分解为子问题求解(是否具有最优子结构)。如果能,考虑使用分治法,或者动态规划。

附录一

常见的动态规划问题的子问题关系表达式。

Fibonacci

         | n                  (n <= 1)
Fib(n) = |
| Fib(n-1) + F(n-2) (n > 1)

Longest Common Subsequence (LCS)

             | 0                              (i == 0 or j == 0)
LCS[i][j] = | LCS[i-1][j-1] + 1 (X[i-1] = Y[j-1])
| max(LCS[i-1][j], LCS[i][j-1]) (X[i-1] != Y[j-1])

Shortest Common Supersequence (SCS)

             | i+j                            (i=0 or j=0)
SCS[i][j] = | SCS[i-1][j-1] + 1 (X[i-1] = Y[j-1])
| min(SCS[i-1][j] + 1, SCS[i][j-1] + 1) (X[i-1] != Y[j-1])

Longest Increasing Subsequence (LIS)

         | 1    (n=1)
LIS(n) = |
| max(L(i)) 0 < i <= n

The Levenshtein distance (Edit distance)

             | max(i, j)                       when min(i, j) = 0

dist[i][j] = | dist[i - 1][j - 1] when X[i-1] == Y[j-1]

| 1 + minimum { dist[i - 1][j], when X[i-1] != Y[j-1]
| dist[i][j - 1],
| dist[i - 1][j - 1] }

References

[1] Dynamic programming - Wikipedia

[2] How to solve a Dynamic Programming Problem ? - Geeksforgeeks

[3] Introduction to Dynamic Programming - Techie Delight

[4] Divide-and-conquer algorithm - Wikipedia

[5] Backtacking - Wikipedia

基本数据结构

List


List 的抽象数据类型

列表(List)包含一组按顺序排列的相同类型的元素,它的操作如下:

  • get() - 返回给定位置的一个元素。
  • insert() - 插入一个元素在指定位置。
  • remove() - 删除第一个元素。
  • removeAt() - 删除一个特定位置的元素。
  • size() - 返回列表元素的数量。
  • isEmpty() - 返回列表是否为空。
  • isFull() - 返回列表是否已满。
  • clear() - 清空列表的内容。

List 的实现

Array List

数组是列表的一种实现,它在内存中是连续的空间。

优点

  • 它可以快速的随机访问。

缺点

  • 插入和删除性能不太好,需要大量的元素位置移动。

应用场景

  • 简单的存储一组数据。

Linked List

链表是一个线性结构,它是列表的另一种实现,它在内存中是非连续的空间。每个元素称为节点,相邻的两个节点由指针进行连接。

优点

  • 它具有很好的扩展性,快速的插入和删除。

缺点

  • 随机访问(按位置访问)性能不好。节点的访问必须从前往后依次遍历。
  • 比数组消耗更多的内存。在使用的指针上。
  • 需要更多的读取时间。在内存中是不连续的,CPU 一次只能获取一个元素,

应用场景

  • 迭代器。

Array List vs Linked List

数组 链表
随机存取 T(n) = O(1) T(n) = O(n)
插入和删除 T(n) = O(n) T(n) = O(1)

Stack


Stack 的抽象数据类型

栈(Stack)是一个线性的数据结构。它遵循一个特殊的操作顺序。后进先出(Last In First Out, LIFO),它的操作如下:

  • push()
  • pop()
  • peek()
  • size()
  • claer()

Stack 的实现

可以基于数据实现,也可以基于链表实现。

优点

  • 方便的按照某种循序访问数据。

缺点

  • 只用于特定场合。

应用场景

  • 逆序输出。
    • 进制转换。
  • 递归嵌套。
    • 括号匹配
    • 栈混洗(Stack Permutation)。通过一个中间栈,重新排序原始栈的数据顺序。。
  • 延迟缓冲。
    • 中缀表达式。操作符以中缀形式处于操作数中间的算术表达式。
    • 逆波兰表达式。不需要括号的后缀表达式。
  • 栈式计算。

Queue


Queue 的抽象数据类型

队列(Queue)是一个线性结构。它和栈类似有特殊的操作顺序。先进先出(First In First Out, FIFO)。它的操作如下:

  • enquque()
  • dequeue()
  • peek()
  • size()
  • claer()

Queue 的实现

队列可以基于数组实现,也可以基于链表实现。

优点

  • 方便的按照某种循序访问数据。

缺点

  • 只使用在特定的场合。

应用场景

  • 系统任务调度。

Binary Tree


Binary Tree 的抽象数据类型

二叉树(Binary Tree)是一个树结构,每个节点最多有两个子节点,分别表示为左子树和右子树。它的操作:

  • getRoot()
  • isEmpty()
  • size()
  • contains()
  • find()
  • toString()
  • iteratorInOrder()
  • iteratorPreOrder()
  • iteratorPostOrder()
  • iteratorLevelOrder()

Binary Tree 的实现

二叉树通过指针关联节点实现。

优点

  • 可以表示树结构的数据关系。

缺点

  • 大量的指针消耗。

应用场景

  • 用于搜索。通过 binary search tree 可以支持快速的搜索、插入和删除,复杂度都是 O(log n)。
  • 用于压缩。使用在 Huffman Coding。
  • 用于数据库的索引的实现。

Graph


Graph 的抽象数据类型

图(Graph)由一组有限个顶点(Vertex)和有限的边(Edge)组成。它的操作如下:

  • addVertex(vert)
  • addEdge(fromVert, toVert)
  • addEdge(fromVert, toVert, weight)
  • getVertex(vertKey)

Graph 的实现

可以通过邻接表(adjacency List),也可以邻接矩阵(adjacency Matrix)实现。

邻接矩阵 邻接表
内存 O(n^2) less than adjacency Matrix
查询一条边 O(1) O(k)
遍历所有边 slow fast
添加/删除一个节点 O(n^2) O(n)
添加一条边 O(1) O(1)

应用场景:

  • 用于计算机网络的路由,找到最小花费路径。

  • 社交媒体应用,用户之间的关联。

  • 地图应用。如,找到最优的行驶路线。

  • 电商网站。推荐商品。

其它数据结构

Dictionary


Dictionary 的抽象数据类型

字典(Dictionary)高效地组织数据,支持快速地存储和查找。每个元素由一个 key 和一个复合类型(对象)组成<key, E>。通过 key 来唯一标识一个对象,可以通过 key 来查找对象。它的操作如下:

  • insert(key, object)
  • remove(key)
  • find(key)
  • size()
  • clear()

Hash Table


Hash Table 的抽象数据类型

哈希表(Hash Table)是一个由链表组成的数组结构。它使用哈希函数计算一个元素值在数组中的索引。它的操作:

  • put(key, value)
  • get(key)
  • containsKey(key)
  • contains(value)
  • remove(key)

Hash Table 的实现

哈希表由链表数组实现。

优点

  • 快速的查找。
  • 快速的插入和删除。

缺点

  • 不保证元素的顺序。不能随机访问。

数据结构的选择

插入删除/扩展性

数据的容量需要很好的扩展性,或者频繁的插入和删除,选择动态的数据结构(指针关联)比较合适。如链表,二叉树等等。不足之处是指针结构对随机存取效率没有静态结构(连续空间)好。

搜索

简单的搜索即从第一个元素一直比较直到最后一个元素。如数组,它的复杂度是 O(n)。

更高效率搜索的数据结构,哈希表的复杂度为 O(1),有序数组复杂度为 O(log n),搜索二叉树的复杂度为 O(log n)

综合考虑

兼顾搜索和插入:可以考虑哈希表和搜索二叉树。有序数组虽然搜索复杂度和搜索二叉树一样,但是有序数组的插入性能不好。

兼顾搜索和随机存取:可以考虑有序数组。

数据结构的总结

组成形式:连续空间,不连续(指针)空间。

结构关系类型:线性,树状,图

本质:存储一组元素,表示元素之间的关系,提供操作元素的方法。

数据结构的总结:

  • 数据结构是用来表示数据和操作数据的。

  • 有些数据结构只适用于特殊的场景。

  • 一个好的数据结构可以大幅度提高算法的效率。

  • 不存在一种数据结构在所有方面都优于另一个数据结构,每个数据结构都有自己的优缺点。数据结构的选择是一个权衡利弊和妥协的过程。

References

[1] Data Structures and Algorithm Analysis in C++ (3rd) by Clifford A. Shaffer

[2] Abstract Data Types - GeeksforGeeks

[3] The BinaryTree ADT

[4] The Graph Abstract Data Type

[5] Linked List - Wikipedia

[6] What are the applications of binary trees? - Stack Overflow

[7] What are real life applications of graphs? - Quora

大多数计算机程序的主要目的不是计算,而是存储和取出信息。面对大量的信息,我们要如何组织信息,使它能支持更高效的处理?这就是数据结构需要解决的问题。在特定问题下,我们可以根据不同的数据结构它们对应的花费和收益,选择一个更高效更快的数据结构。

解决一个问题常常由很多方法。我们要如何选择?首先,我们要知道程序设计的核心是什么?它主要是实现以下两个目标:

  1. 设计一个易于理解、实现和调试的算法。
  2. 设计一个更高效的使用计算机资源的算法。

第一点指的是程序的优雅,第二点是指程序的效率。在算法领域,我们主要是探究算法的效率。一个问题最优的解决方案就是找到一个效率最高的算法。

如何知道一个算法比另一个算法更好?这就要测量一个算法的效率。一个常见的用来估计算法的效率的方法称为渐进分析(Asymptotic Analysis)。

抽象数据类型和数据结构

类型(Type)是指一组值。如布尔类型由 truefalse 组成。整数也是一个类型。类型可以分为:简单类型(Simple Type)和复合类型(Composite Type)。一个数据项只有一个成员的类型称为简单类型。如整数类型。一个数据单元由多个成员组成的类型称为复合类型。如银行帐号的个人信息。

数据类型(Data Type)它指的是一个类型和一组操作。

抽象数据类型(Abstract Data Type,ADT)它定义了一个类型和一组的操作。它是数据类型的逻辑实现。

数据结构(Data Structure)是一个 ADT 的在编程语言上的具体实现。它是数据类型的物理实现。

数据类型只是一个称呼,它没有具体的内容。抽象数据类型是数据类型的抽象的(或逻辑的)表示,它表示了有哪些具体的操作,定义了类型的名称和操作的名称,它是描述文字。数据结构是用具体的编程语言代码对 ADT 的定义,它是可执行的代码。

问题,算法和程序

问题是一个要执行的任务。它表示为任意的输入得到匹配的输出。问题必须是明确定义的和能清晰理解的。问题的定义应该包含计算机资源的限制,如内存,硬盘和时间。

算法是解决一个问题的方法(或过程)。一个算法必须具备以下属性:

  • 正确性。每一个输入能够转换为正确的输出。
  • 可行性。算法中任何计算步骤可以被分解为基本的可执行的操作。
  • 确定性。没有歧义的。
  • 有穷性。能在执行有限个步骤之后终止。
  • 输入项。0个或多个输入。
  • 输出项。一个或多个输出。

程序是一个由编程语言实现的算法的实例(或具体的表现)。程序不像算法,程序是可以一直运行不终止的,如操作系统。

数据结构的选择

给定一个足够的空间去存储一组数据项,无论使用什么数据结构去表示这些数据,每种数据结构总是可能执行所有必要的操作,如搜索,排序,插入,删除等。然而使用不同的数据结构的区别在于,执行一个程序,一个只需要几秒钟,另一个则需要好几天。一个选择数据结构的方法如下:

  • 分析问题需要哪些基本的操作。如插入,删除,查找。
  • 量化每个操作的资源约束(时间和空间)。
  • 选择满足需求的最好的数据结构。

每个数据结构有它相应的花费和收益。没有一个数据结构在任何情况下都比另一个数据结构好。你需要根据问题需求选择合适的数据结构。

算法的选择

一个好的数据结构的选择,可以做到事半功倍的效果。一个算法实现策略的选择也十分关键。简单地使用直接法(枚举法)可以解决问题,但效率很低,它与贪心策略或动态规划相比,算法执行的时间往往是数量级的差别。

算法的核心优化思路是去除不必要的操作和去除重复的操作。几乎所有的算法策略都是这个思路,如动态规划、分治法。

References

[1] Data Structures and Algorithm Analysis in C++ (3rd) by Clifford A. Shaffer

计算机网络的核心概念

应用层

你必须知道的应用层

  • 应用层提供了两个进程之间的信息交换。它的报文定义了信息的格式和规则。
  • HTTP 协议:它用于 Web 应用,它的报文格式为:请求/相应行 + 头部行 + 数据负载。
  • DNS :DNS 系统的等级结构?如何配置域名解析?获取一个域名的 IP 地址的过程?
  • CDN 内容分发网络的基本概念。

传输层

你必须知道的传输层

  • 传输层是两个进程之间的数据传输。它的报文定义了传输服务所需要的字段。
  • TCP 是如何实现可靠数据传输和拥塞控制的?
  • UDP 和 TCP 的区别?不同的场合如何选择合适传输层协议?

网络层

你必须知道的网络层

  • 网络层是两个终端主机之间的数据交换。
  • 数据包是如何在路由器中转发的?路由器的结构和工作原理?
  • IP 寻址和子网的概念。
  • 因特网的组成结构:它是由许多个 ISP 相互连接而成的图状结构。每个 ISP 是自治的网络。
  • 路由算法是为了寻找两个主机之间的最小花费路径。常见的路由算法有哪些?它们是如何实现的?
  • 网络是如何管理的?

链路层

你必须知道的链路层

  • 链路层是两个相邻的节点之间的数据传输。
  • 主机和路由器的每个网络接口既有 IP 地址也有 MAC 地址。
  • 链路层使用 MAC 地址进行数据转发。
  • ARP 协议可以通过一个局域网的 IP 地址获得对应的 MAC 地址。
  • 以太网是流行的构建局域网技术。NAT 可以用来扩展局域网。
  • 如何搭建一个安全、隔离、可扩展的局域网。

网络安全

你必须知道的网络安全

  • 网络安全是保障网络服务的安全。
  • 网络安全分为网络通信安全和运营安全。
  • 实现网络通信安全的基本要素:保密性,完整性,终端认证。
  • 实现网络通信安全的基础技术:加密算法,消息摘要算法,数字签名,服务端的公钥证书,客户端认证等。
  • 网络安全协议:SSL,IPsec。它们的实现原理。它们的基本原理?
  • 实现运营安全的技术:防火墙,入侵侦测系统和入侵防御系统。它们的基本原理?

网络协议

网络协议是为网络通信定义的规范。所有的网络协议的报文基本上是由三部分组成:

  • 我(报文)从哪里来,要到哪里去。
  • 报文的基本属性。
  • 携带的通信数据。

五层网络协议栈总结:

网络协议栈 是什么 做了什么 数据传输单元
应用层 两个终端进程之间的信息交换 1)定义通信的报文格式。2)把报文数据交给传输层去传输。 报文(Message)
传输层 两个终端进程之间的数据传输 1)错误验证,可靠传输,拥塞控制。2)将应用层报文封装成报文段,把报文段交给网络层传输到目标主机,把所有报文段组装成报文传递给应用层。 报文段(Segment)
网络层 两个终端主机之间的数据传输 1)分配局域网 IP 地址。2)路由选择。3)将传输层报文段封装成数据报,把数据报交给链路层传输到下一个节点,经过多次路由转发数据报到达目标主机,把所有数据报组装成报文段传递给传输层。 数据报(Datagram)
链路层 两个相邻节点之间的数据传输 1)获取局域网主机的 MAC 地址。2)错误验证。3)将网络层数据报封装成帧,把帧交给物理层传输到下一个节点,把所有帧组装成数据报传递给网络层。 帧(Frame)
物理层 两个相邻节点之间的信号传播 1)将链路层帧的数字比特流通过调制解调器转换为模拟信号,通过物理媒介传输模拟信号到下一个节点,将模拟信号解析成数字信号,把数字比特流传递给链路层。 比特流(Bit Stream)

计算机网络的五层网络协议栈,可用用现实生活中寄快递的例子来类比。

  • 应用层:用户发送快递和接收快递。
  • 传输层:用户选择使用哪家快递公司,即哪种运输服务。(不同的快递公司,运输的方式和快慢不一样)
  • 网络层:快递公司如何将货物从出发地点运输到目标地点。如何规划合理的运输路线(一个运输路线由多个节点组成)。
  • 数据链路层:快递公司如何保证两个相邻的节点之间的运输没有问题。
  • 物理层:如何在两个相邻节点之间运输货物。如:司机驾驶货车在公路上运输货物、空运等。

介绍计算机网络安全

传统的网络协议在网络通信时,消息都是明文的传输。如 HTTP,TCP,IP,Ethernet 等等协议。然而,明文传输会遭到他人恶意的攻击,这些攻击会窃取客户端用户的隐私信息,阻止服务端的正常工作等等。常见的安全攻击类型有:恶意软件攻击,拒绝服务攻击,数据嗅探,IP 欺骗等等。

计算机网络安全问题可以归为两类:网络通信安全,运维安全。

常见的网络通信安全问题有:

  • 消息窃听。
  • 消息篡改。插入,删除消息内容。
  • 消息伪造和重复消息。

保证网络通信的安全所需要的必要属性:

  • 保密性(Confidentiality)。消息进行加密,保证只有通信双方知道消息的内容。
  • 消息完整性(Integrity)。保证消息没有被修改。
  • 终端认证(End-point Authentication)。确认通信对方的身份。

一个网络安全协议必须实现以上所需的功能,从而实现安全的网络通信。常见的网络安全协议有 PGP,SSL,IPsec 等。

运维安全是指阻止外界对服务提供机构网络的恶意网络攻击。常见的防御措施有:建立防火墙,入侵侦测系统和入侵防御系统等。

通过本小节我们了解了网络安全的基本概念,接下来我们将介绍实现网络安全功能的基础技术,如机密和完整性校验等;网络安全协议的基本概念;最后我们将了解防火墙和入侵侦测系统是如何保护一个组织网络的安全的。

实现网络安全的技术

加密算法

加密是指发送端发送加密的数据,入侵者不能从拦截的数据中获取原始的信息,接收端可以恢复原始的数据。原始消息称为明文(Plaintext),经过加密算法加密后的消息称为密文(Ciphertext)。加密算法通过明文 m 和密钥 K1 作为输入,产生密文作为输出。密文表示为 K1(m)。解密算法通过密文和密钥 K2 作为输入,产生明文作为输出。解密表示为 K2(K1(m)) = m

我们常常把其它操作与加密(Encryption)产生混淆。如编码(Encoding)和消息摘要(Message Digest)。加密需要通过密钥来加密和解密,其他人没有密钥无法进行加密和解密。区分是不是加密的关键因素是:1. 只有使用密钥才能进行相关操作。2. 数据可以加密和还原。

编码是将一种数据格式转换为另一种格式,相应的操作为编码和解码。编码是任何人都可以操作,它不满足只有使用密钥才能进行相关操作的条件,所以它不是加密算法。常见的编码方式如 Base64。

消息摘要算法。通过 Hash 算法产生一个固定长度的值。它的转换是单向的。常用于数据完整性校验,存储用户密码信息。它没有密钥,也无法还原数据,所以它不是加密算法。常见的消息摘要算法如 MD5。

对称加密(Symmetric Encryption)

对称加密算法共享唯一的密钥进行加密和解密操作,常见的对称加密算法有:DES,3DES,AES 等。对称加密技术的类型有两种:流加密(Stream Ciphers),分组加密(Block Ciphers)。网络安全协议 PGP,SSL,IPsec 等大多使用分组加密技术。这里主要介绍分组加密技术。

分组加密:将消息明文划分为等长的块。每一个块独立加密。密文使用一对一映射,将 k bit 长度的明文块转换为 k bit 长度的密文块。一个 k bit 长的密文所有可能的有 2^k !,暴力破解几乎不可能做到。如暴力破解一个 128 位的 AES 密文大约需要100万亿年。

非对称加密(Asymmetric encryption)

非对称加密使用一对密钥即公钥(Public Key)和私钥(Private Key)来进行加密和解密,常见的算法如 RAS 算法。它可以让公钥公开在网络上分发,让客户端加密消息,但只有服务端才能解密。非对称加密不仅可以用于加密,也可以用于认证和数字签名。一般的用法时公钥加密,私钥解密。私钥签名,公钥验签。

RSA 的实现原理

生成公钥和私钥过程

  • 选择两个大质数 p 和 q。
  • 计算 n = pq, z= (p-1)(q-1)。
  • 选择一个数字 e,小于 n,且与 z 没有公共因数。
  • 找到一个数字 d,始得 ed - 1 能被 z 整除即 (ed - 1) % z = 0。
  • 公钥是一对数字 (n, e),私钥是(n, d)

加密和解密过程

设消息明文表示为 m,密文为 c。公钥为 (n, e),私钥为 (n, d)

使用公钥对明文加密。加密计算公式: c = m ^ e % n

使用私钥对密文解密。解密计算公式: m = c ^ d % n

例子,

设 p=5, q=7, n= 35, z=24, e=5, d= 29

若明文为 m=12

加密计算 c = 12 ^ 5 % 35 = 17

解密计算 m = 17 ^ 29 % 35 = 12

RSA 算法工作原理

RSA 用到的基础理论:

  • 取模运算转换公式:(a % n)^d % n = a^d % n。
  • 数值理论转换。如果 p,q时质数,n=pq,z=(p-1)(q-1),则 x^y % n = x ^ (y % z) % n。下面应用计算应用 x=m, y=ed.

证明过程:

RSA 加密公式为:c = m^e % n,解密公式为: m = c^d % n

由 c = m ^ e % n 推导 m = c ^ d % n 过程如下:

(1)将 c = m ^ e % n 代入解密公式等式,得 m = (m ^ e % n) ^ d % n

(2)由取模运算转换公式,得 m = m ^ ed % n

(3)由数值理论转换和已知条件 (ed - 1) % z = 0,得 m = m ^ (ed % z) % n = m ^ 1 % n = m % n

(4)由 m < n,得 m = m。等式左边等于右边,证明原等式成立。

消息完整性和数字签名


介绍消息完整性

认证消息的完整性(Message Integrity)需要实现以下功能:

  • 验证消息来源自合法的终端。
  • 验证消息没有被篡改。

实现消息完整性常用的技术有:数字签名(Digital Signatures)和终端认证(End-point Authentication)。

哈希算法和消息认证码

哈希算法将输入转换为固定长度的哈希值。哈希算法保证了任意的两个不同的值得到不同的 hash 值。常见的哈希算法有:MD5,Secure Hash Algorithm (SHA-1)。

传统的网络协议使用 checksum 校验消息完整性(数据分片和校验和相加结果为n个1),这种方式存在不同的消息可以存在相同的checksum。哈希值有唯一的特性,可以使用消息的哈希值检验完整性,这样更加安全可靠。

消息认证码

简单的使用哈希进行完整性校验的方式为:将消息 m 的哈希值 H(m) 附加到消息后面,组成新的消息message(m, H(m))。但是,这样的处理很容易被伪造消息。使用伪造一个新的消息,后面附加新消息的哈希值 message(m’, H(m’)),这同样也是一个具有完整性的消息。

为了防止伪造消息,可以使用加密的哈希方法(Cryptographic Hash Function),通信的双方需要共享一个密码 s 称为认证码(Authentication Code)。新的消息可以表示为 message(m+s, H(m+s))。这样入侵者不知道认证码,生成不了正确的哈希值。

数字签名(Digital Signature)

数字签名是一个加密技术,它指出了一个文档的拥有者是谁。数字签名具有的特性:可验证的,不可伪造的。

数字签名可以使用非对称加密实现。使用私钥加密,使用公钥验证。私钥是被唯一拥有的,公钥可以分发在网络上进行验证。可以对整个消息加密作为数字签名K(m),但这样太耗费了。更好的做法是对消息的哈希值进行加密作为数字签名K(H(m))。

公钥证书(Public Key Certification)

数字签名的一个重要的应用就是公钥证书。公钥证书它证明一个公钥属于一个具体的实体。公钥证书使用在很多流行的安全网络协议,如 IPsec 和 SSL。

认证机构(Certification Authority, CA),它可以绑定一个公钥和一个特定的实体。它的工作是验证身份和发布证书。

CA 验证实体的身份后,CA 创建一个证书(Certificate),它绑定实体的公钥和身份。证书包含了公钥和实体的认证信息(人的名称,有效期)等等。

实际使用场景,如客户端浏览器访问一个 HTTPS 网址,建立 SSL Socket 连接时,服务器给客户端发送自己的公钥,客户端可以通过 CA 来验证接收到的公钥是不是该网址的公钥。

终端认证

终端认证(End-Point Authentication)指的是在网络上一个实体提供它的身份给另一个实体的过程。

不安全的客户端终端认证方式:

  • 明文密码认证。可能被窃听,被他人获取密码。
  • 加密密码认证。可能受到回放攻击(Playback Attack)。被他人窃取和利用加密后的密码。

安全的客户端终端认证:

  • 使用 nonce 和加密的密码。一个 nonce 是一个数字仅仅在网络通信中使用一次,即每次通信使用不同的 nonce。这样可以防止他人利用加密后的密码。

网络安全协议

SSL

Secure Sockets Layer(SSL)是一个传输层协议,提供增强 TCP 的安全服务。SSL 提供了保密性,完整性,服务器认证,客户端认证等服务。SSL 的修改版本 SSL version 3 称为 Transport Layer Secure(TLS)。

SSL 建立连接的过程

  • 三次握手
    • 客服端,发送 SYN。
    • 服务端,响应 SYN/ACK
    • 客服端,发送 ACK。
  • 协商(Negotiation)
    • 客户端发送 Hello,以及支持的加密算法列表,和客户端 nonce(Random Number #1)。
    • 服务端响应,发送服务端选择的一个对称加密算法,一个非对称加密算法,一个 MAC 算法,SSL 证书,和服务端 nonce(Random Number #2)。
    • 客服端,验证证书,提取服务端公钥,生成一个 Pre-Master Secret(PMS),使用公钥加密 PMS,发送 PMS 和客户端证书(可选项)给服务端。
    • 服务端,使用私钥解密 Pre-Master Secret。
  • 共享密码
    • 客户端和服务端,通过随机数 #1,#2 和 PMS 计算得到 Master Secret(MS)。使用MS 生成 两个 加密密钥,两个 MAC 密钥。
  • 握手完成
    • 客服端,发送一个哈希消息使用主密码(MS)
    • 服务端,发送一个哈希消息使用主密码(MS)

SSL 实现安全通信的方法

  • 保密性
    • SSL 使用非对称加密算法交换共享的对称加密密钥。
    • SSL 使用对称加密算法加密通信的数据。
  • 完整性
    • 使用报文数据域附加的 MAC 进行完整性校验。
    • 通过 CA 验证服务端公钥的合法性。从而认证消息来自合法的终端。
  • 服务端认证
    • 通过 CA 验证公钥的合法性。从而认证服务端。
  • 防止回放攻击
    • 使用 nonce 防御连接回放攻击(Connection Playback Attack)
    • 使用MAC key + Sequence Number 生成MAC,防御数据包传输途中的回放攻击。

IPsec

IPsec (IP Security Protocol)提供网络层通信安全。它能够在所有主机和路由器上安全地传输网络层报文。IPsec 协议常用来在公共的因特网上创建一个虚拟专用网络(Virtual Private Network, VPN)。

一个机构跨越多个不同的地理区域常常想要自己的专用 IP 网络。但是搭建一个物理的专用网络代价太昂贵了,需要购买、安装和维护自己的物理网络基础设施。所以现实中通常使用 VPN 来搭建专用网络。

IPsec 的实现原理

IPsec 将原来正常的 IP 数据报转换为一个新的加密的 IP 数据报。

IPsec 创建一个新的 IP 报文:

  • 创建新的 IP 报文头部。
  • 将原始的IP 报头部和原始数据负载进行加密,加上 IPsec 头部,尾部和MAC等,组成新的 IP 报文的数据负载。

新的 IP 数据报头部和传统的 IPv4 头部相同,对于路由器来说它和正常的 IP 报文没什么区别。

IPsec 在客户端主机中将传输层的数据封装成加密的 IP 报文,在服务端主机中解密 IP 报文传给传输层。

AH 和 ESP 协议

Authentication Header(AH)协议和 Encapsulation Security Payload (ESP)协议为 IPsec 提供安全通信服务的子协议。IPsec 协议套件需要使用其中的一个协议来实现安全通信。

AH 协议提供终端认证和数据完整性,不提供保密性。

ESP 协议提供 终端认证,数据完整性,和保密性。

因为 ESP 协议提供完整的安全通信功能,所以它比 AH 协议使用更广泛。

安全关联

在 IPsec 协议中,源地址和目标地址创建的一个逻辑连接称为一个安全关联(Security Association,SA)。它是单向的,所以两个终端之间的通信需要建立两个 SA。SA 维护了 IPsec 通信的重要信息,如 加密算法类型,加密密钥,认证密钥等等。客户端和服务端都需要维护 SA 信息,SA 存储在 Security Association Database (SAD)中。通过 SA 中的信息终端主机可以进行加密,解密和认证等操作。

IPsec 的数据报

IPsec 的数据报格式如下图所示:

IPsec vs SSL

  • 安全性。IPsec 和 SSL 都可以提供了安全的网络通信。
  • 便利性。IPsec 需要一个第三方客户端软件。实现更复杂,更高的维护成本。SSL 已经在浏览器中支持了不需要额外的软件和配置。
  • 服务。IPsec 位于网络层,提供全接入的安全网络,即两个主机之间建立安全连接。而 SSL 位于传输层,只能针对某个应用进程提供安全连接。

HTTPS

HTTPS 它是 HTTP 的一个扩展。它本身并不提供安全服务,它使用传输层 SSL 来进行安全通信。HTTPS 本质是 HTTP + SSL。所以,严格意义上它不算是网络安全协议。

HTTPS 和 HTTP 报文格式相同,只是 HTTPS 建立的是 SSL Socket 连接,而 HTTP 建立的是 TCP Socket 连接。

其它安全协议

更多安全网络通信协议,如 Email 安全和无线网络安全,可阅读后面给出的参考书籍。

运维安全

运维安全(Operational Security)是指阻止外界对服务提供机构网络的恶意网络攻击。它的是实现方法为:在一个计算机网络中,进入和离开一个网络时,进行安全的检查,记录,丢弃,和转发。它的实现技术为:防火墙(Firewall),入侵检测系统(Intrusion Detection System, IDS)和入侵防御系统(Intrusion Prevention System, IPS)

防火墙

防火墙允许网络管理员可以控制网络访问,在外部网络和内部网络之间。

防火墙的定义:

  • 所有的进入和外出的网络流量需要经过防火墙。
  • 只有授权的流量时允许通过的。
  • 防火墙本身是免疫渗透攻击的。

防火墙分为三种类型:

  • 传统的数据包过滤器(Traditional Packet Filter)。通过检查报文的头部进行过滤,过滤的规则定义在接入控制列表中。
  • 有状态的过滤器(Stateful Filter)。它追踪 TCP 连接,根据 TCP 连接状态来进行过滤。所有的 TCP 连接记录在过滤列表中。非法 TCP 状态的连接将被过滤。
  • 应用程序网关(Application Gateway)。检查一个具体应用程序的所有进入和外出的报文头部和数据负载。

入侵侦测系统

防火墙只是检测数据包的头部,然而,侦测更多的攻击类型,我们需要执行深度数据包检查,同时检查报文的头部和数据负载。虽然应用程序网关可以进行数据包检查,但是它只针对一个具体的应用程序。

入侵侦测系统(Intrusion Detection System)可以对所有通过它的数据包进行深度检查,当观察到恶意攻击流量时,生成报警信息。

入侵防御系统(Intrusion Prevention System)它可以直接过滤有嫌疑的网络流量。

References

[1] Computer Networking: A Top-Down Approach, by Jim Kurose

[2] Base64 - Wikipedia

[3] MD5 - Wikipedia

要实现一个 HTTP 请求,大致可以分为三个阶段。首先客户端要连接网络,获取自己的 IP 地址;然后客户端通过 DNS 域名解析得到目标域名的 IP 地址;最后客户端和服务端建立一个 TCP 连接进行网络通信。

一、客户端连接网络,获取一个 IP 地址

要访问 HTTP 服务器,首先客户端需要连接网络。获取到一个 IP 地址的过程如下:

  • 广播一个 DHCP 请求报文,报文封装成 UDP 报文段,设置源端口和目标端口。进而封装成 IP 数据报,目标 IP 地址为 255.255.255.255,源地址 IP 地址为 0.0.0.0。
  • IP 数据报封装成 以太网帧,目标 MAC 地址为 FF:FF:FF:FF:FF:FF。
  • DHCP 服务器在路由器中。路由器接收到 DHCP 请求。路由器可以分配的地址的 CIDR block 68.85.2.0/24。DHCP 服务器分配地址 68.85.2.101 给客户端。DHCP 服务器创建一个 DHCP ACK 报文,包含分配的 IP 地址,DNS 服务器 IP 地址 68.87.71.226,默认网关路由器IP地址 68.85.2.1,子网掩码 68.85.2.0/24。DHCP ACK 报文封装为 UDP 报文,进而封装为 IP 数据报,最后封装为 以太网帧,源 MAC 地址为路由器的 MAC 地址,目标 MAC 地址为客户端 MAC 地址。
  • 客户端收到 DHCP ACK 报文,获取到自己的IP地址和其它地址。

二、域名解析

浏览器输入要访问的 URL。浏览器创建一个 TCP Socket 来发送 HTTP 请求。为了创建 Socket 需要知道目标域名的 IP 地址。因此需要进行 DNS 解析。

  • 创建一个 DNS query 报文。封装为 UDP 报文段。设置目标端口。封装为 IP 数据报目标地址为域名服务器的 IP 地址(通过之前 DHCP 获取的)。
  • 客户端以太网帧需要知道目标地址的 MAC 地址。通过 ARP 协议获取到网关路由器的 MAC 地址。
  • 客户端创建 ARP query 报文。目标IP 地址为默认网关 IP 地址。ARP 报文封装为以太网帧,通过广播报文到达路由器。
  • 路由器接收到 ARP query 报文,响应请求,发送 ARP replay 报文。包含路由器的 MAC 地址。
  • 客户端收到 路由器的 MAC 地址,将 DNS query 报文发送给路由器。DNS query 报文封装为 IP 报文,目标 IP 地址是 DNS 服务器,最后封装为链路层报文目标地址是路由器的 MAC 地址。
  • 路由器收到 DNS query 报文,根据转发表将报文转发到下一跳路由器。
  • 经过 ISP 内路由协议和 ISP 之间的路由协议,最终 IP 报文到达 DNS 服务器。DNS 服务器查询缓存得到目标域名的 IP 地址,响应请求,发送 DNS reply 报文。
  • 客户端通过 DNS reply 报文得到目标域名的 IP 地址。

三、通过 HTTP 和 TCP 请求服务器

客户端创建 TCP Socket,发送 HTTP GET 报文给服务器。首先需要 TCP 三次握手。

  • 客户但创建一个 TCP SYN 报文段,目标端口为 80。TCP 报文封装为 IP 报文,目标地址为服务器 IP 地址。最后封装为链路层报文,目标 MAC 地址为网关路由器 MAC 地址。
  • 路由器转发包含 TCP SYN 的 IP 报文给服务器。经过 ISP 内路由协议和 ISP 之间的路由协议,最终 IP 报文到达服务器。
  • 服务器响应一个 TCP SYNACK 报文。
  • 客户端收到 TCP SYNACK 报文,发送应答报文。TCP 连接成功建立。
  • 客户端浏览器创建 HTTP GET 报文,将 HTTP 报文写入 Socket 中,HTTP GET 报文作为 TCP 报文段的负载,进而封装为 IP 报文,经过路由转发请求报文发送到服务器。
  • 服务器通过 Socket 收到 HTTP GET 请求报文,然后创建一个 HTTP response 报文,把网页内容放到 HTTP 响应报文的 body 中。把报文发送给 Socket。通过报文封装和路由转发,响应报文发送到客户端。
  • 客户端通过 Socket 收到收到 HTTP 响应报文,提取 HTTP 响应报文 body,显示网页。
0%