写点什么

「SQL 数据分析系列」13. 索引和约束

发布于: 2 小时前
「SQL数据分析系列」13. 索引和约束

写在前面:

大家好,我是强哥,一个热爱分享的技术狂。目前已有 12 年大数据与 AI 相关项目经验, 10 年推荐系统研究及实践经验。平时喜欢读书、暴走和写作。

业余时间专注于输出大数据、AI 等相关文章,目前已经输出了 40 万字的推荐系统系列精品文章,强哥的畅销书「构建企业级推荐系统:算法、工程实现与案例分析」已经出版,需要提升可以私信我呀。如果这些文章能够帮助你快速入门,实现职场升职加薪,我将不胜欢喜。

想要获得更多免费学习资料或内推信息,一定要看到文章最后喔。

内推信息

如果你正在看相关的招聘信息,请加我微信:liuq4360,我这里有很多内推资源等着你,欢迎投递简历。

免费学习资料

如果你想获得更多免费的学习资料,请关注同名公众号【数据与智能】,输入“资料”即可!

学习交流群

如果你想找到组织,和大家一起学习成长,交流经验,也可以加入我们的学习成长群。群里有老司机带你飞,另有小哥哥、小姐姐等你来勾搭!加小姐姐微信:epsila,她会带你入群。


2020 Learning SQL-中译

第十三章索引和约束

由于本书着重介绍编程技术,所以前十二章集中介绍了 SQL 语言的基础知识,你可以运用这些知识编写强大的 select、insert、update 和 delete 语句。但是,还有些数据库功能会间接影响你编写的代码。本章主要讨论其中两个功能:索引和约束。

索引

在表中插入一行数据时,数据库服务器不会试图将数据放在表中任何一个特定的地方。例如,如果向 customer 表中添加一行,那么服务器不会按照 customer_id 列的数字顺序存放该行,也不会按照 last_name 列的字母顺序存放该行。相反,服务器只是简单地将数据存放在文件中的下一个可用位置(服务器为每个表维护一系列可用空间)。因此,在查询 customer 表时,服务器需要检查表的每一行来完成查询。例如,发出查询如下:

mysql> SELECT first_name, last_name 

-> FROM customer 

-> WHERE last_name LIKE 'Y%';

+------------+-----------+

| first_name | last_name |

+------------+-----------+

| LUIS | YANEZ |

| MARVIN | YEE |

| CYNTHIA | YOUNG |

+------------+-----------+

rows in set (0.09 sec)

要查找姓氏以 Y 开头的所有客户,服务器必须访问 customer 表中的每一行并检查相应 last_name 列的内容,如果姓氏以 Y 开头,则将该行添加到结果集中。这种类型的访问称为表扫描(table scan)。

虽然这种方法对于只有三行的表很有效,但是想象一下,如果表包含 300 万行数据,那么要多久才能完成一次查询。对于大于 3 而小于 300 万行规模的表,在没有其他帮助的情况下,服务器也不能在合适的时间内完成一次查询。所以我们需要额外帮助来提升查询效率,它就是 customer 表上的一个或多个索引(index)。

即使你可能从没听说过数据库索引,但你也肯定知道索引是什么(例如本书就有一个)。索引是在资源中查找特定项的一种机制。例如,每个科技出版物的末尾都有一个索引,以供定位指定的单词或短语。索引按字母顺序列出这些单词和短语,使得读者快速定位到索引中的特定字母,找到所需的条目,然后找到指定单词或短语所在的页面。

和人们在出版物中使用索引查找单词的方式相同,数据库服务器亦使用索引定位表中的行。索引与普通的数据表不同,它是一种以特定顺序保存的专用表。然而,索引并不包括相关实体的所有数据,而只包含用于在数据表中定位行的列,以及描述这些行的物理位置的信息。因此,索引的作用就是方便检索表中行和列的子集,而无需检查表中的每一行。

创建索引

