索引和外键

索引的作用


点击这里,看视频讲解,学习下面内容

假设,我们创建了 这样的表

CREATE TABLE customer2 (
  id int NOT NULL,
  username varchar(150)  NOT NULL,
  `password` varchar(128)  NOT NULL,
  realname varchar(30)  NOT NULL,
  coin int NOT NULL
) ;

而且,我们插入了上百万条数据。

当我们执行一条查询语句,比如下面这样

select * from customer2 where username='byhy233'

数据库服务接收到这样的SQL语句时如何,在数据库文件里面查找用户名为 byhy233 这条记录的呢?

它从表的开始第一条记录开始,看看 username的值是不是等于byhy233。 这样一条条的找下去,直到找到为止。

如果不幸这条记录存储在表的最后一条,那么数据库服务就要查完整个表,几百万条记录,直到找到。

显然这样是很慢的。


索引 ,就是专门解决这样的问题的。 索引的英文叫 index, 也有的叫 key, 都是一个意思。

如果我们经常要根据 username 的值 来查询记录, 我们可以给username这列 创建索引。

像这样:

 CREATE INDEX index_username ON customer2 (username)

这样,就给 customer2 这张表的 username 这列 添加一个名为 index_username 的索引。

索引,大家可以想象成一个目录(底层是一个叫B树的数据结构), 记录了每个username值对应的记录到底存在数据库文件什么位置。

创建这样的索引以后,再执行 下面这样的查询语句或者修改、删除语句

select * from customer2 where username='byhy233';

或者 

update customer2 set coin=100 where username='byhy233';

或者 

delete from customer2 where username='byhy233';

就不需要在数据表中依次一条条地查找, 而是 到index_username索引对应的目录数据结构中,直接查找 byhy233这些记录的位置。 然后就直接到 目录指定的位置,就可以发现要找的数据了。

这个过程,就像查字典一样, 要找一个字, 一页页的翻着找是很麻烦的,你可以在目录中找找到那个字后, 目录会告诉你 字在哪一页, 你就可以直接跳到那一页去即可。


可能有人要问, 如果记录很多,即使在index_username索引目录中找到 username 等于 byhy233 也很麻烦啊。

这个是有办法的,索引目录中,索引数据是有一定的顺序排列的, 我们可以假想一下,比如按照字母排序,要找的时候不需要一个个找过去,可以 二分法查找, 很快就可以找到。

了解了索引的原理之后,大家应该能体会到,创建索引也是有代价的。

首先,为了索引,要额外的存储空间来 存储索引目录。 就像字典目录前面的好几页都是目录内容。

其次, 如果有了索引,以后每次添加、删除数据,数据库服务可能需要更新索引。 这样会导致 更新数据时,性能的下降。

当一个表需要频繁的更新时,要谨慎的使用索引。

比如,我们要导入大量数据(上千万条数据)到表中,通常会先删除表的索引,导入数据结束后,再加上索引。


在 HeidiSQL中,我们可以这样查看表中的索引

image



我们也可以在创建表的时候,就指定索引,比如

CREATE TABLE customer3 (
  id int NOT NULL,
  username varchar(150)  NOT NULL,
  `password` varchar(128)  NOT NULL,
  realname varchar(30)  NOT NULL,
  coin int NOT NULL,
  INDEX (username)
) ;

这样,创建的索引名字 就叫 username , 和列名一致。


唯一索引


点击这里,看视频讲解,学习下面内容

有的时候, 我们的索引对应的列, 每条记录都应该是不同的。

比如用户的登录名,每个用户登录名都不应该相同。

我们可以在创建表的时候,这样指定唯一索引

CREATE TABLE customer4 (
  id int NOT NULL,
  username varchar(150)  NOT NULL,
  `password` varchar(128)  NOT NULL,
  realname varchar(30)  NOT NULL,
  coin int NOT NULL,
  UNIQUE KEY (username)
);

使用 UNIQUE KEY (username) 或者 UNIQUE (username) 都可以。

也可以直接在字段定义的地方指明,比如

