SQL学习指南——索引和约束

索引

向数据表中插入行时,数据库服务器并不会将数据放到数据表中的特定位置。例如,要向customer数据表添加一行,服务器不会依据customer_id列的数值顺序或者last_name列的字母顺序放置该行,而只是简单地将数据存放在文件中的下一个可用位置(服务器为每个数据表预留了一系列可用空间)。因此,当查询customer数据表时,服务器需要检查数据表中的每一行才能完成查询。例如,对于下面的查询:

bash 复制代码
select first_name,last_name from customer where last_name like 'Y%';

要想找出所有姓氏以Y开头的客户,服务器必须访问customer数据表中的每一行并检查last_name列的内容,如果姓氏以Y开头,则将该行添加到结果集,这种访问形式被称为表扫描

尽管这种方法对于仅有3行得数据表的效果的确不错,但想象一下,如果数据表中包含300万行,那么查询需要花多长时间?对于大于3且小于300万的行数,如果没有额外的帮助,服务器无法在合理的时间内响应查询,能够帮上忙的就是customer数据表上的一个或多个索引。

索引是查找资源内特定项的一种机制。例如,每本科技出版物的结尾都配有索引以供读者定位书中的特定单词或者短语。索引依字母顺序列出这些单词或者短语,使读者能够快速定位到索引中的特定字母,找到所需的条目,然后翻到相应页或是单词/短语可能出现的那些页

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

创建索引

回到customer数据表,现在决定为email列添加索引,以提高指定该列值的查询以及指定了客户电子邮件地址的update或delete操作的查询速度。向MySQL数据库添加索引的操作如下:

bash 复制代码
ALTER TABLE customer ADD INDEX idx_email(email);

该语句为customer.email列创建了一个索引(准确地说是B树索引),而且将该索引命名为idx_email。有了索引后,如果索引有利于改善查询,查询优化器就选择使用索引,如果数据表中的索引不止一个,那么优化器必须确定对于特定的SQL语句哪个索引是最佳的

MySQL将索引视为数据表的可选组件,这就是在早期版本中可以使用alter table命令添加或删除索引的原因。包括SQL Server和Oracle Database在内的其他数据库服务器则将索引视为独立的模式对象。对于SQL Server和Oracle Database,可以使用create index命令生成索引:

bash 复制代码
CREATE INDEX dept_name_idx on department(name);

MySQL5.0版也提供了create index,但该命令被映射到alter table 命令,仍然必须使用alter table命令创建主键索引

所有的数据库服务器都允许查看可用的索引,MySQL用户可以使用show命令查看特定数据表的所有索引:

bash 复制代码
show index from customer \G

如果创建索引之后觉得索引用处不大,可以将其删除

bash 复制代码
ALTER TABLE customer DROP INDEX idx_email;

SQL Server和Oracle Database用户必须使用drop index命令来删除索引

bash 复制代码
drop index idx_email;(Oracle)
drop index idx_email on customer;(SQL Server)

MySQL目前也支持drop index命令,不过同样是被映射到alter table命令

唯一索引

设计数据库时,考虑好哪些列可以,哪些列不可以出现重复数据是一件很重要的事情。例如,customer数据表中,两个不同的客户不能有相同的电子邮件地址,可以通过customer.email列创建唯一索引(unique index)来强制避免出现重复值

唯一索引身兼数职:除了提供普通索引所能提供的所有便利,还作为一种避免索引列出现重复值的机制。只要有行插入或是索引列被修改,数据库服务器就会检查唯一索引,以查看该值是否已经在数据表中的其他行存在。创建customer.email列的唯一索引的方法如下:

bash 复制代码
ALTER TABLE customer ADD UNIQUE idx_email (email);

SQL Server和Oracle Database用户只需在创建索引时加入unique关键字:

bash 复制代码
CREATE UNIQUE INDEX idx_email on customer(email);

建立好索引之后,如果尝试添加的新客户所拥有的电子邮件地址已经存在,则会接收到错误消息

不必为主键列创建索引,因为服务器已经检查过主键值的唯一性。如果有必要,可以为同一个数据表创建多个唯一索引

多列索引

除了单列索引,还可以创建跨多列的索引。如果想要根据姓氏和名字搜索客户,可以为这两列一起创建索引

bash 复制代码
ALTER TABLE customer ADD INDEX idx_full_name (last_name,first_name);

该索引适用于两种查询:一种指定了姓氏和名字,另一种只指定了姓氏。但该索引不适用于只指定客户名字的查询。要想知道其原因,可以考虑一下你是如何查找某个人的电话号码的,如果知道此人的姓氏和名字,就可以用电话簿快速查找到电话号码,因为电话簿是先依据姓氏顺序,再依据名字顺序组织的,如果只知道此人的名字,就需要浏览电话簿中的每个条目,以找出有指定名字的所有条目

