MySQL 学习笔记

SQL 语法

SQL 通用语法:

  1. 单行或多行书写,以分号结尾。
  2. 可用空格或缩进增加可读性。
  3. MySQL 数据库的 SQL 语句不区分大小写,关键字规范使用大写。
  4. 注释:
    1. 单行:#--
    2. 多行:/**/

SQL 分类

分类 全称 说明
DDL Data Definition Language 数据定义语言,用来定义数据库对象(数据库,表,字段)
DML Data Manipulation Language 数据操作语言,用来对数据库表中的数据进行增删改
DQL Data Query Language 数据查询语言,用来查询数据库中表的记录
DCL Data Control Language 数据控制语言,用来创建数据库用户、控制数据库的访问权限

DDL

数据定义语言,用来定义数据库对象(数据库,表,字段)

数据库操作

查询

查询所有数据库:SHOW DATABASES;

查询当前数据库:SELECT DATABASE();

创建

CREATE DATABASE[IF NOT EXISTS] 数据库名[ DEFAULT CHARSET 字符集][ COLLATE 排序规则];

字符集 utf8 只支持 3 个字节,utf8mb4 支持 4 字节。

删除

DROP DATABASE [IF EXISTS]数据库名;

使用

USE 数据库名

表操作-查询

查询当前数据库所有表SHOW TABLES;

查询表结构DESC 表名; ,默认不显示建立表时字段的注释信息。
b2ee172f6a2edd0c8027e71a0f2e34dc.png

查询指定表详细信息的建表语句SHOW CREATE TABLE 表名;
265e6ce45d03c360fea634a2de2e0160.png

表操作-创建

d51281656f455bab3708aaa94b8fc0ba.png

1
2
3
4
5
6
create table tb_user(
-> id int comment '编号',
-> name varchar(50) comment '姓名',
-> age int comment '年龄',
-> gender varchar(1) comment '性别'
-> ) comment '用户信息';

表操作-数据类型

MySQL 中数据类型主要分为三类:数值类型、字符串类型、日期时间类型。

数值类型

bf89d1828468524ab3a91803a7e58955.png

DECIMAL 类型需要指定 精度(有效位数) 和 标度(小数位数)。如:123.45,精度为 5,标度为 2。

示例:

无符号小整数:age TINYINT UNSIGNED

指定长度浮点数:score DOUBLE(4,1) ,代表精度为 4,标度为 1。

字符串类型

618334a9d4c7ff5e3f8b2c02d1c698f3.png

示例:

CHARVARCHAR 使用时需要有一个参数,即 CHAR(10) ,参数指当前字符串能够存储的最大长度。指定 10,即最多能存储十个字符。

定长字符串 CHAR(性能好) 即使存一个字符也会占用 10 个字符空间,未占用字符空间会用空格补齐。也就是默认大小 10 字节已被分配。

不定长字符串 VARCHAR (性能较差)会根据所存储的内容计算占用多少字节空间,存储 1 字节就占用 1 字节。动态分配 10 字节,最高 10 字节。

日期时间类型

03ee943ea42c53bbed6b82fbd92f00cb.png

表操作-修改

添加字段ALTER TABLE 表名 ADD 字段名类型(长度)[COMMENT注释][约束];

修改某字段数据类型ALTER TABLE 表名 MODIFY 字段名 新数据类型(长度);

修改字段名和字段类型ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型(长度) [COMMENT注释][约束];

删除字段ALTER TABLE 表名 DROP 字段名;

修改表名ALTER TABLE 表名 RENAME TO 新表名;

删除表DROP TABLE [IF EXISTS] 表名;

删除指定表,并重新创建该表TRUNCATE TABLE 表名;

删除表时,表中的数据会被全部删除。

DML

数据操作语言,用来对数据库表中的数据进行增删改。

主要:添加(INSERT)、修改(UPDATE)、删除(DELETE)

添加数据