再回到 customer 表,你可能打算在 email 列上添加索引,以加速所有为该列定位值的查询,或是与客户电子邮件地址有关的任何 update 或 delete 操作。下面介绍如何在 MySQL 数据库中添加该索引:

mysql> ALTER TABLE customer 

-> ADD INDEX idx_email (email);

Query OK, 0 rows affected (1.87 sec)

Records: 0 Duplicates: 0 Warnings: 0

此语句在 customer.email 列创建了索引(其实是一个 B 树,稍后再讨论)并命名为 idx_email。有了索引,倘若索引有利于查询,那么查询优化器(我们在第三章中讨论过)可以选择索引。如果一个表上有多个索引,那么优化器必须决定对于特定的 SQL 语句而言哪个索引最有利。

注意:MySQL 将索引视为表的可选部件,这就是为什么在早期版本中你需要使用 alter table 命令添加或删除索引的原因。其他数据库服务器(包括 SQL Server 和 Oracle Database)将索引视为独立的模式对象。因此,对于 SQL Server 和 Oracle,你需要使用 create index 命令生成索引,如下所示:


CREATE INDEX idx_email

ON customer (email);

从 5.0 版本的 MySQL 开始,可以使用 create index 命令创建索引(尽管它被映射到了 alter table 命令)。但对于主键,仍要使用 alter table 命令来创建主键索引。

所有数据库服务器都允许查看可用索引。MySQL 用户可以使用 show 命令查看某个表上的所有索引,如下所示:

mysql> SHOW INDEX FROM customer \G;

*************************** 1. row *************************** 

Table: customer 

Non_unique: 0 

Key_name: PRIMARY 

Seq_in_index: 1 

Column_name: customer_id 

Collation: A 

Cardinality: 599 

Sub_part: NULL 

Packed: NULL 

Null: Index_type:

BTREE

...

*************************** 2. row *************************** 

Table: customer 

Non_unique: 1 

Key_name: idx_fk_store_id 

Seq_in_index: 1 

Column_name: store_id 

Collation: A 

Cardinality: 2

Sub_part: NULL

 Packed: NULL 

Null: Index_type:

BTREE

...

*************************** 3. row *************************** 

Table: customer 

Non_unique: 1 

Key_name: idx_fk_address_id 

Seq_in_index: 1 

Column_name: address_id  

Collation: A 

Cardinality: 599 

Sub_part: NULL 

Packed: NULL 

Null: Index_type:

BTREE

...

*************************** 4. row *************************** 

Table: customer 

Non_unique: 1 

Key_name: idx_last_name 

Seq_in_index: 1 

Column_name: last_name 

Collation: A 

Cardinality: 599 

Sub_part: NULL 

Packed: NULL 

Null: Index_type:

BTREE

...

*************************** 5. row *************************** 

Table: customer 

Non_unique: 1 

Key_name: idx_email 

Seq_in_index: 1 

Column_name: email 

Collation: A 

Cardinality: 599 

Sub_part: NULL 

Packed: NULL 

Null: YES Index_type:

BTREE

...

rows in set (0.06 sec)

输出显示 customer 表上有五个索引:一个是 customer_id 列的主键索引 PRIMARY,另外四个是 store_id、address_id、last_name 和 email 列的索引。这些索引是怎么来的呢?其实我先前就在 email 列上创建了索引,而其余的索引是作为示例 Sakila 数据库的一部分安装的。下面是用于创建表的语句:

