1151 lines
42 KiB
Markdown
1151 lines
42 KiB
Markdown
---
|
||
id: 基础
|
||
title: 基础
|
||
sidebar_position: 1
|
||
data: 2022年5月11日
|
||
---
|
||
|
||
## 数据库
|
||
|
||
| 名称 | 全称 | 简称 |
|
||
| :------------- | :----------------------------------------------------------- | :-------------------------------- |
|
||
| 数据库 | 存储数据的仓库,数据是有组织的进行存储 | DataBase(DB) |
|
||
| 数据库管理系统 | 操纵和管理数据库的大型软件 | DataBase Management System (DBMS) |
|
||
| SQL | 操作关系型数据库的编程语言,定义了一套操作 关系型数据库统一标准 | Structured Query Language (SQL) |
|
||
|
||
## 数据模型
|
||
|
||
关系型数据库(RDBMS):建立在关系模型基础上,由多张相互连接的二维表组成的数据库。
|
||
而所谓二维表,指的是由行和列组成的表,可以通过一列关联另外一个表格中的某一列数据,如下图。
|
||
|
||
特点:
|
||
|
||
1. 使用表存储数据,格式统一,便于维护。
|
||
2. 使用 SQL 语言操作,标准统一,使用方便。
|
||
|
||
![img](https://static.7wate.com/img/2022/05/11/6bbfdea71bff2.png)
|
||
|
||
常见的 MySQL、Oracle、DB2、SQLServer 这些都是属于关系型数据库,里面都是基于二维表存储数据的。
|
||
|
||
无论我们使用哪一个关系型数据库,最终在操作时都是使用 SQL 语言来进行统一操作,
|
||
因为我们接下来学习的 SQL 语言,是操作关系型数据库的统一标准。
|
||
|
||
![服务器](https://static.7wate.com/img/2022/05/11/b7d5d1651689a.png)
|
||
|
||
## SQL
|
||
|
||
全称 Structured Query Language,结构化查询语言。操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准。
|
||
|
||
![img](https://static.7wate.com/img/2022/05/11/9459b0983c451.png)
|
||
|
||
1. SQL 语句可以单行或多行书写,以分号结尾。
|
||
2. SQL 语句可以使用空格 / 缩进来增强语句的可读性。
|
||
3. MySQL 数据库的 SQL 语句不区分大小写,**关键字建议使用大写**。
|
||
4. 注释:
|
||
1. 单行注释:-- 注释内容 或 # 注释内容
|
||
2. 多行注释:/ *注释内容* /
|
||
|
||
SQL 语句,根据其功能,主要分为四类:DDL、DML、DQL、DCL。
|
||
|
||
| 分类 | 说明 |
|
||
| :--- | :----------------------------------------------------- |
|
||
| DDL | 数据定义语言,用来定义数据库对象 (数据库,表,字段) |
|
||
| DML | 数据操作语言, 用来对数据库表中的数据进行增删改 |
|
||
| DQL | 数据查询语言,用来查询数据库中表的记录 |
|
||
| DCL | 数据控制语言,用来创建数据库用户、控制数据库的访问权限 |
|
||
|
||
## DDL
|
||
|
||
数据定义语言,定义数据库对象 (数据库,表,字段)
|
||
|
||
### 数据库操作
|
||
|
||
查询所有数据库
|
||
|
||
```sql
|
||
SHOW DATABASES
|
||
```
|
||
|
||
使用某个数据库
|
||
|
||
```sql
|
||
USE 数据库名;
|
||
```
|
||
|
||
查询当前数据库
|
||
|
||
```sql
|
||
SELECT DATABASE();
|
||
```
|
||
|
||
创建数据库
|
||
|
||
UTF8 字符集长度为 3 字节,有些符号占 4 字节,所以创建数据库时推荐用 utf8mb4 字符集
|
||
|
||
```sql
|
||
CREATE DATABASE [ IF NOT EXISTS ] 数据库名 [ DEFAULT CHARSET 字符集] [COLLATE 排序规则 ];
|
||
```
|
||
|
||
删除数据库
|
||
|
||
```sql
|
||
DROP DATABASE [ IF EXISTS ] 数据库名;
|
||
```
|
||
|
||
### 数据表操作
|
||
|
||
创建表
|
||
|
||
最后一个字段后面是没有逗号的。
|
||
|
||
```sql
|
||
CREATE TABLE 表名(
|
||
字段1 字段1类型 [COMMENT 字段1注释],
|
||
字段2 字段2类型 [COMMENT 字段2注释],
|
||
字段3 字段3类型 [COMMENT 字段3注释],
|
||
...
|
||
字段n 字段n类型 [COMMENT 字段n注释]
|
||
)[ COMMENT 表注释 ];
|
||
```
|
||
|
||
查询表结构
|
||
|
||
```sql
|
||
DESC 表名;
|
||
```
|
||
|
||
查询当前数据库所有表,**必须处在数据库中**
|
||
|
||
```sql
|
||
SHOW TABLES;
|
||
```
|
||
|
||
查询某表的建表语句
|
||
|
||
```sql
|
||
SHOW CREATE TABLE 表名;
|
||
```
|
||
|
||
表中添加字段
|
||
|
||
```sql
|
||
ALTER TABLE 表名 ADD 字段名 类型(长度) [COMMENT 注释] [约束];
|
||
-- 例如
|
||
ALTER TABLE emp ADD nickname varchar(20) COMMENT '用户昵称';
|
||
```
|
||
|
||
修改数据类型
|
||
|
||
```sql
|
||
ALTER TABLE 表名 MODIFY 字段名 新数据类型(长度);
|
||
-- 例如
|
||
ALTER TABLE emp MODIFY nickname varchar(30);
|
||
```
|
||
|
||
修改字段名和字段类型
|
||
|
||
```sql
|
||
ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型(长度) [COMMENT 注释] [约束];
|
||
-- 例如
|
||
ALTER TABLE emp CHANGE nickname name varchar(40);
|
||
```
|
||
|
||
删除字段
|
||
|
||
```sql
|
||
ALTER TABLE 表名 DROP 字段名;
|
||
-- 例如
|
||
ALTER TABLE emp drop nickname;
|
||
```
|
||
|
||
修改表名
|
||
|
||
```sql
|
||
ALTER TABLE 表名 RENAME TO 新表名;
|
||
-- 例如
|
||
ALTER TABLE emp RENAME TO empNew;
|
||
```
|
||
|
||
删除表
|
||
|
||
```sql
|
||
DROP TABLE [IF EXISTS] 表名;
|
||
-- 例如
|
||
DROP TABLE [IF EXISTS] emp;
|
||
```
|
||
|
||
删除表,并重新创建该表
|
||
|
||
```sql
|
||
TRUNCATE TABLE 表名;
|
||
-- 例如
|
||
TRUNCATE TABLE emp;
|
||
```
|
||
|
||
## DML
|
||
|
||
数据操作语言,用来对数据库表中的数据进行增删改
|
||
|
||
字符串和日期类型数据应该包含在引号中
|
||
|
||
插入的数据大小应该在**字段的规定范围**内
|
||
|
||
指定字段添加数据
|
||
|
||
```sql
|
||
INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...);
|
||
```
|
||
|
||
全部字段添加数据
|
||
|
||
```sql
|
||
INSERT INTO 表名 VALUES (值1, 值2, ...);
|
||
```
|
||
|
||
批量添加数据
|
||
|
||
```sql
|
||
--指定字段
|
||
INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...);
|
||
--全部字段
|
||
INSERT INTO 表名 VALUES (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...);
|
||
```
|
||
|
||
修改数据
|
||
|
||
```sql
|
||
UPDATE 表名 SET 字段名1 = 值1, 字段名2 = 值2, ... [ WHERE 条件 ];
|
||
-- 例如
|
||
UPDATE emp SET nickname = '乐心湖' WHERE id = 1;
|
||
```
|
||
|
||
删除数据
|
||
|
||
```sql
|
||
DELETE FROM 表名 [ WHERE 条件 ];
|
||
```
|
||
|
||
## DQL
|
||
|
||
数据查询语言,用来查询数据库中表的记录
|
||
|
||
### 基础查询
|
||
|
||
```sql
|
||
SELECT
|
||
字段列表
|
||
FROM
|
||
表名字段
|
||
WHERE
|
||
条件列表
|
||
GROUP BY
|
||
分组字段列表
|
||
HAVING
|
||
分组后的条件列表
|
||
ORDER BY
|
||
排序字段列表
|
||
LIMIT
|
||
分页参数
|
||
```
|
||
|
||
查询所有字段
|
||
|
||
```sql
|
||
SELECT * FROM 表名;
|
||
-- 实际开发中尽量不要写 * 而是建议把每个字段都写出来
|
||
SELECT id,name,age... FROM emp;
|
||
```
|
||
|
||
查询结果字段带别名
|
||
|
||
```sql
|
||
SELECT 字段1 [ AS 别名1 ], 字段2 [ AS 别名2 ], 字段3 [ AS 别名3 ], ... FROM 表名;
|
||
SELECT 字段1 [ 别名1 ], 字段2 [ 别名2 ], 字段3 [ 别名3 ], ... FROM 表名;
|
||
```
|
||
|
||
去除重复记录
|
||
|
||
```sql
|
||
SELECT DISTINCT 字段列表 FROM 表名;
|
||
```
|
||
|
||
### 条件查询
|
||
|
||
```sql
|
||
SELECT 字段列表 FROM 表名 WHERE 条件列表;
|
||
```
|
||
|
||
条件列表
|
||
|
||
| 比较运算符 | 功能 |
|
||
| :-------------- | :------------------------------------------ |
|
||
| > | 大于 |
|
||
| >= | 大于等于 |
|
||
| < | 小于 |
|
||
| <= | 小于等于 |
|
||
| = | 等于 |
|
||
| `<>` 或 != | 不等于 |
|
||
| BETWEEN … AND … | 在某个范围内(含最小、最大值) |
|
||
| IN(…) | 在 in 之后的列表中的值,多选一,或的意思 |
|
||
| LIKE 占位符 | 模糊匹配(_匹配单个字符,% 匹配任意个字符) |
|
||
| IS NULL | 是 NULL |
|
||
|
||
| 逻辑运算符 | 功能 | | |
|
||
| :--------- | :----------------------- | ---- | ---------------------------- |
|
||
| AND 或 && | 并且(多个条件同时成立) | | |
|
||
| OR 或 \ | \ | | 或者(多个条件任意一个成立) |
|
||
| NOT 或 ! | 非,不是 | | |
|
||
|
||
```sql
|
||
-- 年龄等于30
|
||
select * from emp where age = 30;
|
||
-- 没有身份证
|
||
select * from emp where idcard is null or idcard = '';
|
||
-- 有身份证
|
||
select * from emp where idcard;
|
||
select * from emp where idcard is not null;
|
||
-- 不等于
|
||
select * from emp where age != 30;
|
||
-- 年龄在20到30之间
|
||
select * from emp where age between 20 and 30;
|
||
select * from emp where age >= 20 and age <= 30;
|
||
-- 下面语句不报错,但查不到任何信息,因此一定要从先写小再写大
|
||
select * from emp where age between 30 and 20;
|
||
-- 性别为女且年龄小于30
|
||
select * from emp where age < 30 and gender = '女';
|
||
-- 年龄等于25或30或35
|
||
select * from emp where age = 25 or age = 30 or age = 35;
|
||
select * from emp where age in (25, 30, 35);
|
||
-- 姓名为两个字
|
||
select * from emp where name like '__';
|
||
-- 姓名第一个字为钟
|
||
select * from emp where name like '钟%';
|
||
-- 姓名最后第一个字为雪
|
||
select * from emp where name like '%雪';
|
||
-- 身份证最后为X
|
||
select * from emp where idcard like '%X';
|
||
```
|
||
|
||
### 聚合查询
|
||
|
||
将一列数据作为一个整体,进行纵向计算。直接作用于字段。
|
||
|
||
```sql
|
||
SELECT 聚合函数(字段列表) FROM 表名;
|
||
```
|
||
|
||
常见聚合函数如下,注意:所有 null 值不参与聚合运算。
|
||
|
||
| 函数 | 功能 |
|
||
| :---- | :------- |
|
||
| count | 统计数量 |
|
||
| max | 最大值 |
|
||
| min | 最小值 |
|
||
| avg | 平均值 |
|
||
| sum | 求和 |
|
||
|
||
```sql
|
||
-- 统计企业员工数量
|
||
select count(id) from emp;
|
||
-- 统计企业平均年龄
|
||
select avg(age) from emp;
|
||
-- 统计西安地区员工的年龄之和
|
||
select sum(age) from emp where workaddr = '西安';
|
||
```
|
||
|
||
### 分组查询
|
||
|
||
```sql
|
||
SELECT 字段列表 FROM 表名 [ WHERE 条件 ] GROUP BY 分组字段名 [ HAVING 分组后的过滤条件 ];
|
||
```
|
||
|
||
分组往往伴随着聚合
|
||
|
||
where 和 having 的区别:
|
||
|
||
- 执行时机不同:where 是分组之前进行过滤,不满足 where 条件不参与分组;having 是分组后对结果进行过滤。
|
||
- 判断条件不同:where 不能对聚合函数进行判断,而 having 可以。
|
||
|
||
注意:
|
||
|
||
- 执行顺序:where > 聚合函数 > having
|
||
- 分组之后,**查询的字段一般为聚合函数和分组字段**,查询其他字段无任何意义
|
||
|
||
```sql
|
||
-- 根据性别分组,统计男性和女性数量(只显示分组数量,不显示哪个是男哪个是女)
|
||
select count(*) from emp group by gender;
|
||
-- 根据性别分组,统计男性和女性数量
|
||
select gender, count(*) from emp group by gender;
|
||
-- 根据性别分组,统计男性和女性的平均年龄
|
||
select gender, avg(age) from emp group by gender;
|
||
-- 年龄小于45,并根据工作地址分组,获取员工数量
|
||
select workaddr, count(*) from emp where age < 45 group by workaddr;
|
||
-- 年龄小于45,并根据工作地址分组,获取员工数量大于等于3的工作地址
|
||
select workaddr, count(*) address_count from emp where age < 45 group by workaddr having address_count >= 3;
|
||
```
|
||
|
||
### 排序查询
|
||
|
||
如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序
|
||
|
||
```sql
|
||
SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1, 字段2 排序方式2;
|
||
```
|
||
|
||
排序方式
|
||
|
||
- ASC:升序(默认)
|
||
- DESC:降序
|
||
|
||
```sql
|
||
-- 根据年龄升序排序
|
||
select * from emp order by age ASC;
|
||
select * from emp order by age;
|
||
-- 根据年龄对公司的员工进行升序排序,年龄相同,再按照入职时间进行降序排序
|
||
select * from emp order by age ASC, entrydate DESC;
|
||
```
|
||
|
||
### 分页查询
|
||
|
||
```sql
|
||
SELECT 字段列表 FROM 表名 LIMIT 起始索引, 查询记录数;
|
||
```
|
||
|
||
- 起始索引从 0 开始,所以这里有个公式,起始索引 = (查询页码 - 1) * 每页显示记录数
|
||
- **分页查询是数据库的方言,不同数据库有不同实现,MySQL 是 LIMIT**
|
||
- 如果查询的是第一页数据,起始索引可以省略,直接简写 LIMIT 10
|
||
|
||
```sql
|
||
-- 查询第一页数据,展示10条
|
||
select * from emp limit 0, 10;
|
||
-- 查询第二页,每页展示10条
|
||
select * from emp limit 10, 10;
|
||
```
|
||
|
||
### DQL 练习
|
||
|
||
DQL 编写顺序:
|
||
|
||
```sql
|
||
SELECT -> FROM -> WHERE -> GROUP BY -> HAVING -> ORDER BY -> LIMIT
|
||
```
|
||
|
||
DQL 执行顺序:
|
||
|
||
```sql
|
||
FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMIT
|
||
```
|
||
|
||
![img](https://static.7wate.com/img/2022/05/11/e526304cef38f.png)
|
||
|
||
按照需求完成如下 DQL 语句编写
|
||
|
||
```sql
|
||
-- 1.查询年龄为20,21,22,23岁的员工信息。
|
||
select * from emp where age in(20,21,22,23);
|
||
-- 2.查询性别为男,并且年龄在20-40岁(含)以内的姓名为三个字的员工。
|
||
select * from emp where gender = '男' and age between 20 and 40 and name like '___';
|
||
-- 3.统计员工表中,年龄小于60岁的,男性员工和女性员工的人数。
|
||
select gender, count(*) from emp where age < 60 group by gender;
|
||
-- 4.查询所有年龄小于等于35岁员工的姓名和年龄,并对查询结果按年龄升序排序,如果年龄相同按入职时间降序排序。
|
||
select name, age from emp where age < 35 order by age ASC, entrydata DESC;
|
||
-- 5、查询性别为男,且年龄在20-40岁(含)以内的前5个员工信息,对查询的结果按年龄升序排序,年龄相同按入职时间升序排序。
|
||
select * from emp where (gender = '男') and (age >= 20 and age <=40) order by age ASC,entrydata DESC limit 5;
|
||
```
|
||
|
||
## DCL
|
||
|
||
数据控制语言,用来创建数据库用户、控制数据库的访问权限
|
||
|
||
### 用户管理
|
||
|
||
查询用户
|
||
|
||
```sql
|
||
use mysql;
|
||
select * from user;
|
||
```
|
||
|
||
创建用户
|
||
|
||
```sql
|
||
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';
|
||
```
|
||
|
||
修改用户密码
|
||
|
||
```sql
|
||
ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql_native_password BY '新密码';
|
||
```
|
||
|
||
删除用
|
||
|
||
```sql
|
||
DROP USER '用户名'@'主机名';
|
||
```
|
||
|
||
例子
|
||
|
||
```sql
|
||
-- 创建用户lxh,只能在当前主机localhost访问
|
||
create user 'lxh'@'localhost' identified by '123456';
|
||
-- 创建用户test,能在任意主机访问,使用 % 通配符号
|
||
create user 'lxh'@'%' identified by '123456';
|
||
create user 'lxh' identified by '123456';
|
||
-- 修改密码
|
||
alter user 'lxh'@'localhost' identified with mysql_native_password by '123';
|
||
-- 删除用户
|
||
drop user 'lxh'@'localhost';
|
||
```
|
||
|
||
### 权限管理
|
||
|
||
常见的权限如下,更具体需要百度。
|
||
|
||
| 权限 | 说明 |
|
||
| :------------------ | :--------------------- |
|
||
| ALL, ALL PRIVILEGES | 所有权限 |
|
||
| SELECT | 查询数据 |
|
||
| INSERT | 插入数据 |
|
||
| UPDATE | 修改数据 |
|
||
| DELETE | 删除数据 |
|
||
| ALTER | 修改表 |
|
||
| DROP | 删除数据库 / 表 / 视图 |
|
||
| CREATE | 创建数据库 / 表 |
|
||
|
||
```sql
|
||
-- 查询权限:
|
||
SHOW GRANTS FOR '用户名'@'主机名';
|
||
|
||
-- 授予权限:
|
||
GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名';
|
||
|
||
-- 撤销权限:
|
||
REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机名';
|
||
|
||
-- 注意:
|
||
-- 多个权限用逗号分隔
|
||
-- 授权时,数据库名和表名可以用 * 进行通配,代表所有
|
||
```
|
||
|
||
函数是指一段可以直接被另一段程序调用的程序或代码。
|
||
|
||
1. 在企业的 OA 或其他的人力系统中,经常会提供的有这样一个功能,每一个员工登录上来之后都能 够看到当前员工入职的天数。 而在数据库中,存储的都是入职日期,如 2000-11-12,**那如果快速计 算出天数呢?**
|
||
2. 在做报表这类的业务需求中, 我们要展示出学员的分数等级分布。而在数据库中,存储的是学生的 分数值,如 98/75,**如何快速判定分数的等级呢?**
|
||
|
||
以上这些需求都可以在 MySQL 的函数中得到方便解决。
|
||
|
||
## 字符串函数
|
||
|
||
常用的字符串函数
|
||
|
||
| 函数 | 功能 |
|
||
| :------------------------- | :----------------------------------------------------------- |
|
||
| CONCAT(s1, s2, …, sn) | 字符串拼接,将 s1, s2, …, sn 拼接成一个字符串 |
|
||
| LOWER(str) | 将字符串全部转为小写 |
|
||
| UPPER(str) | 将字符串全部转为大写 |
|
||
| LPAD(str, n, pad) | 左填充,用字符串 pad 对 str 的左边进行填充,达到 n 个字符串长度 |
|
||
| RPAD(str, n, pad) | 右填充,用字符串 pad 对 str 的右边进行填充,达到 n 个字符串长度 |
|
||
| TRIM(str) | 去掉字符串头部和尾部的空格 |
|
||
| SUBSTRING(str, start, len) | 返回从字符串 str 从 start 位置起的 len 个长度的字符串,起始索引为 1 |
|
||
|
||
案例:由于业务需求变更,企业员工的工号,统一为 5 位数,目前不足 5 位数的全部在前面补 0。比如: 1 号员 工的工号应该为 00001。
|
||
|
||
```sql
|
||
update emp set workno = LPAD(workno,5,'0');
|
||
```
|
||
|
||
## 数值函数
|
||
|
||
常用的数值函数
|
||
|
||
| 函数 | 功能 |
|
||
| :---------- | :----------------------------------- |
|
||
| CEIL(x) | 向上取整 |
|
||
| FLOOR(x) | 向下取整 |
|
||
| MOD(x, y) | 返回 x/y 的模 |
|
||
| RAND() | 返回 0~1 内的随机数 |
|
||
| ROUND(x, y) | 求参数 x 的四舍五入值,保留 y 位小数 |
|
||
|
||
案例:通过数据库的函数,生成一个六位数的随机验证码。
|
||
|
||
```sql
|
||
-- 通过rand()获取0~1,乘以1000000,通过四舍五入即可。如果未满6位,则补0
|
||
select lpad(round(rand()*1000000, 0), 6, '0');
|
||
```
|
||
|
||
## 日期函数
|
||
|
||
常用日期函数
|
||
|
||
| 函数 | 功能 |
|
||
| :--------------------------------- | :---------------------------------------------------- |
|
||
| CURDATE() | 返回当前日期 |
|
||
| CURTIME() | 返回当前时间 |
|
||
| NOW() | 返回当前日期和时间 |
|
||
| YEAR(date) | 获取指定 date 的年份 |
|
||
| MONTH(date) | 获取指定 date 的月份 |
|
||
| DAY(date) | 获取指定 date 的日期 |
|
||
| DATE_ADD(date, INTERVAL expr type) | 返回一个日期 / 时间值加上一个时间间隔 expr 后的时间值 |
|
||
| DATEDIFF(date1, date2) | 返回起始时间 date1 和结束时间 date2 之间的天数 |
|
||
|
||
案例:查询所有员工的入职天数,并根据入职时间倒叙排序
|
||
|
||
```sql
|
||
select name, datediff(curdate(), entrydate) as dates from emp order by dates desc;
|
||
```
|
||
|
||
## 流程函数
|
||
|
||
常用流程函数
|
||
|
||
| 函数 | 功能 |
|
||
| :----------------------------------------------------------- | :----------------------------------------------------------- |
|
||
| IF(value, t, f) | 如果 value 为 true,则返回 t,否则返回 f |
|
||
| IFNULL(value1, value2) | 如果 value1 不为空,返回 value1,否则返回 value2 |
|
||
| CASE WHEN [val1] THEN [ res1 ] … ELSE [ default ] END | 如果 val1 为 true,返回 res1,… 否则返回 default 默认值 |
|
||
| CASE [expr] WHEN [ val1 ] THEN [ res1 ] … ELSE [ default ] END | 如果 expr 的值等于 val1,返回 res1,… 否则返回 default 默认值 |
|
||
|
||
```sql
|
||
select
|
||
name,
|
||
if(age between 14 and 28, '青年','其他')
|
||
from emp;
|
||
select
|
||
name,
|
||
(case workaddress when '北京市' then '一线城市' when '上海市' then '一线城市' else '二线城市' end) as '工作地址'
|
||
from employee;
|
||
```
|
||
|
||
约束是作用于表中字段上的,用于限制存储在表中的数据。目的是保证数据中数据的正确、有效性和完整性。
|
||
|
||
可以在创建表 / 修改表的时候添加约束。
|
||
|
||
常见约束:
|
||
|
||
| 约束 | 描述 | 关键字 |
|
||
| :----------------------- | :------------------------------------------------------- | :---------------------------------- |
|
||
| 非空约束 | 限制该字段的数据不能为 null | NOT NULL |
|
||
| 唯一约束 | 保证该字段的所有数据都是唯一、不重复的 | UNIQUE |
|
||
| 主键约束 | 主键是一行数据的唯一标识,要求非空且唯一 | PRIMARY KEY(自增:AUTO_INCREMENT) |
|
||
| 默认约束 | 保存数据时,如果未指定该字段的值,则采用默认值 | DEFAULT |
|
||
| 检查约束(8.0.1 版本后) | 保证字段值满足某一个条件 | CHECK |
|
||
| 外键约束 | 用来让两张图的数据之间建立连接,保证数据的一致性和完整性 | FOREIGN KEY |
|
||
|
||
## 常用约束
|
||
|
||
![img](https://static.7wate.com/img/2022/05/11/58a6b68ad6471.png)
|
||
|
||
```sql
|
||
create table user(
|
||
id int primary key auto_increment comment '主键id',
|
||
name varchar(10) not null unique comment '姓名',
|
||
age int check(age between 0 and 120) comment '年龄',
|
||
status char(1) default '1' comment '状态',
|
||
gender char(1) comment '性别'
|
||
) comment '用户表';
|
||
```
|
||
|
||
## 外键约束
|
||
|
||
外键约束是用来让两个表的数据之间建立连接,从而保证数据的一致性和完整性。
|
||
|
||
![img](https://static.7wate.com/img/2022/05/11/110dd22cdc2cf.png)
|
||
|
||
```sql
|
||
-- 在创建表时添加外键
|
||
CREATE TABLE 表名(
|
||
字段名 字段类型,
|
||
...
|
||
[CONSTRAINT] [外键名称] FOREIGN KEY(外键字段名) REFERENCES 主表(主表列名)
|
||
);
|
||
-- 为表补上外键
|
||
ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名) REFERENCES 主表(主表列名);
|
||
-- 删除外键
|
||
ALTER TABLE 表名 DROP FOREIGN KEY 外键名;
|
||
```
|
||
|
||
案例:为 emp 表补上外键,dept_id 关联 dep 中的 id
|
||
|
||
```sql
|
||
alter table emp add constraint fk_emp_dept_id foreign key dept_id references dep(id);
|
||
```
|
||
|
||
## 删除 / 更新行为
|
||
|
||
表添加了外键之后,再删除父表数据时产生的约束行为,我们就称为删除 / 更新行为。有以下常见的几种:
|
||
|
||
| 行为 | 说明 |
|
||
| :---------- | :----------------------------------------------------------- |
|
||
| NO ACTION | 当在父表中删除 / 更新对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除 / 更新(与 RESTRICT 一致) |
|
||
| RESTRICT | 当在父表中删除 / 更新对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除 / 更新(与 NO ACTION 一致) |
|
||
| CASCADE | 当在父表中删除 / 更新对应记录时,首先检查该记录是否有对应外键,如果有则也删除 / 更新外键在子表中的记录 |
|
||
| SET NULL | 当在父表中删除 / 更新对应记录时,首先检查该记录是否有对应外键,如果有则设置子表中该外键值为 null(要求该外键允许为 null) |
|
||
| SET DEFAULT | 父表有变更时,子表将外键设为一个默认值(Innodb 不支持) |
|
||
|
||
```sql
|
||
ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段) REFERENCES 主表名(主表字段名) ON UPDATE 行为 ON DELETE 行为;
|
||
-- 其实就是在添加外键时的多一个定义
|
||
-- 例如:在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则也删除/更新外键在子表中的记录
|
||
alter table emp add constraint fk_emp_dept_id foreign key dept_id references dep(id) on update cascade on delete cascade;
|
||
```
|
||
|
||
项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,基本上分为三种:
|
||
|
||
## 多表关系
|
||
|
||
### 一对多
|
||
|
||
案例:部门与员工
|
||
|
||
关系:一个部门对应多个员工,一个员工对应一个部门
|
||
|
||
实现:在多的一方建立外键,指向一的一方的主键
|
||
|
||
![img](https://static.7wate.com/img/2022/05/11/0f32732cb6a00.png)
|
||
|
||
### 多对多
|
||
|
||
案例:学生与课程
|
||
|
||
关系:一个学生可以选多门课程,一门课程也可以供多个学生选修
|
||
|
||
实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
|
||
|
||
![img](https://static.7wate.com/img/2022/05/11/c029ca35af10d.png)
|
||
|
||
### 一对一
|
||
|
||
案例:用户与用户详情
|
||
|
||
关系:一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情字段放在另一张表中,以提升操作效率
|
||
|
||
实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE)
|
||
|
||
![img](https://static.7wate.com/img/2022/05/11/ae2c3633fde99.png)
|
||
|
||
## 多表查询
|
||
|
||
指的是从多表中查询出想要的数据。
|
||
|
||
**笛卡尔积**:笛卡尔乘积是指在数学中,两个集合 A 集合和 B 集合的所有组合情况。(在多表查询时,需要消除无效的笛
|
||
卡尔积)
|
||
|
||
例如:使用 `select * from employee, dept;` 查询出来的结果是两个表的乘积。
|
||
|
||
**消除笛卡尔积**:`select * from employee, dept where employee.dept = dept.id;`
|
||
|
||
![img](https://static.7wate.com/img/2022/05/11/eaa68daf10655.png)
|
||
|
||
在进行多表查询测试之前,我们先准备好数据表。
|
||
|
||
```sql
|
||
-- 准备数据
|
||
create table dept(
|
||
id int auto_increment comment 'ID' primary key,
|
||
name varchar(50) not null comment '部门名称'
|
||
)comment '部门表';
|
||
|
||
create table emp(
|
||
id int auto_increment comment 'ID' primary key,
|
||
name varchar(50) not null comment '姓名',
|
||
age int comment '年龄',
|
||
job varchar(20) comment '职位',
|
||
salary int comment '薪资',
|
||
entrydate date comment '入职时间',
|
||
managerid int comment '直属领导ID',
|
||
dept_id int comment '部门ID'
|
||
)comment '员工表';
|
||
|
||
-- 添加外键
|
||
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id);
|
||
|
||
INSERT INTO dept (id, name) VALUES (1, '研发部'), (2, '市场部'),(3, '财务部'), (4, '销售部'), (5, '总经办'), (6, '人事部');
|
||
INSERT INTO emp (id, name, age, job, salary, entrydate, managerid, dept_id) VALUES
|
||
(1, '金庸', 66, '总裁',20000, '2000-01-01', null,5),
|
||
|
||
(2, '张无忌', 20, '项目经理',12500, '2005-12-05', 1,1),
|
||
(3, '杨逍', 33, '开发', 8400,'2000-11-03', 2,1),
|
||
(4, '韦一笑', 48, '开发',11000, '2002-02-05', 2,1),
|
||
(5, '常遇春', 43, '开发',10500, '2004-09-07', 3,1),
|
||
(6, '小昭', 19, '程序员鼓励师',6600, '2004-10-12', 2,1),
|
||
|
||
(7, '灭绝', 60, '财务总监',8500, '2002-09-12', 1,3),
|
||
(8, '周芷若', 19, '会计',48000, '2006-06-02', 7,3),
|
||
(9, '丁敏君', 23, '出纳',5250, '2009-05-13', 7,3),
|
||
|
||
(10, '赵敏', 20, '市场部总监',12500, '2004-10-12', 1,2),
|
||
(11, '鹿杖客', 56, '职员',3750, '2006-10-03', 10,2),
|
||
(12, '鹤笔翁', 19, '职员',3750, '2007-05-09', 10,2),
|
||
(13, '方东白', 19, '职员',5500, '2009-02-12', 10,2),
|
||
|
||
(14, '张三丰', 88, '销售总监',14000, '2004-10-12', 1,4),
|
||
(15, '俞莲舟', 38, '销售',4600, '2004-10-12', 14,4),
|
||
(16, '宋远桥', 40, '销售',4600, '2004-10-12', 14,4),
|
||
(17, '陈友谅', 42, null,2000, '2011-10-12', 1,null);
|
||
```
|
||
|
||
### 内连接查询
|
||
|
||
内连接查询的是两张表交集的部分。
|
||
|
||
![img](https://static.7wate.com/img/2022/05/11/8d8fa0226c444.png)
|
||
|
||
隐式内连接:
|
||
`SELECT 字段列表 FROM 表1, 表2 WHERE 条件 ...;`
|
||
|
||
显式内连接:
|
||
`SELECT 字段列表 FROM 表1 [ INNER ] JOIN 表2 ON 连接条件 ...;`
|
||
|
||
SQL**显式性能比隐式高**
|
||
|
||
```sql
|
||
-- 查询每一个员工的姓名,及关联的部门的名称(隐式内连接查询)
|
||
select emp.name, dept.name from emp, dep where emp.dept_id = dept.id;
|
||
select e.name, d.name from emp e, dep d where e.dept_id = d.id;
|
||
-- 查询每一个员工的姓名,及关联的部门的名称(显式内连接)
|
||
select emp.name, dept.name from emp inner join dept on emp.dept_id = dept.id;
|
||
select e.name, d.name from emp e inner join dept d on e.dept_id = d.id;
|
||
```
|
||
|
||
### 外连接查询
|
||
|
||
```sql
|
||
-- 查询emp表中的所有数据和对应的部门信息(左外连接)
|
||
select e.*, d.* from emp e left join dept d on e.dept_id = d.id;
|
||
-- 查询dept表中的所有数据和对应的员工信息(右外连接)
|
||
select d.name, e.* from emp e right join dept d on e.dept_id = d.id;
|
||
-- 以上可以看到,左连接可以查询到没有dept的employee,右连接可以查询到没有employee的dept
|
||
```
|
||
|
||
### 自连接查询
|
||
|
||
自连接查询指的是于自身的连接查询,把自身当作另一个表,所以要求**必须使用表别名**,**自连接查询可以是内连接查询或者外连接查询。**
|
||
|
||
```sql
|
||
-- 查询员工及其领导
|
||
select a.name '员工', b.name '领导' from emp a, emp b where a.managerid = b.id;
|
||
-- 查询所有员工emp及其领导的名字,如果员工没有领导,也需要查询出来
|
||
select a.*, b.name '领导' from emp a left join emp b on a.managerid = b.id;
|
||
```
|
||
|
||
### 联合查询 union
|
||
|
||
联合查询就是把多次查询的结果合并,形成一个新的查询集。**联合查询比使用 or 效率高,不会使索引失效。**对于联合查询,多张表的列数必须保持一致,字段类型也必须保持一段。union all 会将全部的数据直接合并在一起,union 会对合并之后的数据去重。
|
||
|
||
```sql
|
||
SELECT 字段列表 FROM 表A ...
|
||
UNION [ALL]
|
||
SELECT 字段列表 FROM 表B ...
|
||
-- 将薪资低于5000的员工,和年龄大于50岁的员工全部查询出来.
|
||
select * from emp where salary < 5000
|
||
union
|
||
select * from emp where age > 50
|
||
-- 使用 UNION ALL 会有重复结果,UNION 则不会。
|
||
```
|
||
|
||
## 子查询
|
||
|
||
SQL 语句中嵌套 SELECT 语句,称谓嵌套查询,又称子查询。
|
||
|
||
```sql
|
||
SELECT * FROM t1 WHERE column1 = ( SELECT column1 FROM t2);
|
||
```
|
||
|
||
子查询外部的语句可以是 **INSERT / UPDATE / DELETE / SELECT 的任何一个**
|
||
|
||
根据子查询结果可以分为:
|
||
|
||
- 标量子查询(子查询结果为单个值)
|
||
- 列子查询(子查询结果为一列)
|
||
- 行子查询(子查询结果为一行)
|
||
- 表子查询(子查询结果为多行多列)
|
||
|
||
根据子查询位置可分为:
|
||
|
||
- WHERE 之后
|
||
- FROM 之后
|
||
- SELECT 之后
|
||
|
||
### 标量子查询
|
||
|
||
子查询返回的结果是单个值(数字、字符串、日期等)。
|
||
|
||
常用操作符:`- < > > >= < <=`
|
||
|
||
```sql
|
||
-- 查询销售部所有员工
|
||
select id from dept where name = '销售部';
|
||
-- 根据销售部部门ID,查询员工信息
|
||
select * from emp where dept = 4;
|
||
-- 合并成标量子查询
|
||
select * from emp where dept = (select id from dept where name = '销售部');
|
||
-- 查询李白入职以后的新入职员工信息
|
||
select * from emp where entrydate > (select entrydate from employee where name = '李白');
|
||
```
|
||
|
||
### 列子查询
|
||
|
||
返回的结果是一列(可以是多行)。
|
||
|
||
常用操作符:
|
||
|
||
| 操作符 | 描述 |
|
||
| :----- | :------------------------------------------ |
|
||
| IN | 在指定的集合范围内,多选一 |
|
||
| NOT IN | 不在指定的集合范围内 |
|
||
| ANY | 子查询返回列表中,有任意一个满足即可 |
|
||
| SOME | 与 ANY 等同,使用 SOME 的地方都可以使用 ANY |
|
||
| ALL | 子查询返回列表的所有值都必须满足 |
|
||
|
||
```sql
|
||
-- 查询销售部和市场部的所有员工信息
|
||
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 = '财务部'));
|
||
-- 查询比研发部任意一人工资高的员工信息
|
||
select * from emp where salary > any(select salary from emp where dept_id = (select id from dept where name = '研发部'));
|
||
```
|
||
|
||
### 行子查询
|
||
|
||
返回的结果是一行(可以是多列)。
|
||
|
||
常用操作符:`=, <, >, IN, NOT IN`
|
||
|
||
```sql
|
||
-- 查询与李白的薪资及直属领导相同的员工信息
|
||
select * from emp where (salary, manager) = (12500, 1);
|
||
select * from emp where (salary, manager) = (select salary, manager from emp where name = '李白');
|
||
```
|
||
|
||
### 表子查询
|
||
|
||
返回的结果是多行多列
|
||
常用操作符:IN
|
||
|
||
```sql
|
||
-- 查询与xxx1,xxx2的职位和薪资相同的员工
|
||
select * from emp where (job, salary) in (select job, salary from emp where name = 'xxx1' or name = 'xxx2');
|
||
-- 查询入职日期是2022-01-01之后的员工,及其部门信息
|
||
select e.*, d.* from (select * from emp where entrydate > '2022-01-01') as e left join dept as d on e.dept_id = d.id;
|
||
```
|
||
|
||
## 多表练习
|
||
|
||
数据准备:
|
||
|
||
```sql
|
||
-- 准备数据
|
||
create table dept(
|
||
id int auto_increment comment 'ID' primary key,
|
||
name varchar(50) not null comment '部门名称'
|
||
)comment '部门表';
|
||
|
||
create table emp(
|
||
id int auto_increment comment 'ID' primary key,
|
||
name varchar(50) not null comment '姓名',
|
||
age int comment '年龄',
|
||
job varchar(20) comment '职位',
|
||
salary int comment '薪资',
|
||
entrydate date comment '入职时间',
|
||
managerid int comment '直属领导ID',
|
||
dept_id int comment '部门ID'
|
||
)comment '员工表';
|
||
|
||
-- 添加外键
|
||
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id);
|
||
|
||
INSERT INTO dept (id, name) VALUES (1, '研发部'), (2, '市场部'),(3, '财务部'), (4, '销售部'), (5, '总经办'), (6, '人事部');
|
||
INSERT INTO emp (id, name, age, job, salary, entrydate, managerid, dept_id) VALUES
|
||
(1, '金庸', 66, '总裁',20000, '2000-01-01', null,5),
|
||
|
||
(2, '张无忌', 20, '项目经理',12500, '2005-12-05', 1,1),
|
||
(3, '杨逍', 33, '开发', 8400,'2000-11-03', 2,1),
|
||
(4, '韦一笑', 48, '开发',11000, '2002-02-05', 2,1),
|
||
(5, '常遇春', 43, '开发',10500, '2004-09-07', 3,1),
|
||
(6, '小昭', 19, '程序员鼓励师',6600, '2004-10-12', 2,1),
|
||
|
||
(7, '灭绝', 60, '财务总监',8500, '2002-09-12', 1,3),
|
||
(8, '周芷若', 19, '会计',48000, '2006-06-02', 7,3),
|
||
(9, '丁敏君', 23, '出纳',5250, '2009-05-13', 7,3),
|
||
|
||
(10, '赵敏', 20, '市场部总监',12500, '2004-10-12', 1,2),
|
||
(11, '鹿杖客', 56, '职员',3750, '2006-10-03', 10,2),
|
||
(12, '鹤笔翁', 19, '职员',3750, '2007-05-09', 10,2),
|
||
(13, '方东白', 19, '职员',5500, '2009-02-12', 10,2),
|
||
|
||
(14, '张三丰', 88, '销售总监',14000, '2004-10-12', 1,4),
|
||
(15, '俞莲舟', 38, '销售',4600, '2004-10-12', 14,4),
|
||
(16, '宋远桥', 40, '销售',4600, '2004-10-12', 14,4),
|
||
(17, '陈友谅', 42, null,2000, '2011-10-12', 1,null);
|
||
|
||
create table salgrade(
|
||
grade int,
|
||
losal int,
|
||
hisal int
|
||
) comment '薪资等级表';
|
||
|
||
insert into salgrade values (1,0,3000);
|
||
insert into salgrade values (2,3001,5000);
|
||
insert into salgrade values (3,5001,8000);
|
||
insert into salgrade values (4,8001,10000);
|
||
insert into salgrade values (5,10001,15000);
|
||
insert into salgrade values (6,15001,20000);
|
||
insert into salgrade values (7,20001,25000);
|
||
insert into salgrade values (8,25001,30000);
|
||
```
|
||
|
||
sql 练习
|
||
|
||
以显式内连接为主
|
||
|
||
```sql
|
||
-- 1. 查询员工的姓名、年龄、职位、部门信息 (隐式内连接)
|
||
select e.name, e.age, e.job, d.name from emp e, dept d where e.dept_id = d.id;
|
||
-- 2. 查询年龄小于30岁的员工的姓名、年龄、职位、部门信息(显式内连接)
|
||
select e.name, e.age, e.job, d.name from emp e inner join dept d on e.dept_id = d.id where e.age < 30;
|
||
-- 3. 查询拥有员工的部门ID、部门名称
|
||
-- 使用内连接可以让空的数据过滤
|
||
-- 使用 distinct 可以过滤相同数据
|
||
select distinct d.id, d.name from dept d inner join emp e on d.id = e.dept_id;
|
||
-- 4. 查询所有年龄大于40岁的员工, 及其归属的部门名称; 如果员工没有分配部门, 也需要展示出来
|
||
select e.*, d.name from emp e left join dept d on e.dept_id = d.id where e.age > 40;
|
||
-- 5. 查询所有员工的工资等级
|
||
select e.name, e.salary, s.grade from emp e inner join salgrade s on e.salary between s.losal and s.hisal;
|
||
-- 6. 查询 "研发部" 所有员工的信息及工资等级
|
||
select d.name '部门', e.*, s.grade from emp e inner join salgrade s on e.salary between s.losal and s.hisal inner join dept d on e.dept_id = d.id where d.name = '研发部';
|
||
-- 7. 查询 "研发部" 员工的平均工资
|
||
select d.name '部门', avg(e.salary) from emp e inner join dept d on e.dept_id = d.id where d.name = '研发部';
|
||
-- 8. 查询工资比 "灭绝" 高的员工信息。
|
||
select * from emp where salary > (select salary from emp where name = '灭绝');
|
||
-- 9. 查询比平均薪资高的员工信息
|
||
select * from emp where salary > (select avg(emp.salary) from emp);
|
||
-- 10. 查询低于本部门平均工资的员工信息
|
||
select * from emp e2 where e2.salary < (select avg(e1.salary) from emp e1 where e1.dept_id = e2.dept_id );
|
||
-- 11. 查询所有的部门信息, 并统计部门的员工人数
|
||
select d.*, (select count(*) from emp e where e.dept_id = d.id) '人数' from dept d;
|
||
```
|
||
|
||
![img](https://static.7wate.com/img/2022/05/11/7ee27cf278dd9.png)
|
||
|
||
事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求
|
||
|
||
**这些操作要么同时成功,要么同时失败。**
|
||
|
||
默认 MysQL 的事务是自动提交的,当执行一条 DML 语句,MySQL 会立即隐式地提交事务。
|
||
|
||
## 事务操作
|
||
|
||
事务演示如下,如果 2-3 步骤之间报错终端,那么张三就丢了 1000;而李四没收到 1000;
|
||
|
||
```sql
|
||
-- 1. 查询张三账户余额
|
||
select * from account where name = '张三';
|
||
-- 2. 将张三账户余额 -1000
|
||
update account set money = money - 1000 where name = '张三';
|
||
-- 3. 将李四账户余额 +1000
|
||
update account set money = money + 1000 where name = '李四';
|
||
```
|
||
|
||
因此我们需要把上述代码整合成一个事务,完全执行后才提交。
|
||
|
||
```sql
|
||
-- 查看事务提交方式
|
||
SELECT @@AUTOCOMMIT;
|
||
-- 设置事务提交方式,1 为自动提交,0 为手动提交,该设置只对当前会话有效
|
||
SET @@AUTOCOMMIT = 0;
|
||
-- 提交事务
|
||
COMMIT;
|
||
-- 回滚事务
|
||
ROLLBACK;
|
||
```
|
||
|
||
第二种方式
|
||
|
||
```sql
|
||
-- 开启事务:
|
||
START TRANSACTION 或 BEGIN TRANSACTION;
|
||
-- 提交事务:
|
||
COMMIT;
|
||
-- 回滚事务:
|
||
ROLLBACK;
|
||
```
|
||
|
||
调整事务之后,重新运行 sql
|
||
|
||
```sql
|
||
-- 1. 查询张三账户余额
|
||
select * from account where name = '张三';
|
||
-- 2. 将张三账户余额 -1000
|
||
update account set money = money - 1000 where name = '张三';
|
||
-- 3. 将李四账户余额 +1000
|
||
update account set money = money + 1000 where name = '李四';
|
||
commit;
|
||
```
|
||
|
||
## 事务四大特性
|
||
|
||
四大特性 ACID:
|
||
|
||
- 原子性 (Atomicity):事务是不可分割的最小操作但愿,要么全部成功,要么全部失败。
|
||
- 一致性 (Consistency):事务完成时,必须使所有数据都保持一致状态。
|
||
- 隔离性 (Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
|
||
- 持久性 (Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
|
||
|
||
## 并发事务问题
|
||
|
||
| 问题 | 描述 |
|
||
| :--------- | :----------------------------------------------------------- |
|
||
| 脏读 | 一个事务读到另一个事务还没提交的数据 |
|
||
| 不可重复读 | 一个事务先后读取同一条记录,但两次读取的数据不同 |
|
||
| 幻读 | 一个事务按照条件查询数据时,没有对应的数据行,但是再插入数据时,又发现这行数据已经存在 |
|
||
|
||
### 脏读
|
||
|
||
如下图,事务 A 更新 1,但还未提交,此时被事务 B 查去了,这就导致可能事务 A 最终决定不提交,但是事务 B 拿出来后当真了,所以这种现象叫做脏读。
|
||
|
||
![img](https://static.7wate.com/img/2022/05/11/85c311e17a51d.png)
|
||
|
||
### 不可重复读
|
||
|
||
如下图,事务 A 查询第一次后,事务 B 更新了这条数据,事务 A 查询第二次时发现跟第一次查询的结果不一样,这种现象叫做不可重复读。
|
||
|
||
![img](https://static.7wate.com/img/2022/05/11/0d298382a22d4.png)
|
||
|
||
### 幻读
|
||
|
||
幻读是在解决 [不可重复读] 的基础上产生的新问题,如下图,事务 A 读取 id 为 1 的数据为空,事务 B 插入 id 为 1 的数据,之后事务 A 想要插入这条数据发现插入不了(比如被主键约束了),然后事务 A 重新查询还是找不到 id 为 1 的数据(因为我们解决了[不可重复读],所以查询出来的结果跟第一次查是一致的)。
|
||
|
||
![img](https://static.7wate.com/img/2022/05/11/b5bdbc0d6b4e0.png)
|
||
|
||
## 事务隔离级别
|
||
|
||
MySQL 默认的事务隔离级别是 Repeatable Read
|
||
|
||
Oracle 默认的事务隔离级别是 Read committed
|
||
|
||
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|
||
| :-------------------- | :--- | :--------- | :--- |
|
||
| Read uncommitted | √ | √ | √ |
|
||
| Read committed | × | √ | √ |
|
||
| Repeatable Read(默认) | × | × | √ |
|
||
| Serializable | × | × | × |
|
||
|
||
√ 表示在当前隔离级别下该问题会出现,
|
||
|
||
Serializable 性能最低;Read uncommitted 性能最高,数据安全性最差。
|
||
|
||
```sql
|
||
-- 查看事务隔离级别:
|
||
SELECT @@TRANSACTION_ISOLATION;
|
||
-- 设置事务隔离级别:
|
||
SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE };
|
||
-- SESSION 是会话级别,表示只针对当前会话有效
|
||
-- GLOBAL 表示对所有会话有效
|
||
```
|
||
|
||
>版权属于:乐心湖's Blog
|
||
>
|
||
>本文链接:<https://www.xn2001.com/archives/677.html>
|