因此,在创建多列索引时,必须仔细考虑哪一列在前,哪一列在后,这样才能使索引尽可能地发挥作用。记住,如果需要确保充分的响应时间,完全可以基于不同顺序为列的同一集合创建多个索引

索引类型

索引是一种功能强大的工具,但因为存在很多不同类型的数据,单一的索引策略并不总能适用。下面描述了不同服务器提供的各种类型的索引

B树索引

到目前为止,所展示的所有索引都属于平衡树索引(balanced-tree index),其更常见的名称是B树索引(B-tree index)。MySQL、Oracle Database和SQL Server均默认采用B树索引,除非明确要求使用其他类型的索引。B树索引按照树的形式组织,拥有一级或多级分支节点,分支节点又指向单级的叶子节点。分支节点用于遍历树,叶子节点则保存实际的值和位置信息。

对customer数据表插入、更新、删除行时,服务器会尽力保持树的平衡,以避免出现根节点的某一侧比另一侧拥有过多的分支节点/叶子节点。服务器通过添加或删除分支节点使值分布得更加均匀,甚至可以添加或删除整个一级的分支节点。通过保持树的平衡,服务器就能够快速遍历叶子节点以找到所需的值,而不用再经过多级分支节点。

位图索引

虽然B树索引擅长处理包含大量不同值的列,比如客户的姓氏/名字,但是在处理仅允许少量不同值得列时就显得不便了。例如,为了快速检索所有的活跃客户或非活跃客户,你可能希望为customer.active列创建索引,但是,由于只有2个不同的值(1表示活跃客户,0表示非活跃客户),而且活跃客户的数量要多得多,因此随着客户数量得不断增长,很难维持B树索引的平衡。

对于那些只包含少量值却占据了大量行的列(所谓的低基数数据),需要采用不同的索引策略。为了更有效地处理这个问题,Oracle Database引入了位图索引(bitmap index),其为存储在列中的每个值生成一个位图。如果为customer.active列创建位图索引,则该索引维护两个位图:一个用于值0,另一个用于值1。当编写查询以检索所有的非活跃客户时,数据库服务器遍历0位图,并快速检索所需的行。

对于低基数数据而言,位图索引是一种友好且紧凑的索引解决方案,但是如果列中存储的值的数量相较于行数攀升得过高(所谓的高基数数据),这种索引策略就不适合了,因为服务器需要维护太多的位图。例如,你永远不会选择为主键列创建位图索引,因为这代表了可能的最大基数(每行的值都不一样)Oracle用户只需通过在create index语句中加入bitmap关键字就可以生成位图索引:

bash 复制代码
create bitmap index idx_active on customer(active);

位图索引通常用于数据仓库环境,其中大量数据通常在包含相对较少值的列(例如销售季度、地理区域、产品、销售人员)上进行索引

文本索引

如果数据库中存储了文档,那么可能需要允许用户在文档中搜索单词或者短语。你当然不希望在每次请求搜索时服务器都要浏览每份文档并扫描所需的文本,传统的索引策略对这种情况并不适用。为此,MySQL,SQL Server和Oracle Database为文档提供了专用的索引和搜索机制,其中,MySQL和SQL Server提供的是全文索引(full-text index),Oracle Database提供了一套称为Oracle Text的强大工具集。文档搜索的专业性很强

如何使用索引

服务器通常首先利用索引快速定位特定数据表中的行,之后再访问相关数据表以提取用户请求的其他信息

可以使用explain语句让服务器显示查询的执行计划,而不是执行查询

bash 复制代码
explain select customer_id,first_name,last_name from customer where first_name like 'S%' and last_name like 'P%' \G

索引的不足

一般来说,索引不能太多,也不能太少

每个索引其实都是一个数据表,因此,每次对数据表添加或者删除行时,数据表中的所有索引都必须被相应修改,更新行时,受影响的那些列的索引也需要被修改。因此,索引越多,服务器就需要做越多的工作来保持所有模式对象都处于最新状态,这会使服务器的运行速度减慢

约束

约束就是施加于数据表中一列或多列的限制。约束包括下列几种不同的类型

主键约束:标识一列或多列,保证其值在数据表中的唯一性

外键约束:限制一列或多列只能包含其他数据表的主键列中的值(如果已建立update cascade或delete cascade规则,也可以限制其他数据表中的可用值)

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

检查约束:限制列的可用值范围

如果没有约束,数据库的一致性就会存疑。例如,如果服务器允许在不修改rental数据表中相同客户ID的情况下修改customer数据表中的客户ID,那么最终结果就是租借数据不再指向有效的客户记录(所谓的孤儿行)。但是,有了主键约束和外键约束,如果试图修改或删除被其他数据表引用的数据,服务器要么引发错误,要么将这些改动传播到 其他数据表。

如果想在MySQL服务器中使用外键约束,数据表的存储引擎必须是InnoDB