高级SQL特性
- 1约束
- 1.1主键
- 1.2外键
- 1.3唯一约束
- 1.4检查约束
- 2索引
- 3触发器
- 4数据库安全
1约束
正确的进行关系数据设计,需要一种方法保证只在表中插入合法数据。虽然可以在插入新行的时候进行检查(在另一个表上执行SELECt,以保证所有值合法并存在),但最好不要这么做,原因如下:
- 如果在客户端层面上实施数据库完整性规则,则每个客户端都要被迫实施这些规则,一定会有一些客户端不实施这些规则。
- 在执行UPDATE和DELETE操作时,也必须实施这些规则。
- 执行客户端检查是非常耗时的,而DBMS执行这些检查会相对高效。
约束(constraint):管理如何插入或处理数据库数据的规则
DBMS通过在数据库表上实施约束来实施引用完整性。大多数约束是在表定义中定义的,比如CREATE TABLE语句或ALTER TABLE语句。
1.1主键
主键是一种特殊的约束,用来保证一列(或者一组列)中的值是唯一的,而且永不改动。这方便了直接或交互的处理表中的行。
表中任意行满足以下条件都可以用于主键:
- 任意两行的主键值都不相同
- 每行都具有一个主键值(即列中不允许NULL值)
- 包含主键值的列从不修改或更新。
- 主键值不能重用,如果从表中删除某一行,其主键值不分配给新行。
一种定义主键的方法是创建它,如下所示:
CREATE TABLE Vendors
(
vend_id CHAR(10) NOT NULL PRIMARY KEY,
vend_name CHAR(50) NOT NULL,
vend_address CHAR(50) NULL,
vend_city CHAR(50) NULL,
vend_state CHAR(5) NULL,
vend_zip CHAR(10) NULL,
vend_country CHAR(50) NULL
);
分析:在此示例中给vend_id列定义添加关键字PRIMARY KEY,使其成为主键。
ALTER TABLE Vendors
ADD CONSTRAINT PRIMARY KEY (vend_id);
分析:这里定义相同的列为主键,但是使用的是CONSTRAINT(约束)语法。此语法也可以用于CREATE TABLE和ALTER TABLE语句。
1.2外键
外键是表中的一列,其值在另一个表的主键中。外键是保证引用完整性积极重要的一部分。
下面是定义外键的方法:
原来是没有外键的
添加外键
ALTER TABLE Orders1
ADD CONSTRAINT FK_Orders1_Customers
FOREIGN KEY (cust_id) REFERENCES Customers (cust_id);
分析:其中的表定义使用了REFERENCES关键字,它表示cust_id中的任何值都必须是Customers表的cust_id中的值。
1.3唯一约束
唯一约束保证一列(或者一组列)的数据是唯一的。它类似于主键,但存在以下重要区别。
- 表包含多个唯一约束,但每个表只允许一个主键。
- 唯一约束列可包含NULL值。
- 唯一约束列可修改和更新。
- 唯一约束列的值可重复使用。
- 与主键不一样,唯一约束不能用来定义外键。
这几条与主键相对应区别
唯一约束的语法类似于其他约束的语法。唯一约束既可以用UNIQUE关键I在表定义中定义,也可以用单独的CONSTRAINT定义。
1.4检查约束
检查约束用来保证一列(或者一组列)中的数据满足一组指定的条件。检查约束的常见用途有以下几点。
- 检查最小或最大值。例如,防止0个物品的订单(即使0是合法的数)。
- 指定范围。例如,保证发货日期大于等于今天的日期,但不超过今天起一年后的日期。
- 只允许特定的值。例如,在性别字段中只允许M或F。
换句话说,数据类型限制了列中可保存的数据的类型。检查约束在数据类型内又做了进一步的限制,这些限制及其重要,可以确保插入数据库的数据证实你想要的的数据。不需要依赖于客户端,应用程序或者用户来保证正确获取它,DBMS本身将会拒绝任何无效数据。
下面的列子对OrderItems表施加了检查约束,它保证所有物品的数量大于0。
输入
CREATE TABLE OrderItems
(
order_num INTEGER NOT NULL,
order_item INTEGER NOT NULL,
prod_id CHAR(10) NOT NULL,
quantity INTEGER NOT NULL CHECK (quantity > 0),
item_price MONEY NOT NULL
);
分析:利用这个约束,任何插入(或更新)的行都会被检查,保证quantity大于0。
检查名为gender的列只包含M或F,可编写如下的ALTER TABLE语句:
输入
ADD CONSTRAINT CHECK (gender LIKE '[MF]')
2索引
索引用来排序数据以加快搜索和排序操作的速度。
使用索引的原因是:恰当的排序。
主键数据是排序的,这是DBMS的工作。因此,按住键检索特定行总是一种快速有效的操作。但是搜索其他列中的值通常效率不高。
在创建索引前,应该记住以下内容。
- 索引改善检索操作的性能,但降低了数据插入、修改和删除的性能。在执行这些操作时,DBMS必须动态地更新索引。
- 索引数据可能要占用大量的存储空间。
- 并非所有数据都适合做索引。取值不多的数据(如州)不如具有更多可能值得数据(如姓或名),能通过索引得到那么多的好处。
- 索引用于数据过滤和数据排序。如果你经常以莫衷疼的顺序排序数据,则该数据可能适合做索引。
- 可以在索引中定义多个类(例如,州加上城市)。这样的索引仅在以州加城市的顺序排序时有用。如果想按城市排序,则这种所以因没有用处。
没有严格的规则要求什么应该索引,何时索引。大多数DBMS提供了可用来确定索引效率的使用程序,应该经常使用这些使用程序。
索引用CREATE INDEX语句创建(不同DBMS创建索引的语句变化很大)。
下面的语句在Products表的产品列上创建一个简单的索引。
输入
CREATE INDEX prod_name_ind
ON Products (prod_name);
查看索引
mysql> show index from products;
+----------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+----------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| products | 0 | PRIMARY | 1 | prod_id | A | 9 | NULL | NULL | | BTREE | | | YES | NULL |
| products | 1 | FK_Products_Vendors | 1 | vend_id | A | 3 | NULL | NULL | | BTREE | | | YES | NULL |
| products | 1 | prod_name_ind | 1 | prod_name | A | 9 | NULL | NULL | | BTREE | | | YES | NULL |
+----------+------------+---------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
分析:索引必须唯一命名。这里的索引名prod_name_ind在关键字CREATE INDEX之后定义。ON用来指定被索引的表。而索引中包含的列(此例中仅有一列)在表名后的圆括号给出。
3触发器
触发器是特殊的存储过程,它在特定的数据库活动发生时自动执行。触发器可以与特定表上的INSERT、UPDATe和DELETE操作(或组合)相关联。
与存储过程不一样(存储过程只是简单的存储SQL语句),触发器与单个的表相关联。与Order表上的INSERT操作相关联的触发器只在Orders表汇总插入行时执行。类似地,Customers表上对的INSERT和UPDATE操作的触发器只在表上出现这些操作时执行。
触发器内的代码具有以下数据的访问权:
INSERT操作中的所有新数据;
UPDATE操作中的所有新数据和旧数据;
DELETE操作中删除的数据。
根据所使用的DBMS的不同,触发器可在特定操作执行之前或之后执行。
下面是一些常见用途。
- 保证数据一致。例如,在INSERT或UPDATE操作汇总将所有州名转换为大写。
- 基于某个表的变动在其他表上执行活动。例如,每当更新或者删除一行时将审计跟踪记录写入某个日志表。
- 进行额外的验证并根据需要回退数据。
- 计算计算列的值或更新时间戳。
下面创建一个触发器,它对所有INSERT和UPDATE操作,将Customer表中的cust_name列转换为大写。
这是本例的MySQL版本
输入
CREATE TRIGGER customer_state BEFORE INSERT ON customers FOR EACH ROW
BEGIN
UPDATE Customers
SET cust_state = Upper( cust_state )
WHERe
Customers.cust_id = INSERT.cust_id;
END;
查看触发器的信息 使用SHOW TRIGGERS;
mysql> SHOW TRIGGERS;
+----------------+--------+-----------+----------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+
| Trigger | Event | Table | Statement | Timing | Created | sql_mode | Definer | character_set_client | collation_connection | Database Collation |
+----------------+--------+-----------+----------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+
| customer_state | INSERT | customers | BEGIN
UPDATE Customers
SET cust_state = Upper(cust_state)
WHERe Customers.cust_id = INSERT.cust_id;
END | BEFORE | 2020-05-11 12:35:50.24 | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION | root@localhost | utf8mb4 | utf8mb4_0900_ai_ci | utf8mb4_0900_ai_ci |
+----------------+--------+-----------+----------------------------------------------------------------------------------------------------------------+--------+------------------------+-----------------------------------------------------------------------------------------------------------------------+----------------+----------------------+----------------------+--------------------+
使用SELECT * FROM information_schema.triggers \G ;语句查看触发器的详细信息
mysql> SELECt * FROM information_schema.triggers \G ;
*************************** 1. row ***************************
TRIGGER_CATALOG: def
TRIGGER_SCHEMA: sys
TRIGGER_NAME: sys_config_insert_set_user
EVENT_MANIPULATION: INSERT
EVENT_OBJECT_CATALOG: def
EVENT_OBJECT_SCHEMA: sys
EVENT_OBJECT_TABLE: sys_config
ACTION_ORDER: 1
ACTION_CONDITION: NULL
ACTION_STATEMENT: BEGIN
IF @sys.ignore_sys_config_triggers != true AND NEW.set_by IS NULL THEN
SET NEW.set_by = USER();
END IF;
END
ACTION_ORIENTATION: ROW
ACTION_TIMING: BEFORE
ACTION_REFERENCE_OLD_TABLE: NULL
ACTION_REFERENCE_NEW_TABLE: NULL
ACTION_REFERENCE_OLD_ROW: OLD
ACTION_REFERENCE_NEW_ROW: NEW
CREATED: 2020-05-03 08:57:27.57
SQL_MODE: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
DEFINER: mysql.sys@localhost
CHARACTER_SET_CLIENT: utf8mb4
COLLATION_CONNECTION: utf8mb4_0900_ai_ci
DATABASE_COLLATION: utf8mb4_0900_ai_ci
*************************** 2. row ***************************
TRIGGER_CATALOG: def
TRIGGER_SCHEMA: sys
TRIGGER_NAME: sys_config_update_set_user
EVENT_MANIPULATION: UPDATE
EVENT_OBJECT_CATALOG: def
EVENT_OBJECT_SCHEMA: sys
EVENT_OBJECT_TABLE: sys_config
ACTION_ORDER: 1
ACTION_CONDITION: NULL
ACTION_STATEMENT: BEGIN
IF @sys.ignore_sys_config_triggers != true AND NEW.set_by IS NULL THEN
SET NEW.set_by = USER();
END IF;
END
ACTION_ORIENTATION: ROW
ACTION_TIMING: BEFORE
ACTION_REFERENCE_OLD_TABLE: NULL
ACTION_REFERENCE_NEW_TABLE: NULL
ACTION_REFERENCE_OLD_ROW: OLD
ACTION_REFERENCE_NEW_ROW: NEW
CREATED: 2020-05-03 08:57:27.75
SQL_MODE: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
DEFINER: mysql.sys@localhost
CHARACTER_SET_CLIENT: utf8mb4
COLLATION_CONNECTION: utf8mb4_0900_ai_ci
DATABASE_COLLATION: utf8mb4_0900_ai_ci
*************************** 3. row ***************************
TRIGGER_CATALOG: def
TRIGGER_SCHEMA: data
TRIGGER_NAME: customer_state
EVENT_MANIPULATION: INSERT
EVENT_OBJECT_CATALOG: def
EVENT_OBJECT_SCHEMA: data
EVENT_OBJECT_TABLE: customers
ACTION_ORDER: 1
ACTION_CONDITION: NULL
ACTION_STATEMENT: BEGIN
UPDATE Customers
SET cust_state = Upper(cust_state)
WHERe Customers.cust_id = INSERT.cust_id;
END
ACTION_ORIENTATION: ROW
ACTION_TIMING: BEFORE
ACTION_REFERENCE_OLD_TABLE: NULL
ACTION_REFERENCE_NEW_TABLE: NULL
ACTION_REFERENCE_OLD_ROW: OLD
ACTION_REFERENCE_NEW_ROW: NEW
CREATED: 2020-05-11 12:35:50.24
SQL_MODE: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
DEFINER: root@localhost
CHARACTER_SET_CLIENT: utf8mb4
COLLATION_CONNECTION: utf8mb4_0900_ai_ci
DATABASE_COLLATION: utf8mb4_0900_ai_ci
3 rows in set (0.00 sec)
查看指定的某一个触发器的信息,比如查询我的触发器customer_state,使用SELECT * FROM information_schema.triggers WHERe TRIGGER_NAME=‘customer_state’ \G;语句进行查询
mysql> SELECt * FROM information_schema.triggers WHERe TRIGGER_NAME='customer_state' \G;
*************************** 1. row ***************************
TRIGGER_CATALOG: def
TRIGGER_SCHEMA: data
TRIGGER_NAME: customer_state
EVENT_MANIPULATION: INSERT
EVENT_OBJECT_CATALOG: def
EVENT_OBJECT_SCHEMA: data
EVENT_OBJECT_TABLE: customers
ACTION_ORDER: 1
ACTION_CONDITION: NULL
ACTION_STATEMENT: BEGIN
UPDATE Customers
SET cust_state = Upper(cust_state)
WHERe Customers.cust_id = INSERT.cust_id;
END
ACTION_ORIENTATION: ROW
ACTION_TIMING: BEFORE
ACTION_REFERENCE_OLD_TABLE: NULL
ACTION_REFERENCE_NEW_TABLE: NULL
ACTION_REFERENCE_OLD_ROW: OLD
ACTION_REFERENCE_NEW_ROW: NEW
CREATED: 2020-05-11 12:35:50.24
SQL_MODE: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
DEFINER: root@localhost
CHARACTER_SET_CLIENT: utf8mb4
COLLATION_CONNECTION: utf8mb4_0900_ai_ci
DATABASE_COLLATION: utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
4数据库安全
对于组织来说,没有什么比它的数据更重要了,因此应该保护这些数据,使其不被偷到或任意浏览。
需要保护的操作有:
- 对数据库管理功能(创建表、更改或删除已存在的表等)的访问;
- 对特定数据库或表的访问;
- 访问的类型(只读、对特定列的访问等);
- 仅通过视图或存储过程对表进行访问;
- 创建多层次的安全措施,从而允许多种基于登录的反问和控制;
- 限制管理用户账号的能力;
安全性使用SQL的GRANT和REVOKE语句来管理,不过大多数DBMS提供了交互式的管理使用程序,这些使用程序在内部使用GRANT和REVOKE语句。