给指定字段添加数据(只添加一条数据):INSERT INTO 表名(字段名1,字段名2,...)VALUES(值1,值2,.);

给全部字段添加数据(只添加一条数据):INSERT INTO 表名 VALUES(值1, 值2,..);

批量添加数据

INSERT INTO 表名(字段名1,字段名2,...)VALUES(值1,值2,..),(值1,值2,...),(值1,值2,..);

INSERT INTO 表名 VALUES(值1,值2,...),(值1,值2,..),(值1,值2,..);
8f157f316642e2c3b7354b4b44a10afa.png

注意点:

  • 插入数据时,指定的字段顺序需要与值的顺序是一一对应的。

  • 字符串和日期型数据应该包含在引号中。

  • 插入的数据大小,应该在字段的规定范围内。

修改数据

修改表中数据UPDATE 表名 SET 字段名1=值1,字段名2=值2,..[WHERE 条件;

修改语句的条件可以有,也可以没有,如果没有条件(没有指定哪个字段),则会修改整张表的所有数据。
4537ff57a1ef51444de3953720c4c400.png

删除数据

删除表中数据DELETE FROM 表名 [WHERE 条件];

DELETE语句的条件可以有,也可以没有,如果没有条件,则会删除整张表的所有数据。

DELETE语句不能删除某一个字段的值(可以使用UPDATE)。

DQL

DQL英文全称是 DataQueryLanguage(数据查询语言),数据查询语言,用来查询数据库中表的记录。

DQL 语句语法结构(也是编写顺序):

  • SELECT:字段列表。
  • FROM:表名列表。
  • WHERE:条件列表。
  • GROUP BY:分组字段列表。
  • HAVING:分组后条件列表。
  • ORDER BY:排序字段列表。
  • LIMIT:分页参数。

DQL 语句执行顺序:

  • FROM:表名列表。
  • WHERE:条件列表。
  • GROUP BY:分组字段列表。
  • HAVING:分组后条件列表。
  • SELECT:字段列表。
  • ORDER BY:排序字段列表。
  • LIMIT:分页参数。

基本查询

  1. 查询多个字段

    查询指定字段SELECT 字段1,字段2,字段3 FROM 表名;

    查询表中所有数据SELECT * FROM 表名

  2. 设置别名(AS 可以省略,也就是字段后直接加别名)
    SELECT 字段1 [AS 别名1],字段2[AS 别名2] FROM 表名;
    可以给表名也起别名,如 SELECT 字段 FROM 表名 别名 WHERE 别名.age > 10;,表别名用点获取表的字段。
    477302f88ec18bf7bc4eb9bba78c2233.png

  3. 去除重复记录(比如筛选用户来自哪些省份,使用 DISTINCT 则多次出现的省份只会出现一次)

    SELECT DISTINCT 字段列表 FROM 表名;

条件查询

SELECT 字段列表 FROM 表名 WHERE 条件列表;
c474d8aa85a251a73d2deea0aaab973f.png

注意点:

查询字段值(不)为 NULL 的语句:SELECT age FROM tb_user WHERE IS NULL; SELECT age FROM tb_user WHERE IS NOT NULL;

BETWEEN...AND... (闭区间):这里 BETWEEN 后接最小值,AND 后接最大值。

IN(...):类似 OR 的作用。

LIKE 占位符:

  • select * from tb_user where name like '__'; ,like 后是两个下划线,代表匹配两个字符,也就是 name 字段是两个字符的值。

  • select * from tb_user where name like '%m';,name 字段值最后为 m 的值,前面是什么或者几个字符无所谓。

    4717eb354b77c1780138df684b2a179d.png

聚合函数

将一列数据作为一个整体,进行纵向计算。

常见聚合函数:

函数 功能
count 统计数量
max 最大值
min 最小值
avg 平均值
sum 求和

语法:SELECT 聚合函数(字段列表) FROM 表名;

注意点:NULL 值不参与聚合函数计算,如 使用 count 函数,则不会将对应字段的空值算上。

统计整张表的行数量SELECT count(*) FROM tb_user;

条件查询+聚合函数求和SELECT sum(age) FROM tb_user WHERE id > 0;

分组查询

语法:SELECT 字段列表 FROM 表名[WHERE条件]GROUP BY 分组字段名[HAVING 分组后过滤条件];

按性别分组,并统计各性别人数:

b63691cc85e5e1d07bb2e9a4ee45e454.png

按性别分组统计年龄小于19岁的女性数量:

33b85fe42999bba9c3eddd826168f4a0.png

where 和 having 区别:

  • 执行时机不同:where是分组之前进行过滤,不满足where条件,不参与分组;而having是分组之后对结果进行过滤。
  • 判断条件不同:where不能对聚合函数进行判断,而having可以。

注意点:

  • 执行顺序:where -> 聚合函数 -> having
  • 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义。

排序查询

语法:SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1,字段2 排序方式2;

e478e3f46d6a08388bca71b1fe67f8d1.png

排序方式:ASC(升序)、DESC(降序)。升序排序时,ASC 可以省略。

如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序。

分页查询

语法:SELECT 字段列表 FROM 表名 LIMIT 起始索引,查询记录数;,在前面各个语法最后。

56e75a97ab48b6970d079583dca39b5d.png

  • 起始索引从0开始,起始索引=(查询页码-1)* 每页显示记录数
  • 分页查询是数据库的方言,不同的数据库有不同的实现,MySQL中是LIMIT。
  • 如果查询的是第一页数据,起始索引可以省略,直接简写为 limit 10

DCL

DCL英文全称是DataControlLanguage(数据控制语言),用来管理数据库用户、控制数据库的访问权限。

管理用户

  1. 查询用户

    USE mysql;SELECT * FROM user;

  2. 创建用户

    CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';

    CREATE USER '用户名'@'%' IDENTIFIED BY '密码'; 这里的 % 指的是可以可以任意主机访问该数据库,如果是 localhost 则该用户只允许本地访问数据库。

  3. 修改用户密码

    ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql_native_password BY '新密码';

  4. 删除用户

    DROP USER '用户名'@'主机名';

权限控制

常用权限如下:

8402d0cdbf7a44a7164e404db03dc2a0.png

  1. 查询权限

    SHOW GRANTS FOR '用户名'@'主机名';

  2. 授予权限

    GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名';,如果 数据库名.表名*.* 则代表所有数据库。

  3. 撤销权限

    REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机名';

注意点:

  • 多个权限之间,使用逗号分隔

  • 授权时,数据库名和表名可以使用 * 进行通配,代表所有。

函数

使用语法SELECT 函数(参数);

字符串函数

f27a80867bef8c578c84d8c626def1e8.png

使用示例:

update tb_user set id=lpad(id,3,'0');,需要注意,id 这一列需要是字符串类型,如 VARCHAR(3)

这个语句试图将 id 列的每个值转换为字符串,并在其左侧填充零直到其总长度为3。如果 id 的值本身就是一个长度大于或等于3的数字,那么 LPAD() 函数将不会填充任何零,因为原有的长度已经满足或超过了指定的长度。此外,如果 id 是一个整数类型的列,MySQL可能会尝试将结果转换回整数,这样填充的零就会被去掉。

数值函数

9d0bc995fe2f5114df6c6f0a262e391b.png

使用示例:

select ROUND(3.45,2); 本来就是两位小数,结果为 3.45。如果是 select ROUND(3.456,2);,结果是 3.46。

生成六位随机数示例:

SELECT lpad(ROUND(RAND()*1000000,0),6,'0');

日期函数

69479893c07c0e0392867e85e736d159.png

使用示例:

select DAY(NOW());

select DATE_ADD(now(), INTERVAL 10 YEAR);

select datediff('2024-05-10', '2024-05-06'); :注意,这里是前者减后者

流程函数

在 SQL 语句实现条件筛选,提高语句效率。

2e55dc69cc4e0a4ca245754f5ad0e91b.png

使用示例:

SELECT IFNULL(null, 'default'):此时第一个值为 null,因此返回 default。如果第一个值是 '' 空字符串,那么也不是 null,也就是会返回空字符串。

1
2
3
4
select
name,
(case gender when '男' then '帅哥' when '女' then '美女' else 'error' end) as '称呼'
from tb_user;

7c6f82471fccd0baae7f2555396fbb02.png

1
2
3
4
select
name,
(case when age<19 then '永远18' when age>=19 then '已经老了' else 'error' end) as '岁数'
from tb_user;

60b4bdfd4d1456241f15224ff5cf5d95.png

约束

约束是作用于表中字段上的规则,用于限制存储在表中的数据。

目的:保证数据库中数据的正确、有效性和完整性。

分类:

约束 描述 关键字
非空约束 限制该字段的数据不能为null NOT NULL
唯一约束 保证该字段的所有数据都是唯一、不重复的 UNIQUE
主键约束 主键是一行数据的唯一标识,要求非空且唯一 PRIMARY KEY
默认约束 保存数据时,如果未指定该字段的值,则采用默认值 DEFAULT
检查约束(8.0.16版本之后) 保证字段值满足某一个条件 CHECK
外键约束 用来让两张表的数据之间建立连接,保证数据的一致性和完整性 FOREIGN KEY

注意点:约束是作用于表中字段上的,可以在创建表/修改表的时候添加约束。

创建带有约束的表

f5bfff5ec06a5423fb14c133bf85dc34.png

1
2
3
4
5
6
7
create table userdata(
id int PRIMARY KEY AUTO_INCREMENT comment '主键',
name varchar(10) NOT NULL UNIQUE comment '姓名',
age int CHECK( age > 0 && age < 120 ) comment '年龄',
status char(1) DEFAULT '1' comment '状态',
gender char(1) comment '性别'
) comment '用户表';

AUTO_INCREMENT:第一个值为 1,然后自增。新增的数据 id 字段总会比已有最大 id 字段值大 1。使用 insert into 语句插入时,如果不符合约束条件,虽然数据不会被插入,但是 id 值会增加,导致之后插入新的有效值的 id 相对更大。

假设你有一个名为 users 的表,其中 id 是自动递增的主键。

  1. 当前 AUTO_INCREMENT 的值是 1。
  2. 你尝试插入一行数据,但由于某种约束条件(比如唯一性约束)没有被满足,插入操作失败。例如,你尝试插入一个已经存在的电子邮件地址。
  3. 尽管插入操作失败,AUTO_INCREMENT 的值仍然会增加到 2。
  4. 接着你尝试插入另一个新用户,这次满足所有约束条件,插入操作成功。但是,这个新用户的 id 值将是 2,而不是你预期的 1。

这种现象通常被称为 “自动递增间隙” 或 “auto-increment gap”。在某些情况下,这可能会导致 id 值变得不连续,出现间隙。最好插入数据之前先验证数据是否符合所有约束条件,确保只有当所有条件满足时才尝试插入。

1
insert into userdata(name, age, status, gender) values('Tom', 18, '1', '男'),('Marry', 20, '0', '女'); # 名字不能重复了,也不能为 null,被约束了。

4719ed96850b2f958181fda9fd2fbe8f.png

外键约束

外键用来让两张表的数据之间建立连接,从而保证数据的一致性和完整性。

具有外键的表称为 子表,外键所关联的表称为 父表,也成为 主表 或 从表。

07e3e9da8e472a8c48a732459bde44c6.png

注意:目前上述的两张表,在数据库层面,并未建立外键关联,所以是无法保证数据的一致性和完整性的。

建立外键关联

创建表时添加外键:

1
2
3
4
5
CREATE TABLE 表名(
字段名 数据类型,
...
[CONSTRAINT] [外键名称] FOREIGN KEY (外键字段名) REFERENCES 主表(主表列名)
);

创建表后添加外键:

ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名)REFERENCES 主表(主表列名);,外键字段名也就是子表的字段。

