【MySQL基础知识】数据库约束&联合查询&索引

一、数据库约束

针对表的数据内容作出限制;创建表的时候确定出来,跟随建表语句一起被设置到数据库服务器中。

1.什么是数据库约束

是指对数据库表中的数据所施加的规则或条件,用于确保数据的准确性和可靠性。这些约束可以是基于数据类型、值范围、唯一性、非空等规则,以确保数据的正确性和相容性。

2.约束类型

主键约束:表示这一行/这一条记录的身份标识(比如人的身份证)

3.NOT NULL非空约束

一旦出现错误,说明上个SQL没有成功,没有执行完。如果是出现警告,上个SQL是执行完了的。

复制代码
create table student(id int not null, name varchar(20));

insert into student values(null,null);

4.default默认约束

设置默认值。指定列插入时,未被指定的列就是默认值

复制代码
create table student (id int,name varchar(20) default '无名氏');

查询结果:

5.unique唯一约束

确保这个值在所有行的对应列中是唯一的

复制代码
create table student (id int unique,name varchar(20));

这样,本条数据只能插入一次,否则会出现错误(因为id是唯一约束)【空值例外】

此处的"插入"会先触发一个"查询" (MySQL自动的),如果查到了就会报错,没查到就会插入成功。【进行查询的时候,如果表非常大,数据很多,查的速度可能慢(索引可优化效率)】

6.primary key主键约束

这一条数据的"身份标识",通常叫做"xxx_id"

复制代码
create table student (id int primary key, name varchar(20));

id int primary key:相当于unique + not null

上面所说unique是唯一,但除了空值,而这里设置主键后既是唯一且不能为空

一个表中只能有一个主键【可以设置多个 unique**,但不能设置多个主键】**

虽然不能设置多个主键,但可以有一个主键包含多个列------联合主键(不推荐)

复制代码
create table student (name varchar(10), class varchar(20), primary key(name,class));

【特殊的】

unique + not null也可以合成一个主键

自增主键(常用)

针对整数类型的列,可以指定自增主键

复制代码
create table student (id int primary key auto_increment, name varchar(20));

设置自增主键后,插入的 id 可以是空,这里的空不是空值,而是让MySQL自动设置,所以 ' 王五 ' 这条数据的id就为3【且本条数据可以进行插入多次,id依次自增】