CREATE TABLE customer ( 

customer_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,

store_id TINYINT UNSIGNED NOT NULL,

 first_name VARCHAR(45) NOT NULL,

last_name VARCHAR(45) NOT NULL,

email VARCHAR(50) DEFAULT NULL,

address_id SMALLINT UNSIGNED NOT NULL,

active BOOLEAN NOT NULL DEFAULT TRUE,

create_date DATETIME NOT NULL,

last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

PRIMARY KEY (customer_id),

KEY idx_fk_store_id (store_id),

KEY idx_fk_address_id (address_id),

KEY idx_last_name (last_name),

 ...

当创建表时,MySQL 服务器会自动为主键列生成一个索引,在本例中是 customer_id,并将索引命名为 PRIMARY,这是一种与主键约束一起使用的一类特殊索引,我会在本章后面介绍它。

如果创建索引后你后悔了,那么可以通过下面的方法删除索引:

mysql> ALTER TABLE customer

-> DROP INDEX idx_email;

Query OK, 0 rows affected (0.50 sec)

Records: 0 Duplicates: 0 Warnings: 0

注意:SQL Server 和 Oracle Database 用户必须使用 drop index 命令删除索引,如下所示:

DROP INDEX idx_email; (Oracle)

DROP INDEX idx_email ON customer; (SQL Server)

MySQL 现在也支持 drop index 命令,尽管它也映射到了 alter table 命令。

唯一索引

在设计数据库时,考虑哪些列允许、那些列不允许包含重复数据是很重要的事情。例如,在 customer 表中存在两个同名为 John Smith 的客户是允许的,因为每行都有一个不同的标识符(customer_id)、电子邮件和地址来帮助区分他们。但是,两个不同的客户是不能拥有相同的电子邮件地址的。可以通过在 customer.email 列上创建唯一索引(unique index)来限制出现相同的电子邮件地址。

唯一索引能起到多种作用,除了提供常规索引的所有优点外,它还充当一种用于禁止索引列中出现重复值的机制。每当插入一行或修改索引列时,数据库服务器都会检查唯一索引,以检查该值是否已存在于表中的另一行中。下面是为 customer.email 创建唯一索引的语句:

mysql> ALTER TABLE customer

 -> ADD UNIQUE idx_email (email);

Query OK, 0 rows affected (0.64 sec)

Records: 0 Duplicates: 0 Warnings: 0

注意:SQL Server 和 Oracle Database 的用户在创建索引时只需增加 unique 关键字,如下所示:

CREATE UNIQUE INDEX idx_email

ON customer (email);

有了唯一索引之后,如果你尝试添加一行电子邮件地址已在表中存在的新客户信息,服务器将抛出一个错误:

mysql> INSERT INTO customer

-> (store_id, first_name, last_name, email, address_id, active)

 -> VALUES

 -> (1,'ALAN','KAHN', 'ALAN.KAHN@sakilacustomer.org', 394, 1);

ERROR 1062 (23000): Duplicate entry 'ALAN.KAHN@sakilacustomer.org'

for key 'idx_email'

不需要为主键列构建唯一索引,因为服务器已经检查了主键的唯一性。但如果你觉得有必要的话,可以在同一个表上创建多个唯一索引。

多列索引

除了到目前为止演示过的单列索引外,还可以构建跨多列的索引。例如,如果要通过名字和姓氏来搜索客户,则可以一起为这两列创建索引,如下所示:

mysql> ALTER TABLE customer

 -> ADD INDEX idx_full_name (last_name, first_name);

Query OK, 0 rows affected (0.35 sec)

Records: 0 Duplicates: 0 Warnings: 0


此索引对于指定名字和姓氏的查询或是仅指定姓氏的查询非常有用,但是不太适合于仅指定客户名字的查询。想知道为什么,请考虑如何查找某人的电话号码的情况:倘若你可以知道此人的姓名,就可以通过电话簿快速查到号码,因为电话簿是先通过姓氏顺序排序,再通过名字顺序组织的。如果你只知道这个人的名字,那么你就必须浏览电话簿中的每个条目来查找具有指定名字的所有条目。

因此,在构建多列索引时,应该仔细考虑哪一列作为第一列,哪一列作为第二列,以此类推,以使索引尽可能发挥作用。但是请记住,如果你要保证足够的响应时间,那么也可以基于不同顺序为同一组列创建多列索引。

索引类型

索引是一种功能强大的工具,但由于存在许多不同类型的数据,单一的索引策略并不总是能满足需求。以下各节介绍各种服务器中使用的不同类型的索引。

B 树索引

迄今为止演示的所有索引都是平衡树索引(balanced-tree index),通常称为 B 树索引(B-tree index)。MySQL、Oracle Database 和 SQL Server 都默认使用 B 树索引,因此除非显式地指定使用其它类型的索引,否则你使用的就是 B 树索引。如你所料,B 树索引以树结构组织,其中有一个或多个分支节点(branch node)用于指向单级叶节点(leaf node)。分支节点用于遍历树,而叶节点用于保存实际值和位置信息。例如,基于 customer.last_name 列的 B 树索引如下图(13-1)所示:

如果要查询检索姓氏以 G 开头的所有客户,那么服务器将查看顶部的分支节点(称为根节点(root node))并顺指针顺序前进到姓氏以 A 到 M 开头的分支节点。此分支节点会依次将服务器导向包含姓氏以 G 开头的叶节点,然后服务器开始读取叶节点中的值直到遇到一个不以 G 开头的值(在本例中是 Hawthorne)。

当向 customer 表中插入、更新和删除行时,服务器将尽力保持树的平衡,避免出现根节点的一侧的分支节点/叶节点比另一侧多得多的情况。服务器可以添加或删除分支节点以重新分配值使得其分布更均匀,甚至还能添加或删除整个级别的分支节点来做到这一点。通过保持树的平衡,服务器可以在无需遍历多层分支节点的情况下快速遍历到叶节点以找到需要的值。

位图索引

尽管 B-tree 索引很适合处理包含许多不同值的列(例如客户的名字/姓氏),但在处理只有少量不同值的列时,它可能会变得非常笨拙。例如,你可以在 customer.active 创建索引列,以便可以快速检索到所有活跃或非活跃帐户。但是,由于只有两个不同的值(活跃存储为 1,非活跃存储为 0),而且活跃客户比非活跃客户要多得多,因此随着客户数量的增长,很难保持 B 树索引的平衡。

对于仅包含少量值却跨大量行(称为低基数(low-cardinality))的列,应该采用不同的索引策略。为了更有效地处理这种情况,Oracle Database 使用了位图索引(bitmap index),它为列中存储的每个值生成一个位图(bitmap)。如果要为 customer.active 列建立位图索引,索引将维护两个位图:一个用于值 0,另一个用于值 1。当查询所有非活跃客户时,数据库服务器可以遍历 0 位图并快速检索所需的行。

对于低基数数据,位图索引是一个友好而紧凑的索引解决方案,但是如果列中存储的值的数量相对行数太高时(称为高基数(high-cardinality)),这种索引策略就会失败,因为服务器需要维护太多的位图。例如,你永远不会在主键列上创建一个位图索引,因为这可能代表最高的基数(每行的值都不同)。

Oracle 用户只需将 bitmap 关键字添加到 create index 语句中就可以生成位图索引,如下所示:

CREATE BITMAP INDEX idx_active ON customer (active);

位图索引通常用于数据仓库环境,其中会有大量数据被索引,列的值也相对较少(例如,销售季度、地区、产品、销售员)。

文本索引

如果数据库中有存储文档,则可能需要允许用户在文档中查找单词或短语。你当然不希望服务器在每次请求查找时都打开所有文档并扫描所需的文本,但是传统的索引策略并不适用于这种情况。为了处理这种情况,MySQL、SQL Server 和 Oracle Database 包括专门的文档索引和搜索机制,其中,SQL Server 和 MySQL 包含所谓的全文索引,Oracle Database 包含一组称为 Oracle Text 的强大工具集。文档查找的专业性很强,所以我就不举例子说明了,但我希望你至少了解一些处理这种情况的方法。

如何使用索引

服务器通常使用索引以快速定位特定表中的行,然后服务器会访问关联表以提取用户请求的附加信息。考虑以下查询:

mysql> SELECT customer_id, first_name, last_name

 -> FROM customer

 -> WHERE first_name LIKE 'S%' AND last_name LIKE 'P%';

+-------------+------------+-----------+

| customer_id | first_name | last_name |

+-------------+------------+-----------+

| 84 | SARA | PERRY |

| 197 | SUE | PETERS |

| 167 | SALLY | PIERCE |

+-------------+------------+-----------+

rows in set (0.00 sec)

对于此查询,服务器可以采用以下策略之一:

• 扫描 customer 表中的所有行;

• 使用 last_name 列上的索引查找姓氏以 P 开头的所有客户,然后访问 customer 表的每一行,只查找姓氏以 S 开头的行;

• 使用 last_name 和 first_name 列的索引查找姓以 P 开头、名以 S 开头的所有客户。

第三种选择似乎是最佳选择,因为索引将在无需重新访问表的情况下产生结果集所需的所有行。但你怎么知道数据库会使用这三种策略中的哪一个呢?为了了解 MySQL 的查询优化器是如何执行查询的,我使用 explain 语句请求服务器显示查询的执行计划而不执行查询:

mysql> EXPLAIN

 -> SELECT customer_id, first_name, last_name

 -> FROM customer

 -> WHERE first_name LIKE 'S%' AND last_name LIKE 'P%' \G;

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: customer

partitions: NULL

type: range

possible_keys: idx_last_name,idx_full_name

key: idx_full_name

key_len: 274

ref: NULL

rows: 28

 filtered: 11.11

Extra: Using where; Using index

row in set, 1 warning (0.00 sec)

注意:每个数据库服务器都提供用于查看查询优化器是如何处理 SQL 语句的一些工具。SQL Server 要在 SQL 语句之前加上 show plan_text on 来查看执行计划。Oracle Database 包含 explain plan 语句,该语句把执行计划写入一个名为 plan_ table 的专用表中。

查看查询结果可以知道,possible_keys 列告诉你服务器可以选择使用 idx_last_name 或 idx_full_name 索引,而 key 列则告诉你服务器使用的是 idx_full_name 索引。此外,type 列告诉你将使用范围扫描,这意味着数据库服务器将在索引中查找一系列值,而不是检索一行。

注意:我刚才引导你完成了一个查询优化的示例。优化涉及查看 SQL 语句和确定服务器可用于执行该语句的资源。为了更高效地执行,你可以修改 SQL 语句,调整数据库资源,或者两种方法都采用。优化是一个很深入的话题,我强烈建议你阅读服务器的优化指南,或者选择一本好的调优书籍,这样你就能了解服务器所有可用的不同方法了。

索引的缺点

既然索引这么好,为什么不索引一切东西呢?好吧,要理解这一点的关键是要记住:每个索引都是一个表(一种特殊类型的表,但也是表)。因此,每次对表执行增加或删除操作时,都必须修改该表上的所有索引。当更新一行时,受影响的列的所有索引也需要修改。因此,索引越多,服务器为了保持所有模式对象处于最新状态所做的工作就越多,这往往会拖慢服务器的处理速度。

索引还需要磁盘空间,同时也需要管理员费心神去管理,因此最好的策略是:仅在有明确需求时添加索引。如果出于特殊目的(如每月维护例程)需要索引,则可以添加索引、运行例程,然后删除索引,直到下次需要再重复此过程。对数据仓库而言,在营业时间生成运行报表和特定查询时,索引至关重要,但当数据在一夜之间被加载到仓库中时就会出现问题,所以通常的做法是在加载数据之前删除索引,然后在仓库开放营业之前重新创建它们。

一般来说,你应该尽量避免索引太多和太少的情况。如果不确定应该有多少索引,可以默认使用以下策略:

• 确保所有主键列都已被索引(大多数服务器在创建主键约束时会自动创建唯一索引)。对于多列主键,请考虑为主键列的子集构建附加索引,或是以与主键约束定义不同的顺序为所有主键列构建索引;

• 为外键约束所引用的所有列构建索引。请记住,删除父级时,服务器会检查以确保没有子行存在,因此必须发出查询以搜索列中的特定值。如果列上没有索引,则服务器必须扫描整个表;

• 索引被频繁检索的列。大多数日期列以及短字符串列(2 到 50 个字符)都是很好的候选列。

构建完一套初始索引后,请尽可能捕获表中的真实查询,查看服务器的执行计划,并修改索引策略以满足最常见的访问路径。

约束

约束是一种简单地强加于表中一列或多列的限制。有几种不同类型的约束,包括:

• 主键约束(Primary key constraints)

 标识表中保证唯一性的一列或多列。

• 外键约束(Foreign key constraints)

 限制一列或多列中的值必须被包含在另一个表的主键列中(如果建立了 update cascade 或 delete cascade 规则,还可以限制其他表中的可用值,这两种规则叫级联更新或级联删除)。

• 唯一约束(Unique constraints)

 限制一列或多列的值,保证其在表中的唯一性(主键约束是一类特殊的唯一约束)。

• 检查约束(Check constraints)

 限制一列的可用值。

如果没有约束,那么难以保证数据库的一致性。例如,如果服务器允许更改 customer 表中的客户 ID,而不更改 rental 表中相同的客户 ID,那么就会得到不再指向有效客户记录的租赁数据(称为孤儿行(orphaned rows))。但如果有了主键约束和外键约束,任何视图修改或删除被其他表引用的数据时都会要么抛出一个错误,要么将这些改变传播到其他表(稍后将详细介绍这点)。

注意:如果你想在 MySQL 服务器上使用外键约束,那么表的存储引擎必须是 InnoDB。

创建约束

约束通常与关联表通过 create table 语句同时创建。下面是 Sakila 示例数据库的模式生成脚本示例:

CREATE TABLE customer ( 

customer_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, 

store_id TINYINT UNSIGNED NOT NULL

first_name VARCHAR(45) NOT NULL

last_name VARCHAR(45) NOT NULL

email VARCHAR(50) DEFAULT NULL

address_id SMALLINT UNSIGNED NOT NULL

active BOOLEAN NOT NULL DEFAULT TRUE, create_date DATETIME NOT NULL

last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP  ON UPDATE CURRENT_TIMESTAMP, 

PRIMARY KEY (customer_id), 

KEY idx_fk_store_id (store_id), 

KEY idx_fk_address_id (address_id), 

KEY idx_last_name (last_name), 

CONSTRAINT fk_customer_address

FOREIGN KEY (address_id)  

REFERENCES address (address_id) ON DELETE RESTRICT ON UPDATE CASCADECONSTRAINT fk_customer_store FOREIGN KEY (store_id)

 REFERENCES store (store_id)

ON DELETE RESTRICT ON UPDATE CASCADE)ENGINE=InnoDB

DEFAULT CHARSET=utf8;

customer 表包含三个约束:一个指定 customer_id 列作为表的主键,另外两个约束指定 address_id 和 store_id 列分别作为 address 和 store 表的外键。其实你也可以创建不带外键约束的 customer 表,然后通过 alter table 语句添加外键约束:

ALTER TABLE customer

ADD CONSTRAINT fk_customer_address

FOREIGN KEY (address_id)

REFERENCES address (address_id) ON DELETE RESTRICT ON UPDATE CASCADE;ALTER TABLE customer

ADD CONSTRAINT fk_customer_store

FOREIGN KEY (store_id)

REFERENCES store (store_id)

ON DELETE RESTRICT ON UPDATE CASCADE;

这两个语句都包含 on 子句:

• on delete restrict,如果在子表(customer)所引用的父表(address 或 store)中删除一行,服务器将抛出错误;

• on delete cascade,服务器会将父表(address 或 store)主键值的更改传播到子表(customer)。

当从父表中删除行时,on delete restrict 子句可以防止生成孤儿行。为了说明这一点,下面在 address 表中选取一行,并显示 address 和 customer 表中共享此值的数据:

mysql> SELECT c.first_name, c.last_name, c.address_id, a.address 

-> FROM customer c 

-> INNER JOIN address a 

-> ON c.address_id = a.address_id

 -> WHERE a.address_id = 123;

+------------+-----------+------------+----------------------------------+

| first_name | last_name | address_id | address |

+------------+-----------+------------+----------------------------------+

| SHERRY | MARSHALL | 123 | 1987 Coacalco de Berriozbal Loop |

+------------+-----------+------------+----------------------------------+

row in set (0.00 sec)

结果显示,有一个客户行(对于 Sherry Marshall)的 address_id 列包含值 123。

如果尝试从父表(address)中删除此行,会发生以下情况:

mysql> DELETE FROM address WHERE address_id = 123;

ERROR 1451 (23000): Cannot delete or update a parent row:  

a foreign key constraint fails (`sakila`.`customer`,  

CONSTRAINT `fk_customer_address` FOREIGN KEY (`address_id`)

REFERENCES `address` (`address_id`)  

ON DELETE RESTRICT ON UPDATE CASCADE)

因为子表中至少有一行包含 address_id 列中的值 123,所以外键约束的 on delete restrict 子句导致语句失败。

当使用不同的策略更新父表中的主键值时,on update cascade 子句也可以防止出现孤儿行。下面修改 address.address_id 列的某个值:

mysql> UPDATE address 

-> SET address_id = 9999 

-> WHERE address_id = 123;

Query OK,

row affected (0.37 sec

Rows matched: 1 Changed: 1 Warnings: 0

语句成功执行,并且修改了一行。但是 Sherry Marshall 在 customer 表上的相关行发生了什么变化吗?它是否仍然指向不再存在的地址 ID 123?想知道事实如何,请再次运行上一个查询,但是用新值 9999 替换旧值 123:

mysql> SELECT c.first_name, c.last_name, c.address_id, a.address 

-> FROM customer c 

-> INNER JOIN address a 

-> ON c.address_id = a.address_id 

-> WHERE a.address_id = 9999;

+------------+-----------+------------+----------------------------------+

| first_name | last_name | address_id | address |

+------------+-----------+------------+----------------------------------+

| SHERRY | MARSHALL | 9999 | 1987 Coacalco de Berriozbal Loop |

+------------+-----------+------------+----------------------------------+

row in set (0.00 sec)

如你所见,返回的结果与以前相同(新的 addressID 值除外),这意味着 customer 表中的值 9999 已自动更新,这就是级联(cascade),它是用于防止产生孤儿行的第二种机制。

除了 restrict 和 cascade 之外,还可以使用 set null,当父表中的行被删除或更新时,它会在子表中将外键值设置为 null。在定义外键约束时,总共有六种不同的选策略可供选择:

• on delete restrict

• on delete cascade

• on delete set null

• on update restrict

• on update cascade

• on update set null

这些都是可选的,因此在定义外键约束时可以选择零个、一个或两个(一个用于删除,一个用于更新)策略。

最后,如果要删除主键或外键约束,可以再次使用 alter table 语句,只需用 drop 替换 add。虽然删除主键约束并不是很常见,但有时外键约束会在某些维护操作期间被删除,然后重新建立。

发布于: 2 小时前阅读数: 7
用户头像

还未添加个人签名 2018.05.14 加入

公众号【数据与智能】主理人,个人微信:liuq4360 12 年大数据与 AI相关项目经验, 10 年推荐系统研究及实践经验,目前已经输出了40万字的推荐系统系列精品文章,并有新书即将出版。

评论

发布
暂无评论
「SQL数据分析系列」13. 索引和约束