示例:

alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id);

上述命令解释为:emp 表建立了名为 fk_emp_dept_id 的外键,emp 表的 dept_id 列与 dept 表的 id 列建立外键关联。

命令的作用:当要删除主表的数据时,如果存在从表有与主表的外键关联,则不允许删除。

删除外键

ALTER TABLE 表名 DROP FOREIGN KEY 外键名称;

外键删除/更新行为规则

默认行为就是 NO ACTION/RESTRICT

55af636675cc59da33e9137c2bc896f1.png

CASCADE:在外键更新时,也会同步更新子表。如主表中修改了 id 为 2,从表关联了这个 id,则从表也会与主表同步修改为 2。如果主表中删除了对应的外键 id,那么从表相应的 id 整行也会被删除。

ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY(外键字段)REFERENCES 主表名(主表字段名)ON UPDATE CASCADE ON DELETE CASCADE;

SET NULL:在外键删除时,会将子表中的关联值置为 NULL。

ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY(外键字段)REFERENCES 主表名(主表字段名)ON UPDATE SET NULL ON DELETE SET NULL;

可以在创建表时指定外键删除/更新规则:

1
2
3
4
5
6
7
CREATE TABLE child_table (
id INT PRIMARY KEY,
parent_id INT,
FOREIGN KEY (parent_id) REFERENCES parent_table(id)
ON DELETE CASCADE -- 当父表记录被删除时,子表的相关记录也会被删除
ON UPDATE CASCADE -- 当父表记录被更新时,子表的相关记录也会被更新
);