【注意】自增主键只会根据当前表中的最大值 进行+1! (如果此时的表id为1-9排列,插入了一条id为100的数据,再次使用自增主键进行插入数据时,这条数据的id就为101。如果在表中id为101这条数据删除了,再次使用自增主键进行插入数据,此时的数据id不是续这个表中的100!而是102。这里是因为最大值不是根据这个表实时来看的,而是用MySQL拿一个变量来记录,即使把最大值删除掉了,也是按照刚才的值来累加的

【工作中应用场景】

7.foreign key外键约束

涉及两张表

外键用于定义主表和从表之间的关系

外键约束主定义在从表的列上,主表关联的列必须是主键或唯一约束

当定义外键后,要求从表中的外键列数据必须在主表的主键或唯一列存在或为null

复制代码
create table student (
  student_id int primary key auto_increment,
  class_name varchar(20),class_id int,
  foreign key(class_id) references class(class_id) //references关联哪个表的哪一列
);
  • 创建student表,外键的列是class_id,关联class表里的class_id列

在这两个表中class表为父表,student表为子表

父表和子表相互约束

相关代码:

复制代码
USE test1;
drop table class;
drop table student;

SHOW tables;

desc class;
desc student;

create table class (
  class_id int primary key auto_increment,
  class_name varchar(20)
);
create table student (
  student_id int primary key auto_increment,
  class_name varchar(20),class_id int,
  foreign key(class_id) references class(class_id)
);

insert into class values (null,'java100'),(null,'java101'),(null,'java102');
insert into student values (null,'张三',1),(null,'李四',2),(null,'王五',3);

select * from class;
select * from student;

如果给student表中插入class_id为4的数据,就会出现报错,因为父表class表没有id为4的数据【同样,修改删除也会受到限制

针对子表操作,和父表关联的数据在父表范围内,可进行修改和删除操作

比如将class_id为1的修改为3,是可以的,此时有两个class_id为3,也可删除class_id为3的数据

---修改

---删除

【注意】

  • 如果在创建父表class表时没有设置主键,直接给子表student表设置外键时会报错,显示缺失索引
  • 在MySQL中,强制要求 使用外键时被关联的父表被关联的这一列必须带有索引 !(可手动添加)
  • 针对主键列自动带有索引(唯一键 unique****也可自带索引)

【重要场景】

8.check约束【常用】

可以根据实际需要,灵活定义,check后面可以指定一个条件(MySQL8才有效)

复制代码
create table student(
  id int,name varchar(20),age int,gender varchar(1),
  check(age >= 17),check(gender = '男' or gender = '女')  
);
  • 指定条件后再插入数据,就无法插入age >= 17和性别为其他的数据了,会出现报错

二、联合查询

多表查询

前提条件:联合查询的表,需要有一定的关系(一对一、一对多...)

笛卡尔积=>排列组合:

表名.列名

进一步基于这个有效结果,再添加其他条件,就可以满足具体的查询需求了

【总结】

1.简介

2.1 为什么要使用联合查询

在数据设计时由于范式的要求,数据被拆分到多个表中,那么要查询一个条数据的完整信息,就要从多个表中获取数据,如下图所示:要获取学生的基本信息和班级信息就要从学生表和班级表中获取,这时就需要使用联合查询,这里的联合指的是多个表的组合。

2.2 多表联合查询时MYSQL内部是如何进行计算的

  • 参与查询的所有表取笛卡尔积,结果集在临时表中
  • 观察哪些记录是有效数据,根据两个表的关联关系过滤掉无效数据

如果联合查询表的个数越多,表中的数据量越大,临时表就会越大,所以根据实际情况确定联合查询表的个数

2.3 构造练习案例数据

复制代码
# 课程表
insert into course (name) values ('Java'), ('C++'), ('MySQL'), ('操作系统'), ('计
算机⽹络'), ('数据结构');

# 班级表
insert into class(name) values ('Java001班'), ('C++001班'), ('前端001班');

# 学⽣表
insert into student (name, sno, age, gender, enroll_date, class_id) values
('唐三藏', '100001', 18, 1, '1986-09-01', 1),
('孙悟空', '100002', 18, 1, '1986-09-01', 1),
('猪悟能', '100003', 18, 1, '1986-09-01', 1),
('沙悟净', '100004', 18, 1, '1986-09-01', 1),
('宋江', '200001', 18, 1, '2000-09-01', 2),
('武松', '200002', 18, 1, '2000-09-01', 2),
('李逹', '200003', 18, 1, '2000-09-01', 2),
('不想毕业', '200004', 18, 1, '2000-09-01', 2);

# 成绩表
insert into score (score, student_id, course_id) values
(70.5, 1, 1),(98.5, 1, 3),(33, 1, 5),(98, 1, 6),
(60, 2, 1),(59.5, 2, 5),
(33, 3, 1),(68, 3, 3),(99, 3, 5),
(67, 4, 1),(23, 4, 3),(56, 4, 5),(72, 4, 6),
(81, 5, 1),(37, 5, 5),
(56, 6, 2),(43, 6, 4),(79, 6, 6),
(80, 7, 2),(92, 7, 6);

2.4 案例:一个完整的联合查询的过程

  • 查询学生姓名为孙悟空的详细信息,包括学生个人信息和班级信息
  1. 确定参与查询的表,学生表和班级表--->判断这两个实体是否有关系

    在from后同时写所有参与查询的表,并⽤逗号隔开

    mysql> select * from student, class;
    +----+--------------+--------+------+--------+-------------+----------+----+--------------+
    | id | name | sno | age | gender | enroll_date | class_id | id | name |
    +----+--------------+--------+------+--------+-------------+----------+----+--------------+
    | 1 | 唐三藏 | 100001 | 18 | 1 | 1986-09-01 | 1 | 3 | 前端001班 |
    | 1 | 唐三藏 | 100001 | 18 | 1 | 1986-09-01 | 1 | 2 | C++001班 |
    | 1 | 唐三藏 | 100001 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001班 |
    | 2 | 孙悟空 | 100002 | 18 | 1 | 1986-09-01 | 1 | 3 | 前端001班 |
    | 2 | 孙悟空 | 100002 | 18 | 1 | 1986-09-01 | 1 | 2 | C++001班 |
    | 2 | 孙悟空 | 100002 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001班 |
    | 3 | 猪悟能 | 100003 | 18 | 1 | 1986-09-01 | 1 | 3 | 前端001班 |
    | 3 | 猪悟能 | 100003 | 18 | 1 | 1986-09-01 | 1 | 2 | C++001班 |
    | 3 | 猪悟能 | 100003 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001班 |
    | 4 | 沙悟净 | 100004 | 18 | 1 | 1986-09-01 | 1 | 3 | 前端001班 |
    | 4 | 沙悟净 | 100004 | 18 | 1 | 1986-09-01 | 1 | 2 | C++001班 |
    | 4 | 沙悟净 | 100004 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001班 |
    | 5 | 宋江 | 200001 | 18 | 1 | 2000-09-01 | 1 | 3 | 前端001班 |
    | 5 | 宋江 | 200001 | 18 | 1 | 2000-09-01 | 1 | 2 | C++001班 |
    | 5 | 宋江 | 200001 | 18 | 1 | 2000-09-01 | 1 | 1 | Java001班 |
    | 6 | 武松 | 200002 | 18 | 1 | 2000-09-01 | 1 | 3 | 前端001班 |
    | 6 | 武松 | 200002 | 18 | 1 | 2000-09-01 | 1 | 2 | C++001班 |
    | 6 | 武松 | 200002 | 18 | 1 | 2000-09-01 | 1 | 1 | Java001班 |
    | 7 | 李逹 | 200003 | 18 | 1 | 2000-09-01 | 1 | 3 | 前端001班 |
    | 7 | 李逹 | 200003 | 18 | 1 | 2000-09-01 | 1 | 2 | C++001班 |
    | 7 | 李逹 | 200003 | 18 | 1 | 2000-09-01 | 1 | 1 | Java001班 |
    | 8 | 不想毕业 | 200004 | 18 | 1 | 2000-09-01 | 1 | 3 | 前端001班 |
    | 8 | 不想毕业 | 200004 | 18 | 1 | 2000-09-01 | 1 | 2 | C++001班 |
    | 8 | 不想毕业 | 200004 | 18 | 1 | 2000-09-01 | 1 | 1 | Java001班 |
    +----+--------------+--------+------+--------+-------------+----------+----+--------------+
    24 rows in set (0.00 sec)

  2. 确定连接条件,student表中的class_id与class表中id列的值相等

    在where⼦句中加⼊连接条件

    mysql> select * from student, class where student.class_id = class.id;
    +----+--------------+--------+------+--------+-------------+----------+----+------------+
    | id | name | sno | age | gender | enroll_date | class_id | id | name |
    +----+--------------+--------+------+--------+-------------+----------+----+------------+
    | 1 | 唐三藏 | 100001 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001班 |
    | 2 | 孙悟空 | 100002 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001班 |
    | 3 | 猪悟能 | 100003 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001班 |
    | 4 | 沙悟净 | 100004 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001班 |
    | 5 | 宋江 | 200001 | 18 | 1 | 2000-09-01 | 1 | 1 | Java001班 |
    | 6 | 武松 | 200002 | 18 | 1 | 2000-09-01 | 1 | 1 | Java001班 |
    | 7 | 李逹 | 200003 | 18 | 1 | 2000-09-01 | 1 | 1 | Java001班 |
    | 8 | 不想毕业 | 200004 | 18 | 1 | 2000-09-01 | 1 | 1 | Java001班 |
    +----+--------------+--------+------+--------+-------------+----------+----+------------+
    8 rows in set (0.00 sec)

  3. 加入查询条件

    依题意添加where条件

    mysql> select * from student, class
    where student.class_id = class.id
    and name = '宋江';

    ERROR 1052 (23000): Column 'name' in where clause is ambiguous

    由于两个表中都有name列,所以MySQL不清楚具体要使⽤哪个列,这时可以⽤"表名.列号"的⽅式指

    定具体的列

    得到⽬标记录⾏

    mysql> select * from student, class
    where student.class_id = class.id
    and student.name = '宋江';
    +----+--------+--------+------+--------+-------------+----------+----+------------+
    | id | name | sno | age | gender | enroll_date | class_id | id | name |
    +----+--------+--------+------+--------+-------------+----------+----+------------+
    | 5 | 宋江 | 200001 | 18 | 1 | 2000-09-01 | 1 | 1 | Java001班 |
    +----+--------+--------+------+--------+-------------+----------+----+------------+
    1 row in set (0.00 sec)

  4. 精简查询结果字段

    select
    student.id,
    student.name,
    student.sno,
    student.age,
    student.gender,
    student.enroll_date,
    class.name
    from
    student, class
    where
    student.class_id = class.id
    and
    student.name = '宋江';

    +----+--------+--------+------+--------+-------------+------------+
    | id | name | sno | age | gender | enroll_date | name |
    +----+--------+--------+------+--------+-------------+------------+
    | 5 | 宋江 | 200001 | 18 | 1 | 2000-09-01 | Java001班 |
    +----+--------+--------+------+--------+-------------+------------+
    1 row in set (0.00 sec)

  5. 可以为表名指定别名【不建议】

    select
    s.id,
    s.name,
    s.sno,
    s.age,
    s.gender,
    s.enroll_date,
    c.name
    from
    student s , class c
    where
    s.class_id = c.id
    and
    s.name = '宋江';

    +----+--------+--------+------+--------+-------------+------------+
    | id | name | sno | age | gender | enroll_date | name |
    +----+--------+--------+------+--------+-------------+------------+
    | 5 | 宋江 | 200001 | 18 | 1 | 2000-09-01 | Java001班 |
    +----+--------+--------+------+--------+-------------+------------+
    1 row in set (0.00 sec)

2.内连接

最终查询的数据,要在两个表中同时存在

相当于上面的多表联合查询,把两个表之间的**"** ,****"换成了 joinwhere****换成了 on

2.1 语法

复制代码
select 字段 from 表1 别名1, 表2 别名2 where 连接条件 and 其他条件;
select 字段 from 表1 别名1 [inner] join 表2 别名2 on 连接条件 where 其他条件;

3.2 示例

  • 查询"唐三藏"同学的成绩

    mysql> select s.name, sc.score
    from student s join score sc on sc.student_id = s.id
    where s.name = '唐三藏';
    +--------+-------+
    | name | score |
    +--------+-------+
    | 唐三藏 | 70.5 |
    | 唐三藏 | 98.5 |
    | 唐三藏 | 33 |
    | 唐三藏 | 98 |
    +--------+-------+
    4 rows in set (0.00 sec)

  • 查询所有同学的总成绩,及同学的个人信息

    mysql> select s.name, sum(sc.score)
    from student s, score sc
    where sc.student_id = s.id group by (s.id);
    +--------+----------------+
    | name | sum(sc.score) |
    +--------+----------------+
    | 唐三藏 | 300 |
    | 孙悟空 | 119.5 |
    | 猪悟能 | 200 |
    | 沙悟净 | 218 |
    | 宋江 | 118 |
    | 武松 | 178 |
    | 李逵 | 172 |
    +--------+----------------+
    7 rows in set (0.00 sec)

Group by使用了 student.id进行分组,查询表列表中的student.name没有出现在Group by分组中,也没有包含在聚合函数中,这是因为SQL规定在Group by分组查询时,如果查询列表中的列没有出现在GROUPBY子句中,但这些列的值在每个分组内部是相同的,那么它们可以出现在查询结果中。

  • 查询所有同学每门课的成绩,及同学的个人信息

    select
    s.id as id,
    s.name as 姓名,
    s.sno as 学号,
    s.gender as 性别,
    c.name as 班级,
    sc.score as 分数
    from
    student s, course c, score sc
    where
    s.id = sc.student_id
    and
    c.id = sc.course_id
    order by
    s.id;

    结果集中没有"不想毕业"同学的成绩,因为score表中没有这位同学的记录

    +----+--------+--------+--------+------------+-------+
    | id | 姓名 | 学号 | 性别 | 班级 | 分数 |
    +----+--------+--------+--------+------------+-------+
    | 1 | 唐三藏 | 100001 | 1 | Java | 70.5 |
    | 1 | 唐三藏 | 100001 | 1 | MySQL | 98.5 |
    | 1 | 唐三藏 | 100001 | 1 | 计算机网络 | 33 |
    | 1 | 唐三藏 | 100001 | 1 | 数据结构 | 98 |
    | 2 | 孙悟空 | 100002 | 1 | Java | 60 |
    | 2 | 孙悟空 | 100002 | 1 | 计算机网络 | 59.5 |
    | 3 | 猪悟能 | 100003 | 1 | Java | 33 |
    | 3 | 猪悟能 | 100003 | 1 | MySQL | 68 |
    | 3 | 猪悟能 | 100003 | 1 | 计算机网络 | 99 |
    | 4 | 沙悟净 | 100004 | 1 | Java | 67 |
    | 4 | 沙悟净 | 100004 | 1 | MySQL | 23 |
    | 4 | 沙悟净 | 100004 | 1 | 计算机网络 | 56 |
    | 4 | 沙悟净 | 100004 | 1 | 数据结构 | 72 |
    | 5 | 宋江 | 200001 | 1 | Java | 81 |
    | 5 | 宋江 | 200001 | 1 | 计算机网络 | 37 |
    | 6 | 武松 | 200002 | 1 | C++ | 56 |
    | 6 | 武松 | 200002 | 1 | 操作系统 | 43 |
    | 6 | 武松 | 200002 | 1 | 数据结构 | 79 |
    | 7 | 李逵 | 200003 | 1 | C++ | 80 |
    | 7 | 李逵 | 200003 | 1 | 数据结构 | 92 |
    +----+--------+--------+--------+------------+-------+
    20 rows in set (0.00 sec)

3.外连接

对于两个表一一对应的情况,外连接和内连接完全等价

  • 外连接分为左外连接、右外连接全外连接三种类型,MySQL不支持全外连接。
  • 左外连接:返回左表的所有记录和右表中匹配的记录。如果右表中没有匹配的记录,则结果集中对应字段会显示为NULL。
  • 右外连接:与左外连接相反,返回右表的所有记录和左表中匹配的记录。如果左表中没有匹配的记录,则结果集中对应字段会显示为NULL。
  • 全外连接:结合了左外连接和右外连接的特点,返回左右表中的所有记录。如果某一边表中没有匹配的记录,则结果集中对应字段会显示为NULL。

3.1 语法

复制代码
-- 左外连接,表1完全显示
select 字段名 from 表名1 left join 表名2 on 连接条件;
-- 右外连接,表2完全显示
select 字段 from 表名1 right join 表名2 on 连接条件;

3.2 示例

  • 查询没有参加考试的同学信息

    左连接以JOIN左边的表为基础,左表显示全部记录,右表中没有匹配的记录用NULL填充

    mysql> select s.id, s.name, s.sno, s.age, sc.* from student s
    LEFT JOIN score sc on sc.student_id = s.id;
    +----+--------+--------+-----+----+-------+------------+-------------+
    | id | name | sno | age | id | score | student_id | course_id |
    +----+--------+--------+-----+----+-------+------------+-------------+
    | 1 | 唐三藏 | 100001 | 18 | 1 | 70.5 | 1 | 1 |
    | 1 | 唐三藏 | 100001 | 18 | 2 | 98.5 | 1 | 3 |
    | 1 | 唐三藏 | 100001 | 18 | 3 | 33 | 1 | 5 |
    | 1 | 唐三藏 | 100001 | 18 | 4 | 98 | 1 | 6 |
    | 2 | 孙悟空 | 100002 | 18 | 5 | 60 | 2 | 1 |
    | 2 | 孙悟空 | 100002 | 18 | 6 | 59.5 | 2 | 5 |
    | 3 | 猪悟能 | 100003 | 18 | 7 | 33 | 3 | 1 |
    | 3 | 猪悟能 | 100003 | 18 | 8 | 68 | 3 | 3 |
    | 3 | 猪悟能 | 100003 | 18 | 9 | 99 | 3 | 5 |
    | 4 | 沙悟净 | 100004 | 18 | 10 | 67 | 4 | 1 |
    | 4 | 沙悟净 | 100004 | 18 | 11 | 23 | 4 | 3 |
    | 4 | 沙悟净 | 100004 | 18 | 12 | 56 | 4 | 5 |
    | 4 | 沙悟净 | 100004 | 18 | 13 | 72 | 4 | 6 |
    | 5 | 宋江 | 200001 | 18 | 14 | 81 | 5 | 1 |
    | 5 | 宋江 | 200001 | 18 | 15 | 37 | 5 | 5 |
    | 6 | 武松 | 200002 | 18 | 16 | 56 | 6 | 2 |
    | 6 | 武松 | 200002 | 18 | 17 | 43 | 6 | 4 |
    | 6 | 武松 | 200002 | 18 | 18 | 79 | 6 | 6 |
    | 7 | 李逵 | 200003 | 18 | 19 | 80 | 7 | 2 |
    | 7 | 李逵 | 200003 | 18 | 20 | 92 | 7 | 5 |
    | 8 | 不想毕业 | 200004 | 18 | NULL | NULL | NULL | NULL |
    +----+--------+--------+-----+----+-------+------------+-------------+
    21 rows in set (0.00 sec)

    过滤没参加考试的同学

    mysql> select s.* from student s LEFT JOIN score sc on sc.student_id = s.id
    where sc.score is null;

    +----+--------+--------+-----+--------+------------+----------+
    | id | name | sno | age | gender | enrol_date | class_id |
    +----+--------+--------+-----+--------+------------+----------+
    | 8 | 不想毕业 | 200004 | 18 | 1 | 2000-09-01 | 1 |
    +----+--------+--------+-----+--------+------------+----------+
    1 row in set (0.00 sec)

  • 查询没有学生的班级

    右连接以JOIN右边的表为基准,右表显示全部记录,左表中没有匹配的记录用NULL填充

    mysql> select * from student s RIGHT JOIN class c on c.id = s.class_id;

    +----+--------+--------+-----+--------+------------+----------+----+--------+
    | id | name | sno | age | gender | enrol_date | class_id | id | name |
    +----+--------+--------+-----+--------+------------+----------+----+--------+
    | 4 | 沙悟净 | 100004 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001班 |
    | 3 | 猪悟能 | 100003 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001班 |
    | 2 | 孙悟空 | 100002 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001班 |
    | 1 | 唐三藏 | 100001 | 18 | 1 | 1986-09-01 | 1 | 1 | Java001班 |
    | 8 | 不想毕业 | 200004 | 18 | 1 | 2000-09-01 | 2 | 2 | C++001班 |
    | 7 | 李逵 | 200003 | 18 | 1 | 2000-09-01 | 2 | 2 | C++001班 |
    | 6 | 武松 | 200002 | 18 | 1 | 2000-09-01 | 2 | 2 | C++001班 |
    | 5 | 宋江 | 200001 | 18 | 1 | 2000-09-01 | 2 | 2 | C++001班 |
    | NULL | NULL | NULL | NULL | NULL | NULL | NULL | 3 | 前端001班 |
    +----+--------+--------+-----+--------+------------+----------+----+--------+
    15 rows in set (0.00 sec)

    过滤有学生的班级

    mysql> select c.* from student s RIGHT JOIN class c on c.id = s.class_id
    where s.id is null;
    +----+--------+
    | id | name |
    +----+--------+
    | 3 | 前端001班 |
    +----+--------+
    1 row in set (0.00 sec)

4.自连接

自己和自己链接(很少用)

通过自连接,把行和行之间的关系,转成列和列之间的关系。

4.1 应用场景

自连接是自己与自己取笛卡尔积,可以把行转化成列,在查询的时候可以使用where条件对结果进行过滤,或者说实现行与行之间的比较。在做表连接时为表起不同的别名。

复制代码
# 不为表指定别名
mysql> select * from score, score;
ERROR 1066 (42000): Not unique table/alias: 'score'

# 指定别名
mysql> select * from score s1, score s2;

4.2 示例

  • 显示所有"MySQL"成绩比"JAVA"成绩高的成绩信息
复制代码
# 首先分两步进行,先查出JAVA和MySQL的课程id,分别为1和3
mysql> select * from course where name = 'Java' or name = 'MySQL';

+----+--------+
| id | name   |
+----+--------+
|  1 | Java   |
|  3 | MySQL  |
+----+--------+
2 rows in set (0.00 sec)

# 再查询成绩表中,JAVA成绩比MySQL成绩好的信息
mysql> select s1.* from score s1, score s2 
where 
  s1.student_id = s2.student_id 
and 
  s1.course_id = 3 
and 
  s2.course_id = 1 
and 
  s1.score > s2.score;
+----+-------+------------+-------------+
| id | score | student_id | course_id   |
+----+-------+------------+-------------+
|  2 |  98.5 |          1 |           3 |
|  8 |   60  |          3 |           3 |
+----+-------+------------+-------------+
2 rows in set (0.00 sec)

# 结合在一起进行查询
select s1.* from 
  score s1, 
  score s2, 
  course c1, 
  course c2 
where 
  s1.student_id = s2.student_id 
and 
  s1.course_id = c1.id 
and 
  s2.course_id = c2.id 
and 
  s1.score > s2.score 
and 
  c1.name = 'MySQL' 
and 
  c2.name = 'Java';

+----+-------+------------+-------------+
| id | score | student_id | course_id   |
+----+-------+------------+-------------+
|  2 |  98.5 |          1 |           3 |
|  8 |   60  |          3 |           3 |
+----+-------+------------+-------------+
2 rows in set (0.00 sec)

4.3 表连接练习

  • 显示所有"MySQL"成绩比"JAVA"成绩高的学生信息和班级以及成绩信息

    相关的表全部加入连接,并确定连接条件

    select stu.name as 姓名, c.name as 班级, s1.score as MySQL分数, s2.score as Java
    分数 from
    score s1,
    score s2,
    course c1,
    course c2,
    student stu,
    class c
    where
    s1.student_id = s2.student_id
    and
    s1.course_id = c1.id
    and
    s2.course_id = c2.id
    and
    s1.score > s2.score
    and
    stu.id = s1.student_id
    and
    stu.class_id = c.id
    and
    c1.name = 'MySQL'
    and
    c2.name = 'Java';

    +--------+--------+----------+--------+
    | 姓名 | 班级 | MySQL分数 | Java分数 |
    +--------+--------+----------+--------+
    | 唐三藏 | Java001班 | 98.5 | 70.5 |
    | 猪悟能 | Java001班 | 68 | 33 |
    +--------+--------+----------+--------+
    2 rows in set (0.00 sec)

5.子查询

【不建议用】

子查询是把一个SELECT语句的结果当做别一个SELECT语句的条件,也叫嵌套查询【查询语句嵌套】

子查询把 SQL 嵌套,通常情况下,把一个SQL的结果,作为另一个SQL的条件(一部分),也可以把一个 SQL 的结果,作为一个表,和其他表进行笛卡尔积。

5.1 语法

复制代码
select * from table1 where col_name1 in | IN (
    select col_name1 from table2 where col_name2 in | IN (
        select ...
    )
) ... 

5.2 单行子查询

嵌套的查询中只返回一行数据

  • 示例:查询与"不想毕业"同学的同班同学
    根据名字,找到"不想毕业"的class_id,再根据 class_id 继续查询
复制代码
mysql> select * from student 
where class_id = (select class_id from student where name = '不想毕业');
+----+--------+--------+-----+--------+------------+----------+
| id | name   | sno    | age | gender | enrol_date | class_id |
+----+--------+--------+-----+--------+------------+----------+
|  5 | 宋江   | 200001 |  18 |      1 | 2000-09-01 |        2 |
|  6 | 武松   | 200002 |  18 |      1 | 2000-09-01 |        2 |
|  7 | 李逵   | 200003 |  18 |      1 | 2000-09-01 |        2 |
|  8 | 不想毕业 | 200004 |  18 |      1 | 2000-09-01 |        2 |
+----+--------+--------+-----+--------+------------+----------+
4 rows in set (0.00 sec)

5.3 多行子查询

嵌套的查询中返回多行数据,使用NOT IN关键字

  • 示例:查询"MySQL"或"Java"课程的成绩信息

复制代码
mysql> select * from score 
where course_id in (
  select id from course 
  where name = 'Java' or name = 'MySQL'
);

+----+-------+------------+-------------+
| id | score | student_id | course_id   |
+----+-------+------------+-------------+
|  1 |  70.5 |          1 |           1 |
|  5 |   60  |          2 |           1 |
|  7 |   33  |          3 |           1 |
| 10 |   67  |          4 |           1 |
| 14 |   81  |          5 |           1 |
|  2 |  98.5 |          1 |           3 |
|  8 |   68  |          3 |           3 |
| 11 |   23  |          4 |           3 |
+----+-------+------------+-------------+
8 rows in set (0.00 sec)

# 使用NOT IN 可以查询除了"MySQL"或"Java"课程的成绩
mysql> select * from score 
where course_id not in (
  select id from course 
  where name = 'Java' or name = 'MySQL');
  
+----+-------+------------+-------------+
| id | score | student_id | course_id   |
+----+-------+------------+-------------+
|  3 |   33  |          1 |           5 |
|  4 |   98  |          1 |           6 |
|  6 |  59.5 |          2 |           5 |
|  9 |   99  |          3 |           5 |
| 12 |   56  |          4 |           5 |
| 13 |   72  |          4 |           6 |
| 15 |   37  |          5 |           5 |
| 16 |   56  |          6 |           2 |
| 17 |   43  |          6 |           4 |
| 18 |   79  |          7 |           2 |
| 19 |   80  |          7 |           6 |
| 20 |   92  |          7 |           5 |
+----+-------+------------+-------------+
12 rows in set (0.00 sec)

5.4 多列子查询

单行子查询和多行子查询都只返回一列数据,多列子查询中可以返回多个列的数据,外层查询与嵌套的内层查询的列要匹配

  • 示例:查询重复录入的分数

    插入重复的分数:score, student_id, course_id重复

    mysql> insert into score(score, student_id, course_id) values
    (70.5, 1, 1),(98.5, 1, 3),(60, 2, 1);

    子查询中返回多个列

    mysql> SELECT * FROM score WHERE (score, student_id, course_id) IN (
    SELECT score, student_id, course_id
    FROM score
    GROUP BY score, student_id, course_id
    HAVING count(*) > 1
    );

    +----+-------+------------+-------------+
    | id | score | student_id | course_id |
    +----+-------+------------+-------------+
    | 1 | 70.5 | 1 | 1 |
    | 2 | 98.5 | 1 | 3 |
    | 5 | 60 | 2 | 1 |
    | 21 | 70.5 | 1 | 1 |
    | 22 | 98.5 | 1 | 3 |
    | 23 | 60 | 2 | 1 |
    +----+-------+------------+-------------+
    6 rows in set (0.00 sec)

5.5 在from子句中使用子查询

当一个查询产生结果时,MySQL自动创建一个临时表,然后把结果集放在这个临时表中,最终返回给用户,在from子句中也可以使用临时表进行子查询或表连接操作

  • 示例:查询所有比"Java001班"平均分高的成绩信息

    首先分步进行,第一步先查出Java001班的平均分

    mysql> select avg(sc.score) score from student s
    join class c on s.class_id = c.id
    join score sc on s.id = sc.student_id
    where c.name = 'Java001班';
    +----------+
    | score |
    +----------+
    | 66.65625 |
    +----------+
    1 row in set (0.00 sec)

    把以上查询做为临时表,与真实表进行比较

    mysql> select * from score s, (
    select avg(sc.score) score from student s
    join class c on s.class_id = c.id
    join score sc on s.id = sc.student_id
    where c.name = 'Java001班'
    ) tmp
    where s.score > tmp.score;

    +----+-------+------------+-------------+----------+
    | id | score | student_id | course_id | score |
    +----+-------+------------+-------------+----------+
    | 1 | 70.5 | 1 | 1 | 66.65625 |
    | 2 | 98.5 | 1 | 3 | 66.65625 |
    | 4 | 98 | 1 | 6 | 66.65625 |
    | 8 | 68 | 3 | 3 | 66.65625 |
    | 10 | 67 | 4 | 1 | 66.65625 |
    | 14 | 81 | 5 | 1 | 66.65625 |
    | 18 | 79 | 7 | 2 | 66.65625 |
    | 19 | 80 | 7 | 6 | 66.65625 |
    | 20 | 92 | 7 | 5 | 66.65625 |
    | 21 | 70.5 | 1 | 1 | 66.65625 |
    | 22 | 98.5 | 1 | 3 | 66.65625 |
    | 23 | 60 | 2 | 1 | 66.65625 |
    | 24 | 67 | 3 | 1 | 66.65625 |
    +----+-------+------------+-------------+----------+
    13 rows in set (0.00 sec)

tmp 是临时表的别名

6.合并查询

把多个 SQL 的查询结果,合并成一个结果集合

子查询是"嵌套",合并查询是**"并列"**

在实际应用中,为了合并多个select操作返回的结果,可以使用集合操作符 union,union all( 节省内存空间)

6.1创建新表并初始化数据

复制代码
# 创建一个新表并初始化数据
mysql> create table student1 like student; //根据某张表的结果 创建一个新的表
Query OK, 0 rows affected (0.03 sec)

# 插入4行数据
mysql> insert into student1 (name, sno, age, gender, enroll_date, class_id) values 
('唐三藏', '100001', 18, 1, '1986-09-01', 1),
('刘备', '300001', 18, 1, '1993-09-01', 3),
('张飞', '300002', 18, 1, '1993-09-01', 3),
('关羽', '300003', 18, 1, '1993-09-01', 3);

Query OK, 4 rows affected (0.01 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> select * from student1;

+----+--------+--------+-----+--------+------------+----------+
| id | name   | sno    | age | gender | enroll_date | class_id |
+----+--------+--------+-----+--------+------------+----------+
|  1 | 唐三藏 | 100001 |  18 |      1 | 1986-09-01 |        1 |
|  2 | 刘备   | 300001 |  18 |      1 | 1993-09-01 |        3 |
|  3 | 张飞   | 300002 |  18 |      1 | 1993-09-01 |        3 |
|  4 | 关羽   | 300003 |  18 |      1 | 1993-09-01 |        3 |
+----+--------+--------+-----+--------+------------+----------+
4 rows in set (0.00 sec)

6.2 Union

该操作符用于取得两个结果集的并集。当使用该操作符时,会自动去掉结果集中的重复行。

  • 示例:查询student表中id<3的同学和student1表中的所有同学

    结果集中有两张表中的数据,但是唐三藏只返回了一条记录

    mysql> select * from student where id < 3 union select * from student1;

    +----+--------+--------+-----+--------+------------+----------+
    | id | name | sno | age | gender | enroll_date | class_id |
    +----+--------+--------+-----+--------+------------+----------+
    | 1 | 唐三藏 | 100001 | 18 | 1 | 1986-09-01 | 1 |
    | 2 | 孙悟空 | 100002 | 18 | 1 | 1986-09-01 | 1 |
    | 3 | 刘备 | 300001 | 18 | 1 | 1993-09-01 | 3 |
    | 4 | 张飞 | 300002 | 18 | 1 | 1993-09-01 | 3 |
    | 5 | 关羽 | 300003 | 18 | 1 | 1993-09-01 | 3 |
    +----+--------+--------+-----+--------+------------+----------+
    5 rows in set (0.00 sec)

6.3 Union all

该操作符用于取得两个结果集的并集 。当使用该操作符时,不会去掉结果集中的重复行

复制代码
# 结果集中有两张表中的数据,返回了所有唐三藏的记录
mysql> select * from student where id < 3 union all select * from student1;

+----+--------+--------+-----+--------+------------+----------+
| id | name   | sno    | age | gender | enroll_date | class_id |
+----+--------+--------+-----+--------+------------+----------+
|  1 | 唐三藏 | 100001 |  18 |      1 | 1986-09-01 |        1 |
|  2 | 孙悟空 | 100002 |  18 |      1 | 1986-09-01 |        1 |
|  1 | 唐三藏 | 100001 |  18 |      1 | 1986-09-01 |        1 |
|  3 | 刘备   | 300001 |  18 |      1 | 1993-09-01 |        3 |
|  4 | 张飞   | 300002 |  18 |      1 | 1993-09-01 |        3 |
|  5 | 关羽   | 300003 |  18 |      1 | 1993-09-01 |        3 |
+----+--------+--------+-----+--------+------------+----------+
6 rows in set (0.00 sec)

三、索引

索引在数据库中是重要的数据结构

作用:加快查询速度,但是要消耗额外的存储空间,在插入/删除/修改的时候也可能有额外开销。

1. 面试题

  • B+树的特点和优势

  • 索引失效情况

2. 简介

2.1 索引是什么?

MySQL的索引是一种数据结构( 占据额外空间),它可以帮助数据库高效地查询、更新数据表中的数据。索引通过一定的规则排列数据表中的记录,使得对表的查询可以通过对索引的搜索来加快速度。

MySQL索引类似于书籍的目录,通过指向数据行的位置,可以快速定位和访问表中的数据,比如汉语字典的目录(索引)页,我们可以按笔画、偏旁部首、拼音等排序的目录(索引)快速查找到需要的字。

  • 笔画索引
  • 偏旁部首索引
  • 拼音索引

2.2 为什么要使用索引?

显而易见,使用索引的目的只有一个,就是提升数据检索的效率 ,在应用程序的运行过程中,查询操作的频率远远高于增删改的频率。

3. 索引应该选择哪种数据结构

【注意】顺序表时间复杂度O(1),效率高的是"随机访问",也就是说顺序表查询效率高,其实是随机访问的效率高。查询是按照"值"查找,按照"下标"找值就叫做随机访问了。

3.1 HASH

时间复杂度是O(1),查询速度非常快,但是会发生哈希冲突,会让每一个数组上的元素变成链表,但是也可以通过扩容,控制链表的长度

只能处理相等查询,无法应对范围查询

3.2 二叉搜索树

二叉搜索树的中序遍历是一个有序数组,但有几个问题导致它不适合用作索引的数据结构

  1. 最坏情况下时间复杂度为O(N)
  2. 节点个数过多无法保证树高
    元素数目比较多二叉搜索树就会变高,高度高了,比较次数就增加了,多一次比较就是多读一次硬盘。

AVL和红黑树,虽然是平衡或者近似平衡,但是毕竟是二叉结构

在检索数据时,每次访问某个节点的子节点时都会发生一次磁盘IO,而在整个数据库系统中,IO是性能的瓶颈,减少IO次数可以有效的提升性能

3.3 N叉树 (B树)

为了解决树高的问题,可以使用N叉树

N叉树每次可以分为N+1个支

通过观察,相同数据量的情况下,N叉树的树高可以得到有效的控制,也就意味着在相同数据量的情况下可以减少IO的次数,从而提升效率。但是MySQL认为N叉树做为索引的数据结构还不够好。除此之外,B树读取硬盘次数不稳定

3.4 B+树

基于B树进行改进的,也是个N叉搜索树

区别B树:

  1. 一个节点有N个值,就能延伸出N个子树,B树是N+1,意味着每个节点的最后一个值就是对当前这个数的最大值
  2. 父节点的值会在子节点中以最大值的身份重新出现,此时叶子节点的内容就是整个数据集合的"全集"
  3. 针对叶子节点,使用"双向链表"方式,把叶子节点连上
  4. 由于当前叶子节点是"全集",只使用叶子节点存储 涉及到的"数据行",非叶子节点,只需要存储"索引列"

B+树相比于其他数据结构作为索引的优势【面试题】!!

  1. 作为一个N叉搜索树,高度比较低,查询的时候,需要读取硬盘的次数比较有限
  2. 所有的数据行,都在叶子节点上,所有的查询操作最终也要落在叶子节点上,每次查询,需要访问硬盘的次数是固定的
  3. 叶子节点作为链表,非常适合范围查询
  4. 非叶子节点只需要存储id,占据的空间比较小,比较方便地通过内存来缓存,减少读取硬盘的次数

为此:MySQL的索引主要就是通过B+树方式来实现的

3.4.1 简介

B+树是一种经常用于数据库和文件系统等场合的平衡查找树,MySQL索引采用的数据结构,以4阶B+树为例,如下图所示:

3.4.2 B+树的特点
  • 能够保持数据稳定有序,插入与修改有较稳定的时间复杂度
  • 非叶子节点仅具有索引作用,不存储数据,所有叶子节点保真实数据
  • 所有叶子节点构成一个有序链表,可以按照key排序的次序依次遍历全部数据
3.4.3 B+树与B树的对比
  • 叶子节点中的数据是连续的,且相互链接,便于区间查找和搜索。
  • 非叶子节点的值都包含在叶子节点中
  • 对于B+树而言,在相同树高的情况下,查找任一元素的时间复杂度都一样,性能均衡。

4. MySQL中的页

B+树上面的结点,在MySQL内部称为"页"

分为索引页(非叶子节点)只存储索引列

数据页(叶子节点)存储数据行

4.1 为什么要使用页

  • .ibd文件中最重要的结构体就是Page(页) ,页是内存与磁盘交互的最小单元,默认大小为16KB (可以通过配置修改,但通常情况下不需用),每次内存与磁盘的交互至少读取一页,所以在磁盘中每个页内部的地址都是连续的,之所以这样做,是因为在使用数据的过程中,根据局部性原理,将来要使用的数据大概率与当前访问的数据在空间上是临近的,所以一次从磁盘中读取一页的数据放入内存中,当下次查询的数据还在这个页中时就可以从内存中直接读取,从而减少磁盘I/O提高性能
    MySQL进行和硬盘交互的读写操作,都是以 页 为单位的 (局部性原理)

局部性原理:是指程序在执行时呈现出局部性规律,在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域,局部性通常有两种形式:时间局部性和空间局部性。

时间局部性(Temporal Locality):如果一个信息项正在被访问,那么在近期它很可能还会被再次访问。

空间局部性(Spatial Locality):将来要用到的信息大概率与正在使用的信息在空间地址上是临近的。

  • 每一个页中即使没有数据也会使用16KB的存储空间,同时与索引的B+树中的节点对应,后续在专题七:索引中详细讲解B+树的内容,查看页的大小,可以通过系统变量innodb_page_size查看

  • 每个表都会有索引(对于MySQL Innodb 存储引擎来书,大部分的默认情况)
    如果主键,自然就是以主键列创建索引
    如果没有主键,生成一个隐藏列,通过隐藏列作为主键,作为索引列

    mysql> SHOW VARIABLES LIKE 'innodb_page_size';
    +------------------+-------+
    | Variable_name | Value |
    +------------------+-------+
    | innodb_page_size | 16384 | # 16KB
    +------------------+-------+
    1 row in set, 1 warning (0.04 sec)

  • 在MySQL中有多种不同类型的页,最常用的就是用来存储数据和索引的**"索引页"** ,也叫做"数据页" ,但不论哪种类型的页都会包含页头(FileHeader)页尾(FileTrailer) ,页的主体信息使用数据"行" 进行填充,数据页 的基本结构如下图所示:
    页头和页尾存储一些描述性信息,如叶子节点的链表-->next prev(指针)等

4.2 页文件头和页文件尾

页文件头和页文件尾中包含的信息,如下图所示:

这里我们只关注,上一页页号和下一页页号 ,通过这两个属性可以把页与页之间链接起来,形成一个双向链表

4.3 页主体

  • 页主体部分是保存真实数据的主要区域,每当创建一个新页,都会自动分配两个行,一个是页内最小行Infimun,另一个是页内最大行Supremun,这两个行并不存储任何真实信息,而是做为数据行链表的头和尾,第一个数据行有一个记录下一行的地址偏移量的区域next_record将页内所有数据行组成了一个单向链表,此时新页的结构如下所示:

  • 当向一个新页插入数据时,将Infimun连接第一个数据行,最后一行真实数据行连接Supremun,这样数据行就构建成了一个单向链表,更多的行数据插入后,会按照主键从小到大的顺序进行链接,如下图所示

4.4 页目录

  • 当按主键或索引查找某条数据时,最直接简单的方法就是从头行infimun开始,沿着链表顺序逐个比对查找,但一个页有16KB,通常会存在数百行数据,每次都要遍历数百行,无法满足高效查询,为了提高查询效率,InnoDB采用二分查找来解决查询效率问题;
  • 具体实现方式是,在每一个页中加入一个叫做页目录 Page Directory的结构,将页内包括头行、尾行在内的所有行进行分组,约定头行单独为一组,其他每个组最多8条数据,同时把每个组最后一行在页中的地址,按主键从小到大的顺序记录在页目录中在,页目录中的每一个位置称为一个,每个槽都对应了一个分组,一旦分组中的数据行超过分组的上限8个时,就会分裂出一个新的分组;
  • 后续在查询某行时,就可以通过二分查找,先找到对应的槽,然后在槽内最多8个数据行中进行遍历即可,从而大幅提高了查询效率,这时一个页的核心结构就完成了;
  • 例如要查找主键为6的行,先比对槽中记录的主键值,定位到最后一个槽2,再从最后一个槽中的第一条记录遍历,第二条记录就是我们要查询的目标行。

4.5 数据页头

数据页头记录了当前页保存数据相关的信息,如下图所示

5. B+在MySQL索引中的应用

  • 非叶子节点保存索引数据,叶子节点保存真实数据,如下图所示

  • 以查找id为5的记录,完整的检索过程如下:

  1. 首先判断B+树的根节点中的索引记录,此时5<7,应访问左孩子节点,找到索引页
  2. 在索引页2中判断id的大小,找到与5相等的记录,命中,加载对应的数据页

以上的IO过程,加载索引页1-->加载索引页2-->加载数据页3

5.1 计算三层树高的B+树可以存放多少条记录

  • 假设一条用户数据大小为1KB,在忽略数据页中数据页自身属性空间占用的情况下,一页可以存16条数据
  • 索引页一条数据的大小为,主键用BIGINT类型占8Byte,下一页地址6Byte,一共是14Byte,一个索引页可以保存16*1024/14=1170条索引记录
  • 如果只有三层树高的情况,综合只保存索引的根节点和二级节点的索引页以及保存真实数据的数据页,那么一共可以保存1170117016 = 21,902,400条记录,也就是说在两千多万条数据的表中,可以通过三次IO就完成数据的检索

如果数据很多很大,采取的方法:

6. 索引分类

按照列的维度创建索引

上面所说的B+树的结构,针对 主键索引 来说的

MySQL底层是叫看不到"表",看到的其实是树

6.1 主键索引

  • 当在一个表上定义一个主键PRIMARY KEY时,InnoDB使用它作为聚集索引
  • 推荐为每个表定义一个主键。如果没有逻辑上唯一且非空的列或列集可以使用主键,则添加一个自增列。

6.2 普通索引

  • 最基本的索引类型,没有唯一性的限制。
  • 可能为多列创建组合索引,称为复合索引或组全索引
  • 通过创建索引的sql语句构建的

6.3 唯一索引

  • 当在一个表上定义一个唯一键UNQUE时,自动创建唯一索引。
  • 与普通索引类似,但区别在于唯一索引的列不允许有重复值。

6.4 全文索引

  • 基于文本列(CHAR、VARCHAR或TEXT列)上创建,以加快对这些列中包含的数据查询和DML操作
  • 用于全文搜索,仅MyISAM和InnoDB引擎支持。

6.5 聚集索引/聚簇索引

  • 也就是主键索引(仅针对InnoDb这种存储引擎而言,MyISAM等不是)

  • 如果没有为表定义PRIMARY KEY, InnoDB使用第一个UNIQUENOT NULL的列作为聚集索引。

  • 如果表中没有PRIMARY KEY或合适的UNIQUE索引,InnoDB会为新插入的行生成一个行号并用6字节的ROW_ID字段记录,ROW_ID单调递增,并使用ROW_ID做为索引。

6.6 非聚集索引

  • 聚集索引以外的索引称为非聚集索引或二级索引
  • 二级索引中的每条记录都包含该行的主键列,以及二级索引指定的列。
  • InnoDB使用这个主键值来搜索聚集索引中的行,这个过程称为回表查询

6.7 索引覆盖

非聚簇索引的特殊情况,select 要查的内容就已经包含在索引列中,不需要回表了

  • 当一个select语句使用了普通索引,且查询列表中的列刚好是创建普通索引时的所有或部分列,这时就可以直接返回数据,而不用回表查询,这样的现象称为索引覆盖

7. 使用索引

7.1 自动创建

  • 当我们为一张表加主键约束(Primary key),外键约束(Foreign Key),唯一约束(Unique)时,MySQL会为对应的的列自动创建一个索引

  • 如果表不指定任何约束时,MySQL会自动为每一个表生成一个索引 并用ROW_ID进行标识【隐藏列】

  • 很多时候,需要自主创建索引

7.2 手动创建

注意!创建索引操作可能很危险

如果是针对一个非常大的表创建索引,创建索引会归建初B+树,触发大量的硬盘IO,开销会非常大,严重情况会把数据库搞崩

如何解决:

7.2.1 主键索引
复制代码
#方式一,创建表时创建主键
create table t_test_pk(
    id bigint primary key auto_increment,
    name varchar(20)
);

#方式二,创建表时单独指定主键列
create table t_test_pk1(
    id bigint auto_increment, 
    name varchar(20),
    primary key(id)
);

#方式三,修改表中的列为主键索引
create table t_test_pk2(
    id bigint,
    name varchar(20)
);
alter table t_test_pk2 add primary key(id); 
alter table t_test_pk2 modify id bigint auto_increment;
7.2.2 唯一索引
复制代码
#方式一,创建表时创建唯一键
create table t_test_uk(
    id bigint primary key auto_increment, 
    name varchar(20) unique 
);

#方式二,创建表时单独指定唯一列
create table t_test_uk1(
    id bigint primary key auto_increment, 
    name varchar(20),
    unique(name)
);

#方式三,修改表中的列为唯一索引
create table t_test_uk2(
    id bigint primary key auto_increment,
    name varchar(20)
);
alter table t_test_uk2 add unique(name);
7.2.3 普通索引
复制代码
#方式一,创建表时指定索引列
create table t_test_index(
    id bigint primary key auto_increment, 
    name varchar(20) unique 
    sno varchar(10),
    index(sno)  //常用
);

#方式二,修改表中的列为普通索引
create table t_test_index1(
    id bigint primary key auto_increment, 
    name varchar(20),
    sno varchar(10)
);
alter table t_test_index1 add index(sno);

#方式三,单独创建索引并指定索引名
create table t_test_index2(
    id bigint primary key auto_increment, 
    name varchar(20),
    sno varchar(10)
);  
create index index_name on t_test_index2(sno);   //常用

7.3 创建复合索引

创建语法与创建普通索引相同,只不过指定多个列,列与列之间用逗号隔开

复制代码
#方式一,创建表时指定索引列
create table t_test_index4(
    id bigint primary key auto_increment, 
    name varchar(20), 
    sno varchar(10), 
    class_id bigint,
    index(sno, class_id)
);

#方式二,修改表中的列为复合索引
create table t_test_index5(
    id bigint primary key auto_increment, 
    name varchar(20), 
    sno varchar(10), 
    class_id bigint 
);
alter table t_test_index5 add index(sno, class_id);

#方式三,单独创建索引并指定索引名
create table t_test_index6(
    id bigint primary key auto_increment, 
    name varchar(20), 
    sno varchar(10), 
    class_id bigint 
);
create index index_name on t_test_index6(sno, class_id);

7.4 查看索引

复制代码
#方式一:show keys from 表名
mysql> show keys from t_test_index6\G
*************************** 1. row ***************************
        Table: t_test_index6
   Non_unique: 0
     Key_name: PRIMARY
 Seq_in_index: 1
  Column_name: id
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: 
   Index_type: BTREE
      Comment: 
Index_comment: 
      Visible: YES
     Expression: NULL
*************************** 2. row ***************************
        Table: t_test_index6
   Non_unique: 1
     Key_name: index_name
 Seq_in_index: 1
  Column_name: sno
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: YES
   Index_type: BTREE
      Comment: 
Index_comment: 
      Visible: YES
     Expression: NULL
*************************** 3. row ***************************
        Table: t_test_index6
   Non_unique: 1
     Key_name: index_name
 Seq_in_index: 2
  Column_name: class_id
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: YES
   Index_type: BTREE
      Comment: 
Index_comment: 
      Visible: YES
     Expression: NULL
3 rows in set (0.00 sec)

#方式二
show index from t_test_index6;

#方式三,简要信息:desc 表名; 
desc t_test_index6;

7.5 删除索引

一般情况不删索引

7.5.1 主键索引
复制代码
#语法
alter table 表名 drop primary key;

#示例,删除t_test_index6表中的主键
mysql> alter table t_test_index6 drop primary key;
ERROR 1075 (42000): Incorrect table definition; there can be only one auto column and it must be defined as a key

#如查提示由于自增列的错误,先删除自增属性
mysql> alter table t_test_index6 modify id bigint;
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0

#重新删除主键
mysql> alter table t_test_index6 drop primary key;
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0

#查看结果
mysql> show keys from t_test_index6\G
*************************** 1. row ***************************
        Table: t_test_index6
   Non_unique: 1
     Key_name: index_name
 Seq_in_index: 1
  Column_name: sno
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: YES
   Index_type: BTREE
      Comment: 
Index_comment: 
      Visible: YES
     Expression: NULL
*************************** 2. row ***************************
        Table: t_test_index6
   Non_unique: 1
     Key_name: index_name
 Seq_in_index: 2
  Column_name: class_id
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: YES
   Index_type: BTREE
      Comment: 
Index_comment: 
      Visible: YES
     Expression: NULL
2 rows in set (0.00 sec)
7.5.2 其他索引
复制代码
#语法 
alter table 表名 drop index 索引名;

#示例,删除t_test_index6表中名为index_name的索引
mysql> alter table t_test_index6 drop index index_name;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

#查看结果
mysql> show keys from t_test_index6\G
Empty set (0.00 sec)

7.6 创建索引的注意事项

  • 索引应该创建在高频查询的列上
  • 索引需要占用额外的存储空间
  • 对表进行插入、更新和删除操作时,同时也会修索引,可能会影响性能
  • 创建过多或不合理的索引会导致性能下降,需要谨慎选择和规划索引

索引失效情况【面试题】

单个列索引

1.查询操作,没有在查询条件中使用索引列

2.查询的时候,使用了索引列作为条件,但是索引列的值表较单一,此时索引无法发挥作用

3.查询的时候,查询条件针对索引列进行了表达式运算

4.查询条件中包含了or,or一边是索引列的条件,另一边是非索引列的条件

5.字符串查询的时候涉及到 like

6.针对索引列进行查询

7.多个表联合查询,但是表的字符集不一致,使用索引也会失效

复合索引

多个列共同构建的索引,一般很少见

【图解】

最左前缀原则

借助explain****关键字,通过mysql服务器分析,这个查询具体怎么查,不会真的去查

通过查看查询类型,可以知道是否触发查询

相关推荐
像风一样自由20201 小时前
11.PostgreSQ、-MySQL与MongoDB-AI应用如何选择数据库
数据库·人工智能·mysql·mongodb·大模型·rag·智能体
夏之小星星1 小时前
【找回mysql被删除数据】
数据库·sql·mysql
Awna1 小时前
MySQL 存储空间与索引运维实战:从空间排查到在线扩容
android·运维·mysql
__zRainy__1 小时前
Node系列 · ORM:Sequelize 模型
数据库·后端·node.js
whcyhhh1 小时前
头歌实践教学平台:数据科学与大数据技术导论(五)
大数据·数据库·python
大鹏说大话2 小时前
用 Python 与 DeviceAtlas 构建自动化设备数据库
数据库·python·自动化
肠畔码农2 小时前
深度解密 Redis 核心引擎:从 MULTI/EXEC 事务机制到 EVAL 脚本的原子性演进
数据库·redis·junit
lailai04102 小时前
定制化企业网盘与标准化方案
数据库
PHP实战开发录2 小时前
AI接口结构化输出解析异常排查记录
数据库·安全·ai·php·开发