CREATE TABLE customer4 (
  id int NOT NULL,
  username varchar(150)  NOT NULL  UNIQUE,
  `password` varchar(128)  NOT NULL,
  realname varchar(30)  NOT NULL,
  coin int NOT NULL,
) ;

也可以在这样,给已经存在的表添加 唯一索引

CREATE UNIQUE INDEX username ON customer4(username);


username有唯一索引后,如果我们给表添加一条记录时, username已经存在,就会报错,如下

Duplicate entry 'cus01' for key 'username'


如果有这样一张表

CREATE TABLE customer5 (
  id int NOT NULL,
  username varchar(150)  NOT NULL,
  nickname  varchar(150),
  UNIQUE KEY (username),
  UNIQUE KEY (nickname)
) ;

唯一索引对应的列 nickname,没有加 NOT NULL 修饰, 说明,添加记录时,这列的内容可以是空的。

多条记录 nickname 都是空的,唯一索引是允许的。 不算值重复。


主键


点击这里,看视频讲解,学习下面内容

大家还记得,前面我们创建表的时候,有 PRIMARY KEY

CREATE TABLE customer1 (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(150)  NOT NULL,
  `level` int NOT NULL,
  `coin` int NOT NULL,
  PRIMARY KEY (id)
) ;

PRIMARY KEY 就是 主键 , 也可以这样指定