多表查询

项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,基本上分为三种:一对多(多对一)、多对多、一对一

一对多(多对一):

例如:部门和员工的关系,一个部门对应多个员工,一个员工对应一个部门。

实现:在多的一方建立外键,指向一的一方的主键。

b211c266f5c02b3fd9b4d726fe39fa13.png

多对多:

例如:学生和课程的关系。一个学生可以选修多门课程,一门课程亦可以供多个学生选择。

实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键。

531cfe542ba875eba46dec67993b0dce.png

一对一:

例如:用户与用户详情的关系(原本存放在一张表中,此时拆分为两张表,并通过外键关联)。一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情字段放在另一张表中,以提升操作效率。

实现:任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE)。

33478f3f042e042cd719af257556acf6.png

多表查询概述

从多张表中查询数据。以下内容示例如上述 一对多 的图中的表。

笛卡尔积:笛卡尔乘积是指在数学中,两个集合A集合和B集合的所有组合情况。(在多表查询时,需要消除无效的笛卡尔积,也就是消除重复的无效数据。如一个员工仅对应一个部门,此时使用 where 指定员工的部门字段与部门的 id 字段相同 )

示例:select * from emp,dept where emp.dept_id = dept.id;,emp(员工表) 和 dept(部门表) 是两张表,条件是员工的部门 id 需要等于部门表的 id。否则,会出现笛卡尔积(也就是所有组合)。

以下为 emp 表:

6108c2e1bc5cebf0c3cfb6fc15f56a80.png

以下为 dept 表:

42cda521d7a152ed971a7a585ba942d0.png

多表查询分类

  • 连接查询

    • 内连接:相当于查询A、B交集部分数据。
    • 外连接:
      • 左外连接:查询左表所有数据,以及两张表交集部分数据
      • 右外连接:查询右表所有数据,以及两张表交集部分数据
    • 自连接:当前表与自身的连接查询,自连接必须使用表别名
  • 子查询

    fcc9b3e824525e08da5514ed13599a1c.png

内连接查询

相当于查询A、B交集部分数据。不能查出 null 值的信息。

内连接分为:隐式内连接显式内连接,仅是语法不同。

注意:如果给表名起了别名,则不能再用原表名加 . 的形式拿到字段,只能用别名加 . 的方式。

隐式内连接

语法:SELECT 字段列表 FROM 表1,表2 WHERE 条件;

示例:查询每一个员工的姓名,及关联的部门的名称(隐式内连接实现)。

select emp.name , dept.name from emp , dept where emp.dept_id = dept.id ;,如果 emp.dept_id 是 null,则查不出,需用外连接。

起别名:select e.name,d.name from emp e , dept d where e.dept_id = d.id;,起别名后 where 后不能用 emp.dept_id 指定字段名。