CREATE TABLE customer1 (
  `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `username` varchar(150)  NOT NULL,
  `level` int NOT NULL,
  `coin` int NOT NULL
) ;


主键其实也是一种索引, 我们也称之为 主键索引。

由于主键索引的特殊存储特征,也被称之为 聚类索引 clustered index。

一张表的记录是按照主键索引的值,排序存储的,主键值相近的记录 ,比如 id为 1000 、1001、1002 存储位置也是靠在一起的。

这样,对于如下的sql语句查询,可以大大的提升查询的效率

select * from customer1 where id > 1000 and id < 2000


主键对应列 的记录值 也 不能相同,这点和 唯一索引 很像。

主键PRIMARY KEY 和 唯一索引 UNIQUE KEY的区别如下:

一个表最多只能有一个PRIMARY KEY约束,但它可以包含任意数量的UNIQUE KEY约束。

属于PRIMARY KEY的列必须定义为NOT NULL。 对于属于UNIQUE KEY约束的列,这不是必需的。

另一个细微差别是 唯一索引 和 普通索引 可以自定义名字, 而主键只能用PRIMARY KEY作为名字。


多列索引


点击这里,看视频讲解,学习下面内容

前面,我们给出例子中, 普通索引、唯一索引、主键索引 都是针对某 一个 列的。

其实 这3种索引都可以 针对 某几列 。

比如

CREATE TABLE customer7 (
  id       int NOT NULL,
  username varchar(150)  NOT NULL,
  age      int  NOT NULL,
  coin     int NOT NULL,
  INDEX (age,coin)
) ;


CREATE TABLE customer8 (
  id       int NOT NULL,
  username varchar(150)  NOT NULL,
  age      int  NOT NULL,
  coin     int NOT NULL,
  UNIQUE  (username,age)
) ;


CREATE TABLE customer9 (
  id       int NOT NULL,
  username varchar(150)  NOT NULL,
  age      int  NOT NULL,
  coin     int NOT NULL,
  PRIMARY KEY  (id,username)
) ;

这种多列的索引,主要用在 where 从句经常是 针对多列内容的,比如

我们的业务有大量的针对 coin 和 age的 查询,修改等

select * from customer1 where age > 10 and coin = 323 

这样使用 INDEX (age,coin) 创建针对 这两列的联合索引, 可以大大的提高效率。


外键


点击这里,看视频讲解,学习下面内容

我们为一个医药公司开发系统。

设计的数据库中,有这样的一张用户表,和药品表

CREATE TABLE user (
  id int NOT NULL AUTO_INCREMENT  PRIMARY KEY,
  username varchar(150)  NOT NULL,
  `password` varchar(128)  NOT NULL,
  realname varchar(30)  NOT NULL
) ;


CREATE TABLE medicine (
  id      int NOT NULL AUTO_INCREMENT  PRIMARY KEY,
  name         varchar(150)  NOT NULL,
  description  varchar(30)  NOT NULL
) ;

现在 我们需要创建一张订单表, 订单里面包括了 下订单的用户,和他所购买的药品。

我们可以这样写吗?

CREATE TABLE `order` (
  id      int NOT NULL AUTO_INCREMENT  PRIMARY KEY,
  name    varchar(150)  NOT NULL,
  user_id      int  NOT NULL,
  medicine_id  int  NOT NULL
) ;

显然,这里面有个要求, 订单表中的用户只能是 user表里面的存在的用户, 订单表中的药品只能是user表里面存在的药品。

像上面这样定义,我们就有可能创建一条订单记录, user_id 是 user表里面不存在的id, medicine_id是 medicine表里面不存在的id。

这样就有问题了。

我们可以使用数据库的外键(Foreign Key)约束来解决这个问题。

我们这样定义

CREATE TABLE `order` (
  id      int NOT NULL AUTO_INCREMENT  PRIMARY KEY,
  name    varchar(150)  NOT NULL,
  user_id      int  NOT NULL,
  medicine_id  int  NOT NULL,
  
  FOREIGN KEY (user_id)
   REFERENCES user(id)
   ON UPDATE CASCADE
   ON DELETE RESTRICT,

  
  FOREIGN KEY (medicine_id)
   REFERENCES medicine(id)
   ON UPDATE CASCADE
   ON DELETE RESTRICT
) ;

我们通过 下面的语句

FOREIGN KEY (user_id)
   REFERENCES user(id)
   ON UPDATE CASCADE
   ON DELETE RESTRICT

指定了 order 这张表里面 user_id 这个字段是 一个外键 , 它 引用了 另外一张表 user 里面的 id 这一列。

这样, 当我们在order表里面插入记录的时候,这个 记录的 user_id 字段的值,必须是 user 表里面存在的一个id。

否则,插入不会成功,数据库系统户报错。

这样就保证了 订单表中的用户只能是 user表里面的存在的用户。


那么其中 这两句是什么意思呢?

ON UPDATE CASCADE
ON DELETE RESTRICT

这里面

ON UPDATE CASCADE 表示 当我们 修改 user表里面 的记录的id值的时候, 如果在order表里面,有和这条记录对应的订单记录, 则数据库服务 自动修改 order表中对应记录的 user_id 值。

UPDATE表示修改记录, CASCADE 表示自动关联修改的意思。

ON DELETE RESTRICT 表示 当我们要 删除 user表里面 的记录的id值的时候, 如果在order表里面,有和这条记录对应的订单记录, 则数据库服务 禁止该操作 ,也就是返回操作不成功。

DELETE 表示删除记录, RESTRICT 表示禁止的意思。

如果我们写 ON UPDATE RESTRICT 表示 当我们 修改 user表里面 的记录的id值的时候, 如果在order表里面,有和这条记录对应的订单记录, 则数据库服务 禁止该操作


上面的示例中,对应medicine表 的 外键约束语句,也是起到了一样的约束作用。


作业和练习

前面的作业,我们创建了Prac库,在其中添加了 user 表 和 medicine表,

  1. 请用SQL语句 ,执行下面的操作
  • 在user表中,将 id 设置为 主键, 为 登录名 字段 添加 唯一索引。

  • 在medicine表中,将 id 设置为 主键, 为 药品名和药品编号 字段 添加 多列唯一索引。

  • 添加一张 药品统计表 stats_medine, 包括字段: 药品id(外键字段,引用medicine表中的id字段)、目前总销量sales_amount_total、目前总销售金额 sales_money_total

  1. 请用 Python语言编写一个程序 执行下面的操作
  • 创建一百万条medicine数据。

  • 连接数据库,将这一百万条数据依次插入到数据库中,统计总体耗时。然后 用SQL语句 删除上面插入的数据。

  • 用SQL语句 先 删除 medicine 表中的索引, 将这一百万条数据依次插入到数据库中,统计总体耗时,然后再添加索引。对比和上面 的插入 耗时的区别。


扫码分享给朋友,一起学更有动力哦




上一页 下一页