显式内连接

语法:SELECT 字段列表 FROM 表1[INNER]JOIN 表2 ON 连接条件;

示例:查询每一个员工的姓名,及关联的部门的名称(显式内连接实现)。

select e.name, d.name from emp e inner join dept d on e.dept_id = d.id;

起别名:select e.name,d.namee from emp e join dept d on e.dept_id = d.id;,inner 可以省略。

外连接查询

分为 左外连接右外连接

左外连接

相当于查询表1(左表)的所有数据包含表1和表2交集部分的数据。也就是完全包含左表的数据(包括 null)。以左表为主。

常用,因为右外连接也可以改为左外连接,可相互替换。

语法:SELECT 字段列表 FROM 表1 LEFT [OUTER]JOIN 表2 ON 条件;

示例:emp 表的所有数据,和对应的部门信息(左外连接)。

select e.*,d.name from emp e left outer join dept d on e.dept_id = d.id;,outer 可省略。

右外连接

相当于查询表2(右表)的所有数据包含表1和表2交集部分的数据。也就是完全包含右表的数据(包括 null)。以右表为主。

语法:SELECT 字段列表 FROM 表1 RIGHT [OUTER]JOIN 表2 ON 条件;

示例:查询 dept 表的所有数据,和对应的员工信息(右外连接)。

select d.*, e.* from emp e right outer join dept d on e.dept_id = d.id;

用左外连接替换,也就是把右表改为左表:select d.*,e.* from dept d left outer join emp e on e.dept_id = d.id;

自连接查询

可以用内连接(不包括 null),也可以用外连接(完整数据,包括 null)。

把当前表看作两张表。

语法:SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件...;,通过给同一表起两个别名,看作两张表,可以使用内连接也可以使用外连接。

示例:查询员工(emp.name)及其所属领导的名字(emp.managerid)。

select a.name ,b.name from emp a, emp b where a.managerid = b.id;,使用内连接。

示例:查询所有员工emp及其领导的名字emp,如果员工没有领导,也需要查询出来。

select a.name '员工',b.name '领导' from emp a left join emp b on a.managerid = b.id; ,使用外连接。

联合查询

对于 union [all] 查询,就是把多次查询的结果合并起来,形成一个新的查询结果集。

语法:

1
2
3
SELECT 字段列表 FROM 表A ...
UNION [ALL]
SELECT 字段列表 FROM 表B ...;
  • 对于联合查询的多张表的列数必须保持一致,字段类型也需要保持一致。

  • union all会将全部的数据直接合并在一起,union会对合并之后的数据(也就是针对于两个条件都满足的数据)去重。

示例:薪资低于5000的员工,和年龄大于50岁的员工全部查询出来。

1
2
3
select * from emp where salary < 5000
union all -- 这里加 all 代表不去重,可能有满足两个条件的重复数据,不加 all 则是去重。
select * from emp where age > 50;

子查询

SQL语句中嵌套 SELECT 语句,称为嵌套查询,又称子查询。

语法:SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2);

子查询外部的语句可以是INSERT/UPDATE/DELETE/SELECT的任何一个。

  • 根据子查询结果不同,分为:

    • 标量子查询 (子查询结果为单个值)

    • 列子查询(子查询结果为一列)

    • 行子查询(子查询结果为一行)

    • 表子查询(子查询结果为多行多列)

  • 根据子查询位置,分为:WHERE之后、FROM之后、SELECT之后。

标量子查询

子查询返回的结果是单个值(数字、字符串、日期等),最简单的形式,这种子查询成为标量子查询。

常用的操作符:= <> > >= < <=<> 是不等于。

示例:查询“销售部”的所有员工信息。

  • 不使用子查询:
    1. 查询 “销售部” 部门ID:select id from dept where name='销售部';,结果为 4。
    2. 据销售部部门ID,查询员工信息:select * from emp where dept_id = 4;
  • 使用子查询(将上述两条语句合并):select * from emp where dept_id =(select id from dept where name = '销售部');

示例:查询在“方东白”入职之后的员工信息。

select * from emp where entrydate > (select entrydate from emp where name ='方东白');

列子查询

子查询返回的结果是一列(可以是多行),这种子查询称为列子查询。

常用的操作符:IN、NOT IN、ANY、SOME、ALL

操作符 描述
IN 在指定的集合范围之内,多选一
NOT IN 不在指定的集合范围之内
ANY 子查询返回列表中,有任意一个满足即可
SOME 与ANY等同,使用SOME的地方都可以使用ANY
ALL 子查询返回列表的所有值都必须满足

示例:查询“销售部”和“市场部”的所有员工信息。

  • 不使用列子查询:
    1. 查询“销售部”和“市场部”的部门ID:select id from dept where name ='销售部' or name = '市场部';,得到的是 (2,4)
    2. 据部门ID,查询员工信息:select * from emp where dept_id in (2,4);
  • 使用列子查询:select * from emp where dept_id in (select id from dept where name='销售部' or name = '市场部')

示例:查询比财务部所有人工资都高的员工信息。

select * from emp where salary > all (select salary from emp where dept_id = (select id from dept where name = '财务部');,all 表示子查询得到的结果都需要满足小于 salary。这条语句实现了三个查询,最右侧是查询财务部部门 id,接着中间通过部门 id 查询该部门所有 salary,最后进行条件查询。

示例:查询比研发部其中任意一人工资高的员工信息。

select * from emp where salary > any ( select salary from emp where dept_id = (select id from dept where name = '研发部') );,any 代表子表查询得到的值都满足条件。

行子查询

子查询返回的结果是一行(可以是多列),这种子查询称为行子查询。

常用操作符:=, <>, IN, NOT IN

示例:查询与”张无忌”的薪资及直属领导相同的员工信息。

  • 不使用行子查询

    1. 查询“张无忌”的薪资及直属领导:select salary,managerid from emp where name = '张无忌';,得到12500,1

    2. 查询与“张无忌”的薪资及直属领导相同的员工信息:

      select * from emp where salary = 12500, managerid = 1;

      可以替换为:select * from emp where (salary,managerid) = (12500,1);,类似元组形式。

  • 使用行子查询:select * from emp where (salary,managerid) = (select salary, managerid from emp where name = '张无忌');,行子查询返回 (12500,1),然后与条件比较。这里子查询返回的是单行数据,则可以用 = ,多行数据需要用 IN

表子查询

子查询返回的结果是多行多列,这种子查询称为表子查询。

常用操作符:IN

示例:查询与“鹿杖客”“宋远桥”的职位和薪资相同的员工信息。

表子查询:select * from emp where(job,salary) in ( select job,salary from emp where name ='鹿杖客' or name ='宋远桥' );,这里子查询返回的是多行多列的表,所以用 IN

示例:查询入职日期是”2006-01-01”之后的员工信息,及其部门信息。

  1. 入职日期是”2006-01-01”之后的员工信息:select * from emp where entrydate > '2006-01-01';
  2. 查询这部分员工,对应的部门信息:select e.*,d.* from (select * from emp where entrydate >'2006-01-01') e left join dept d on e.dept_id = d.id ;,这里将 1 查询返回的表作为另一张表,在返回的表中进行再次查询,又因为要查询出 e.dept_id 为 null 的数据,所以用 左外连接。

多表查询练习

48、49待看

事务

事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。

b8e76159ecfa0eab98773a6257351664.png

示例(转账操作实现):

665371eb773712a4c1a2b6181bff9d08.png

事务操作

修改事务的提交方式

查看/设置事务提交方式SELECT @@autocommit;,如果是 1,则是自动提交;0 则是手动提交。SET @@autocommit=0;,设置为手动提交。

提交事务COMMIT;,提交操作之前,可以有/执行多个 SQL 语句。

回滚事务ROLLBACK;

开启事务

开启事务:START TRANSACTIONBEGIN ,命令的作用是代表要手动控制事务。

提交事务COMMIT;

回滚事务ROLLBACK;

事务四大特性(ACID)

  • 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
  • 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
  • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
  • 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。

并发事务问题

fb701fda23f88e69b5244beae06359cb.png

脏读

事务 A 还没有 commit ,但是这时候事务 B 进行查询,获取到了事务 A update 但是没有 commit 的数据。这就是脏读

00d433c65e8f314770b268ccb9f95854.png

不可重复读

事务 A 的第一步查询了 id 为 1 的数据,这时事务 B 更新了 id 为 1 的数据并 commit 了,接着事务 A 再次查询了 id 为 1 的数据,但是两次查询发现了 id 为 1 的数据不一致,这就是不可重复读

也就是在相同事务中,如在事务 A 中,重复读取 id 为 1 的数据出现不一致就是不可重复读。而在事务 A 提交后,再次查询 id 为 1 的数据,此时才能查到事务 B 提交修改的 id 为 1 的数据,这就是没有出现不可重复读(事务隔离级别Repeatable Read)。

59a96d2155faba2cc370f4061803275d.png

幻读

首先事务 A 查询 id 为 1 的数据,发现数据库中没有,但这时事务 B 向数据库 commit 了 id 为 1 的数据,接着事务 A 因为之前没有查询到 id 为 1 的数据因此进行插入 id 为 1 的数据,但是插入失败了,因为事务 B 已经插入了,导致主键冲突。事务 A 又解决了不可重复读的问题,但是事务 A 第三步又进行查询 id 为 1 的数据,又发现好像没有数据,这就是幻读

在解决了不可重复读的问题后,事务 A 向数据库中查询 id 为 1 的数据,没有查询到,但是事务 B 实际 insert 了 id 为 1 的数据并且 commit 了,这时候事务 A 插入数据又提示主键重复,但实际查询 id 为 1 的数据查询不到,因为事务 A 没有 commit 结束当前事务(因为解决了不可重复读),所以查询不到。这就出现了幻读。

607fd9ca993d17dd6378775076a8c41a.png

事务的隔离级别

解决并发事务问题。

下图打勾的代表事务的隔离级别所存在的并发问题(事务的隔离级别从上到下,从低到高):

78075e7353d0631ae48a91c0ece02073.png

MySQL 默认隔离级别是 Repeatable ReadRead uncommitted 性能最好但并发安全性差,Serializable 性能最差但并发安全性好。

查看事务隔离级别SELECT @@TRANSACTION_ISOLATION;

设置事务隔离级别SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE};,SESSION 指的是当前客户端所有会话窗口有效,GLOBAL 是对所有客户端会话窗口有效。

当设置为 Serializable 事务级别时,两个事务同时操作同一数据库时,如 A 和 B 事务,A 查询了 id 为 1 的数据,然后 B 插入了 id 为 1 的数据,此时 B 会被阻塞,会等到 A commit,也就是 A 结束事务后,B 才能操作。