【MySQL】MySQL基础知识详解
文章目录
- 1. MySQL概述
- 1.1 数据库相关概念
- 1.1.1 数据库、数据库管理系统与SQL
- 1.1.2 关系型数据库与数据模型
- 1.2 MySQL数据库
- 1.2.1 MySQL的安装与配置
- 1.2.2 MySQL服务的启动与停止
- 1.2.3 连接MySQL服务端
- 2. SQL
- 2.1 SQL简介
- 2.2 DDL
- 2.2.1 数据库操作
- 2.2.2 表操作
- 2.2.2.1 创建表
- 2.2.2.2 查询表
- 2.2.2.3 数据类型
- 2.2.2.4 修改表
- 2.2.2.5 删除表
- 2.3 DML
- 2.3.1 DataGrip的安装与使用
- 2.3.2 添加数据
- 2.3.3 修改数据
- 2.3.4 删除数据
- 2.4 DQL
- 2.4.1 基本语法
- 2.4.2 基础查询
- 2.4.3 条件查询
- 2.4.4 聚合函数
- 2.4.5 分组查询
- 2.4.6 排序查询
- 2.4.7 分页查询
- 2.4.8 执行顺序
- 2.4.9 综合案例
- 2.5 DCL
- 2.5.1 管理用户
- 2.5.2 权限控制
- 3. 函数
- 3.1 字符串函数
- 3.2 数值函数
- 3.3 日期函数
- 3.4 流程函数
- 4. 约束
- 4.1 基本约束
- 4.2 外键约束
- 4.2.1 介绍
- 4.2.2 语法
- 4.2.3 删除/更新行为
- 5. 多表查询
- 5.1 多表关系
- 5.1.1 一对多
- 5.1.2 多对多
- 5.1.3 一对一
- 5.2 多表查询概述
- 5.2.1 数据准备
- 5.2.2 概述
- 5.2.3 分类
- 5.3 内连接
- 5.4 外连接
- 5.5 自连接
- 5.5.1 自连接查询
- 5.5.2 联合查询
- 5.6 子查询
- 5.6.1 概述
- 5.6.2 标量子查询
- 5.6.3 列子查询
- 5.6.4 行子查询
- 5.6.5 表子查询
- 5.7 综合案例
- 6. 事务
- 6.1 事务简介
- 6.2 事务操作
- 6.2.1 未控制事务
- 6.2.2 控制事务
- 6.3 事务四大特性
- 6.4 并发事务问题
- 6.5 事务隔离级别
1. MySQL概述
1.1 数据库相关概念
1.1.1 数据库、数据库管理系统与SQL
1.数据库、数据库管理系统与SQL 的概念:
名称 | 简称 | 描述 |
---|---|---|
数据库 | DataBase(DB) | 存储数据的仓库,数据是有组织的进行存储 |
数据库管理系统 | DataBase Management System (DBMS) | 操纵和管理数据库的大型软件 |
SQL | Structured Query Language (SQL) | 操作关系型数据库的编程语言,定义了一套操作 |
2.数据库、数据库管理系统与SQL之间的关系:通过SQL操作数据库管理系统,再通过数据库管理系统来操作数据库以及数据库当中的数据。
1.1.2 关系型数据库与数据模型
1.关系型数据库(RDBMS)
概念:建立在关系模型基础上,由多张相互连接的二维表组成的数据库。
而所谓二维表,指的是由行和列组成的表,如下图(就类似于Excel表格数据,有表头、有列、有行,
还可以通过一列关联另外一个表格中的某一列数据)
特点:
- 使用表存储数据,格式统一,便于维护。
- 使用SQL语言操作,标准统一,使用方便。
注:关系型数据库,都是使用SQL语言来进行统一操作,也就是说
MySQL
、Oracle
、DB2
以及SQLServer
等数据库的操作方式都是一致的。
2.数据模型
MySQL是关系型数据库,是基于二维表进行数据存储的,具体的结构图下:
我们可以通过MySQL客户端连接数据库管理系统DBMS,然后通过DBMS操作数据库。
可以使用SQL语句,通过数据库管理系统操作数据库,以及操作数据库中的表结构及数据。
一个数据库服务器中可以创建多个数据库,一个数据库中也可以包含多张表,而一张表中又可以包含多行记录。
1.2 MySQL数据库
1.2.1 MySQL的安装与配置
1.MySQL的版本
- 社区版本(MySQL Community Server):免费, MySQL不提供任何技术支持
- 商业版本(MySQL Enterprise Edition):收费,可以使用30天,官方提供技术支持
2.MySQL的下载
官网下载地址:https://downloads.mysql.com/archives/installer/
下载完成后,会得到一个安装包 mysql-installer-community-8.0.32.0.msi
3.MySQL的安装
双击安装包进行安装,弹出安装界面后,基本上一直点击Next
即可,需要注意的点如下:
- 如果出现
Execute
选项,则要先点击Execute,然后再Next - 点击Next之后,碰到弹出
Yes
和No
的,要点Yes
- 需要设置
Root
用户的密码并确认
4.配置环境变量
配置环境变量步骤如下:
- 在此电脑上,右键选择属性,找到 “高级系统设置”,选择环境变量
- 找到 Path 系统变量, 点击 “编辑”
- 选择 “新建” , 将MySQL Server的安装目录下的bin目录添加到环境变量 ,
C:\Program Files\MySQL\MySQL Server 8.0\bin
1.2.2 MySQL服务的启动与停止
MySQL安装完成之后,在系统启动时,会自动启动MySQL服务,我们无需手动启动了。
如果需要启动或者停止MySQL服务,可以使用以下两种方式:
- 打开终端输入命令,可以启动与停止服务
- 找到Windows的服务,可以手动启动与停止服务
1.以管理员身份运行cmd
,进入命令行执行如下指令,可以启动与停止服务
# 启动服务
net start mysql80
# 停止服务
net stop mysql80
2.键盘按住Win+R
,然后输入services.msc
打开服务,找到MySQL80
服务,可以右键选择启动或停止服务
1.2.3 连接MySQL服务端
客户端连接MySQL服务端有以下两种方式:
- 使用MySQL提供的客户端命令行工具
- 使用系统自带的命令行工具执行指令
1.使用MySQL提供的客户端命令行工具
找到最近添加,打开MySQL提供的客户端命令行工具,然后输入Root用户的密码,出现如下图,表示客户端连接成功。
注:
1.刚刚安装 MySQL时,设置了Root
的用户的密码为123456
2.客户端能成功连接MySQL服务端,表明MySQL数据库安装成功了
2.使用系统自带的命令行工具执行指令
注:使用这种方式,需要配置好环境变量才行
命令格式如下:
mysql [-h 127.0.0.1] [-P 3306] -u root -p
-h
: MySQL服务所在的主机IP-P
: MySQL服务端口号, 默认3306-u
: MySQL数据库用户名-p
: MySQL数据库用户名对应的密码
打开cmd
输入命令,出现如下图,表示客户端连接成功:
2. SQL
2.1 SQL简介
SQL
:结构化查询语言(Structured Query Language),是操作关系型数据库的编程语言,定义了
一套操作关系型数据库统一标准 。
1.SQL的语法
- SQL语句可以单行或多行书写,以分号结尾
- SQL语句可以使用空格或缩进来增强语句的可读性
- MySQL数据库的SQL语句不区分大小写,关键字建议使用大写
- 注释
- 单行注释:
-- 注释内容
或# 注释内容
(MySQL独有) - 多行注释:
/* 注释内容 */
- 单行注释:
2.SQL的分类
名称 | 全称 | 描述 |
---|---|---|
DDL | Data Definition Language | 数据定义语言,用来定义数据库对象(数据库,表,字段) |
DML | Data Manipulation Language | 数据操作语言,用来对数据库表中的数据进行增删改 |
DQL | Data Query Language | 数据查询语言,用来查询数据库中表的记录 |
DCL | Data Control Language | 数据控制语言,用来创建数据库用户、控制数据库的访问权限 |
2.2 DDL
DDL:数据定义语言(Data Definition Language),用来定义数据库对象(数据库,表,字段)
2.2.1 数据库操作
数据库操作汇总:
# 查询所有数据库
show databases; # 查询当前数据库
select database();# 创建数据库
create database [ if not exists ] 数据库名 [ default charset 字符集 ] [ collate 排序规则];# 删除数据库
drop database [ if exists ] 数据库名;# 切换数据库
use 数据库名;
1.查询所有数据库
show databases;
2.切换数据库和查询当前数据库
例:先切换到sys数据库,再查询当前数据库
use sys;
select database();
3.创建数据库
例1:创建一个itcast数据库, 使用数据库默认的字符集
create database itcast;
注:在同一个数据库服务器中,不能创建两个名称相同的数据库,否则将会报错。可以通过
if not exists
参数来解决这个问题,数据库不存在, 则创建该数据库,如果存在,则不创建。
create database if not exists itcast;
例2:创建一个itheima数据库,并且指定字符集
create database itheima default charset utf8mb4;
4.删除数据库
例:删除itcast数据库
drop database itcast;
注:如果删除一个不存在的数据库,将会报错。此时可以加上参数
if exists
,如果数据库存在,再
执行删除,否则不执行删除。
2.2.2 表操作
表操作:对表的结构进行增删改查
注:此处只涉及对表结构的增删改查,不涉及对表中的数据进行增删改查,对表中的数据进行增删改查的是DML和DQL语句,而此处是DDL语句。
2.2.2.1 创建表
创建表结构:
CREATE TABLE 表名(字段1 字段1类型 [COMMENT 字段1注释 ],字段2 字段2类型 [COMMENT 字段2注释 ],字段3 字段3类型 [COMMENT 字段3注释 ],......字段n 字段n类型 [COMMENT 字段n注释 ]
) [ COMMENT 表注释 ] ;
例:创建一张表 tb_user ,对应的结构如下,那么建表语句为:
create table tb_user(id int comment '编号',name varchar(50) comment '姓名',age int comment '年龄',gender char(1) comment '性别'
) comment '用户表';
切换到itheima
这个数据库下,再创建tb_user
表:
2.2.2.2 查询表
查询表操作汇总:
# 查询当前数据库所有表
show tables;# 查看指定表结构
desc 表名;# 查询指定表的建表语句
show create table 表名;
1.查询当前数据库所有表
use sys;
show tables;
例:切换到sys
这个系统数据库,并查看系统数据库中的所有表结构。
2.查看指定表结构和建表语句
例:切换到itheima
数据库,并查看tb_user
表结构和建表语句
use itheima;
desc tb_user;
show create table tb_user;
2.2.2.3 数据类型
创建表时,需要指定字段的数据类型,此处详细介绍一下MySQL的数据类型。
MySQL中的数据类型有很多,主要分为三类:
- 数值类型
- 字符串类型
- 日期时间类型
1.数值类型
类型 | 大小 | 有符号(SIGNED)范围 | 无符号(UNSIGNED)范围 | 描述 |
---|---|---|---|---|
TINYINT | 1 byte | (-128,127) | (0,255) | 小整数值 |
SMALLINT | 2 bytes | (-32768,32767) | (0,65535) | 大整数值 |
MEDIUMINT | 3 bytes | (-8388608,8388607) | (0,16777215) | 大整数值 |
INT或INTEGER | 4 bytes | (-2147483648,2147483647) | (0,4294967295) | 大整数值 |
BIGINT | 8 bytes | (-263,263-1) | (0,2^64-1) | 极大整数值 |
FLOAT | 4 bytes | (-3.402823466 E+38,3.402823466351 E+38) | 0 和 (1.175494351 E-38,3.402823466 E+38) | 单精度浮点数值 |
DOUBLE | 8 bytes | (-1.7976931348623157 E+308,1.7976931348623157 E+308) | 0 和 (2.2250738585072014 E-308,1.7976931348623157 E+308) | 双精度浮点数值 |
DECIMAL | 依赖于M(精度)和D(标度)的值 | 依赖于M(精度)和D(标度)的值 | 小数值(精确定点数) |
例如:
- 年龄字段 – 不会出现负数, 而且人的年龄不会太大 :
age tinyint unsigned
- 分数 – 总分100分, 最多出现一位小数:
score double(4,1)
2.字符串类型:
类型 | 大小 | 描述 |
---|---|---|
CHAR | 0-255 bytes | 定长字符串 |
VARCHAR | 0-65535 bytes | 变长字符串 |
TINYBLOB | 0-255 bytes | 不超过255个字符的二进制数据 |
TINYTEXT | 0-255 bytes | 短文本字符串 |
BLOB | 0-65 535 bytes | 二进制形式的长文本数据 |
TEXT | 0-65 535 bytes | 长文本数据 |
MEDIUMBLOB | 0-16 777 215 bytes | 二进制形式的中等长度文本数据 |
MEDIUMTEXT | 0-16 777 215 bytes | 中等长度文本数据 |
LONGBLOB | 0-4 294 967 295 bytes | 二进制形式的极大文本数据 |
LONGTEXT | 0-4 294 967 295 bytes | 极大文本数据 |
注:
char
与varchar
都可以描述字符串,char是定长字符串,指定长度多长,就占用多少个字符,和
字段值的长度无关 。而varchar是变长字符串,指定的长度为最大占用长度 。相对来说,char的性
能会更高些。
例如:
- 用户名 username – 长度不定, 最长不会超过50:
username varchar(50)
- 性别 gender – 存储值, 不是男,就是女:
gender char(1)
- 手机号 phone – 固定长度为11:
phone char(11)
3.日期时间类型
DATE | 3 | 1000-01-01 至 9999-12-31 | YYYY-MM-DD | 日期值 |
---|---|---|---|---|
TIME | 3 | -838:59:59 至 838:59:59 | HH:MM:SS | 时间值或持续时间 |
YEAR | 1 | 1901 至 2155 | YYYY | 年份值 |
DATETIME | 8 | 1000-01-01 00:00:00 至 9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 混合日期和时间值 |
TIMESTAMP | 4 | 1970-01-01 00:00:01 至 2038-01-19 03:14:07 | YYYY-MM-DD HH:MM:SS | 混合日期和时间值,时间戳 |
例如:
- 生日字段 birthday:
birthday date
- 创建时间 createtime:
createtime datetime
案例:
设计一张员工信息表,要求如下:
- 编号(纯数字)
- 员工工号 (字符串类型,长度不超过10位)
- 员工姓名(字符串类型,长度不超过10位)
- 性别(男/女,存储一个汉字)
- 年龄(正常人年龄,不可能存储负数)
- 身份证号(二代身份证号均为18位,身份证中有X这样的字符)
- 入职时间(取值年月日即可)
对应的建表语句如下:
create table emp(id int comment '编号',workno varchar(10) comment '员工工号',name varchar(10) comment '员工姓名',gender char(1) comment '性别',age tinyint unsigned comment '年龄',idcard char(18) comment '身份证号',entrydate date comment '入职时间'
) comment '员工表';
2.2.2.4 修改表
修改表操作汇总:
# 添加字段
ALTER TABLE 表名 ADD 字段名 类型 (长度) [ COMMENT 注释 ] [ 约束 ];# 修改数据类型
ALTER TABLE 表名 MODIFY 字段名 新数据类型 (长度);# 修改字段名和字段类型
ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型 (长度) [ COMMENT 注释 ] [ 约束 ];# 删除字段
ALTER TABLE 表名 DROP 字段名;# 修改表名
ALTER TABLE 表名 RENAME TO 新表名;
1.添加字段
例:为emp表增加一个新的字段”昵称”为nickname,类型为varchar(20)
alter table emp add nickname varchar(20) comment '昵称';
2.修改数据类型
例:修改emp表的nickname字段,将其数据类型为char(30)
alter table emp modify nickname char(30);
3.修改字段名和字段类型
例:将emp表的nickname字段修改为username,类型为varchar(30)
alter table emp change nickname username varchar(30) comment '用户名';
4.删除字段
例:删除emp表中的username字段
alter table emp drop username;
5.修改表名
例:将emp表重命名为employee
alter table emp rename to employee;
2.2.2.5 删除表
删除表操作汇总:
# 删除表
DROP TABLE [ IF EXISTS ] 表名;# 删除指定表, 并重新创建表
TRUNCATE TABLE 表名;
注:
1.在删除表的时候,表中的全部数据也都会被删除。
2.可选项IF EXISTS
,代表只有表名存在时才会删除该表,表名不存在,则不执行删除操作。
3.使用TRUNCATE TABLE 表名
会删除原有的表,并按原有表结构重新创建表,从结果上看,就是清空了表中的数据。
1.删除表
例:如果tb_user表存在,则删除tb_user表
drop table if exists tb_user;
2.删除指定表, 并重新创建表(清空表中的数据)
例:清空employee表中的数据
truncate table employee;
2.3 DML
DML:数据操作语言(Data Manipulation Language),用来对数据库表中的数据进行增删改。
2.3.1 DataGrip的安装与使用
MySQL的图形化界面工具,主要有Sqlyog
、Navicat
和 DataGrip
。
此处选择使用DataGrip
,这种图形化界面工具,功能更加强大,界面提示更加友好。
1.下载与安装
百度网盘链接:https://pan.baidu.com/s/13QGW6bIcYwNn-jCiwtxHkg
提取码:odo5
2.安装
双击datagrip-2021.2.2.exe
,一直点击Next
即可。
3.使用
创建新工程:New Project -> 输入工程名,此处我填写的工程名是 mysql-base
连接数据库:左上角加号 -> Database Source ->MySQL ->输入用户名密码,并下载驱动 -> 先点击Apply再OK
展示数据库:
创建数据库:右键MySQL - @localhost
-> New -> Schema
创建表:右键数据库 -> New -> Table
编写SQl语句:右键数据库 -> New -> Query Console
2.3.2 添加数据
添加表数据的操作汇总:
# 给指定字段添加数据
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, ...);
注:
1.插入数据时,指定的字段顺序需要与值的顺序是一一对应的
2.字符串和日期型数据应该包含在引号中
3.插入的数据大小,应该在字段的规定范围内
1.给指定字段添加数据
例1:给employee表的id、name、gender、age字段添加数据
insert into employee (id,name,gender,age) values (2,'张三丰','男',100);
例2:给employee表所有的字段添加数据
insert into employee (id, workno, name, gender, age, idcard, entrydate)
values (1, '2', '张无忌', '男', 18, '123456789012345670', '2005-01-01');
2.给全部字段添加数据
例:给employee表全部字段添加数据
insert into employee values (1,'2','张无忌','男',18,'123456789012345670','2005-01-01');
3.批量添加数据
例:批量插入两条数据到employee表
insert into employee
values (3, '3', '周芷若', '女', 18, '123456789012345670', '2005-01-01'),(4, '4', '赵敏', '女', 18, '123456789012345670', '2005-01-01');
2.3.3 修改数据
修改表数据的语法如下:
UPDATE 表名 SET 字段名1 = 值1 , 字段名2 = 值2 , .... [ WHERE 条件 ];
注:修改语句的条件可以有,也可以没有,如果没有条件,则会修改整张表的所有数据。
例1:修改id为1的数据,将name修改为itheima
update employee set name ='itheima' where id = 1;
例2:修改id为1的数据, 将name修改为小昭, gender修改为 女
update employee set name = '小昭',gender = '女' where id = 1;
例3:将所有的员工入职日期修改为 2008-01-01
update employee set entrydate = '2008-01-01';
2.3.4 删除数据
删除数据的语法如下:
DELETE FROM 表名 [ WHERE 条件 ];
注:
1.DELETE 语句的条件可以有,也可以没有,如果没有条件,则会删除整张表的所有数据
2.DELETE 语句不能删除某一个字段的值(可以使用UPDATE,将该字段值置为NULL即可)
例1:删除gender为女的员工
delete from employee where gender = '女';
例2:删除所有员工
delete from employee;
2.4 DQL
DQL:数据查询语言(Data Query Language),用来查询数据库中表的记录
2.4.1 基本语法
DQL 查询语句,语法结构如下:
SELECT 字段列表
FROM 表名列表
WHERE 条件列表
GROUP BY 分组字段列表
HAVING 分组后条件列表
ORDER BY 排序字段列表
LIMIT 分页参数
完整语法可以拆分为以下几个部分:
- 基本查询(不带任何条件)
- 条件查询(WHERE)
- 聚合函数(count、max、min、avg、sum)
- 分组查询(group by)
- 排序查询(order by)
- 分页查询(limit)
要演示DQL语句,首先要准备一些数据如下:
-- 删除emp表
drop table if exists emp;-- 创建emp表
create table emp
(id int comment '编号',workno varchar(10) comment '工号',name varchar(10) comment '姓名',gender char(1) comment '性别',age tinyint unsigned comment '年龄',idcard char(18) comment '身份证号',workaddress varchar(50) comment '工作地址',entrydate date comment '入职时间'
) comment '员工表';-- 插入数据
insert into emp (id, workno, name, gender, age, idcard, workaddress, entrydate)
values (1, '1', '柳岩', '女', 20, '123456789012345678', '北京', '2000-01-01'),(2, '2', '张无忌', '男', 18, '123456789012345670', '北京', '2005-09-01'),(3, '3', '韦一笑', '男', 38, '123456789012345671', '上海', '2005-08-01'),(4, '4', '赵敏', '女', 18, '123456789012345672', '北京', '2009-12-01'),(5, '5', '小昭', '女', 16, '123456789012345673', '上海', '2007-07-01'),(6, '6', '杨逍', '男', 28, '123456789012345674', '北京', '2006-01-01'),(7, '7', '范瑶', '男', 40, '123456789012345675', '北京', '2005-05-01'),(8, '8', '黛绮丝', '女', 38, '123456789012345676', '天津', '2015-05-01'),(9, '9', '范凉凉', '女', 45, '123456789012345677', '北京', '2010-04-01'),(10, '10', '陈友谅', '男', 53, '123456789012345679', '上海', '2011-01-01'),(11, '11', '张士诚', '男', 55, '12345678901234567X', '江苏', '2015-05-01'),(12, '12', '常遇春', '男', 32, '123456789012345610', '北京', '2004-02-01'),(13, '13', '张三丰', '男', 88, '123456789012345611', '江苏', '2020-11-01'),(14, '14', '灭绝', '女', 65, '123456789012345612', '西安', '2019-05-01'),(15, '15', '胡青牛', '男', 70, '123456789012345613', '西安', '2018-04-01'),(16, '16', '周芷若', '女', 18, null, '北京', '2012-06-01');
执行后查询表中数据,内容如下:
2.4.2 基础查询
基础查询,即不带任何的查询条件,语法汇总如下:
# 查询所有字段
SELECT * FROM 表名;# 查询多个字段
SELECT 字段1, 字段2, 字段3 ... FROM 表名;# 字段设置别名
SELECT 字段1 [ AS 别名1 ] , 字段2 [ AS 别名2 ] ... FROM 表名;
SELECT 字段1 [ 别名1 ] , 字段2 [ 别名2 ] ... FROM 表名;# 去除重复记录
SELECT DISTINCT 字段列表 FROM 表名;
注 :
*
号代表查询所有字段,在实际开发中尽量少用(不直观、影响效率)。
案例:
1.查询指定字段 name, workno, age并返回
select name,workno,age from emp;
2.查询返回所有字段
select * from emp;
select id, workno, name, gender, age, idcard, workaddress, entrydate from emp;
3.查询所有员工的工作地址,起别名
select workaddress as '工作地址' from emp;
# as可以省略
select workaddress '工作地址' from emp;
4.查询公司员工的上班地址有哪些(不要重复)
select distinct workaddress from emp;
2.4.3 条件查询
语法:
SELECT 字段列表 FROM 表名 WHERE 条件列表;
比较运算符:
比较运算符 | 描述 |
---|---|
> | 大于 |
>= | 大于等于 |
< | 小于 |
<= | 小于等于 |
= | 等于 |
<> 或 != | 不等于 |
BETWEEN … AND … | 在某个范围之内(含最小、最大值) |
IN(…) | 在in之后的列表中的值,多选一 |
LIKE 占位符 |
模糊匹配( _ 匹配单个字符, % 匹配任意个字符)
|
IS NULL | 是NULL |
逻辑运算符:
逻辑运算符 | 描述 |
---|---|
AND 或 && | 并且 (多个条件同时成立) |
OR 或 || | 或者 (多个条件任意一个成立) |
NOT 或 ! | 非 , 不是 |
案例:
1.查询年龄等于 88 的员工
select * from emp where age = 88;
2.查询年龄小于 20 的员工信息
select * from emp where age < 20;
3.查询年龄小于等于 20 的员工信息
select * from emp where age <= 20;
4.查询没有身份证号的员工信息
select * from emp where idcard is null;
5.查询有身份证号的员工信息
select * from emp where idcard is not null;
6.查询年龄不等于 88 的员工信息
select * from emp where age != 88;
select * from emp where age <> 88;
7.查询年龄在15岁(包含) 到 20岁(包含)之间的员工信息
select * from emp where age >= 15 and age <=20;
select * from emp where age >= 15 && age <=20;
select * from emp where age between 15 and 20;
8.查询性别为 女 且年龄小于 25岁的员工信息
select * from emp where gender = '女' and age < 25;
9.查询年龄等于18 或 20 或 40 的员工信息
select * from emp where age = 18 or age = 20 or age = 40;
select * from emp where age in(18,20,40);
10.查询姓名为两个字的员工信息
select * from emp where name like '__';
11.查询身份证号最后一位是X的员工信息
select * from emp where idcard like '%X'
2.4.4 聚合函数
聚合函数:将一列数据作为一个整体,进行纵向计算。
聚合函数 | 描述 |
---|---|
count | 统计数量 |
max | 最大值 |
min | 最小值 |
avg | 平均值 |
sum | 求和 |
语法如下:
SELECT 聚合函数(字段列表) FROM 表名;
注:
NULL
值是不参与所有聚合函数运算的
案例:
1.统计该企业员工数量
select count(*) from emp; -- 统计的是总记录数
select count(idcard) from emp; -- 统计的是idcard字段不为null的记录数
对于count聚合函数,统计符合条件的总记录数,还可以通过 count(数字/字符串)
的形式进行统计
查询,比如:
select count(1) from emp;
2.统计该企业员工的平均年龄
select avg(age) from emp;
3.统计该企业员工的最大年龄
select max(age) from emp;
4.统计该企业员工的最小年龄
select min(age) from emp;
5.统计西安地区员工的年龄之和
select sum(age) from emp where workaddress = '西安';
2.4.5 分组查询
语法:
SELECT 字段列表 FROM 表名 [ WHERE 条件 ] GROUP BY 分组字段名 [ HAVING 分组 后过滤条件 ];
where
与 having
区别:
- 执行时机不同:where是分组之前进行过滤,不满足where条件,不参与分组;而having是分组之后对结果进行过滤。
- 判断条件不同:where不能对聚合函数进行判断,而having可以。
注意事项:
1.分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义
2.执行顺序: where > 聚合函数 > having
3.支持多字段分组, 具体语法为 :group by columnA,columnB
案例:
1.根据性别分组 , 统计男性员工 和 女性员工的数量
select gender,count(*) from emp group by gender;
注:只要出现统计数量这个词,就要想到
count(*)
2.根据性别分组 , 统计男性员工 和 女性员工的平均年龄
select gender,avg(age) from emp group by gender;
3.查询年龄小于45的员工 , 并根据工作地址分组 , 获取员工数量大于等于3的工作地址
select workaddress,count(*) address_ount from emp where age < 45
group by workaddress having address_ount >=3;
4.统计各个工作地址上班的男性及女性员工的数量
select workaddress,gender,count(*) '数量' from emp group by workaddress,gender;
解释:统计了工作地址,又统计了男性和女性,所以此处以
workaddress
和gender
两个字段进行分组
2.4.6 排序查询
语法:
SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1 , 字段2 排序方式2;
排序方式:
ASC
: 升序(默认值)DESC
: 降序
注:
1.如果是升序, 可以不指定排序方式ASC
2.如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序
案例:
1.根据年龄对公司的员工进行升序排序
select * from emp order by age asc;
select * from emp order by age;
2.根据入职时间, 对员工进行降序排序
select * from emp order by entrydate desc;
3.根据年龄对公司的员工进行升序排序 , 年龄相同 , 再按照入职时间进行降序排序
select * from emp order by age asc ,entrydate desc;
2.4.7 分页查询
分页操作在业务系统开发时,是非常常见的一个功能,我们在网站中看到的各种各样的分页条,后台
都需要借助于数据库的分页操作。
语法:
SELECT 字段列表 FROM 表名 LIMIT 起始索引, 查询记录数;
注:
1.起始索引从0开始,起始索引 = (查询页码 - 1)* 每页显示记录数
2.分页查询是数据库的方言,不同的数据库有不同的实现,MySQL中是LIMIT
3.如果起始索引是0,那么可以省略,直接简写为 limit 10
案例:
1.查询第1页员工数据, 每页展示10条记录
select * from emp limit 0,10;
select * from emp limit 10;
2.查询第2页员工数据, 每页展示10条记录
select * from emp limit 10,10;
解释:由于每页展示10条数据,也就是第一页数据的索引值分别为
0-9
,而第二页数据的索引值为10-19
,此处第二页的第一条数据为起始索引值,就是10
,又要查完整的第二页10条数据,因此limit 10,10
3.查询年龄第二大的员工
select * from emp order by age desc limit 1,1;
解释:以年龄作为降序,第二条数据即为年龄第二大的员工,第二条数据的索引值为
1
,然后只查这一条数据,因此为limit 1,1
2.4.8 执行顺序
在讲解DQL语句的具体语法之前,我们已经讲解了DQL语句的完整语法,及编写顺序,接下来,我们要来说明的是DQL语句在执行时的执行顺序,也就是先执行那一部分,后执行那一部分。
结论如下:from > where > group by > having > select > order by > limit
2.4.9 综合案例
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,entrydate desc;
5.查询性别为男,且年龄在20-40 岁(含)以内的前5个员工信息,对查询的结果按年龄升序排序,年龄相同按入职时间升序排序
select * from emp where gender = '男' and ( age between 20 and 40 )
order by age, entrydate limit 5;
2.5 DCL
DCL:数据控制语言(Data Control Language),用来创建数据库用户、控制数据库的访问权限。
2.5.1 管理用户
管理用户的操作汇总如下:
# 查询用户
select * from mysql.user;# 创建用户
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';# 修改用户密码
ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql_native_password BY '新密码';# 删除用户
DROP USER '用户名'@'主机名';
注:
1.在MySQL中需要通过用户名@主机名的方式,来唯一标识一个用户
2.主机名可以使用%
通配,表示任意主机都可以访问该数据库
3.这类SQL语句开发人员操作的比较少,主要是DBA( Database Administrator 数据库
管理员)使用
案例:
1.查询用户
select * from mysql.user;
结果如下:
其中
Host
字段代表当前用户访问的主机, 如果为 localhost,仅代表只能够在当前本机访问,是不可以
远程访问的。User
字段代表的是访问该数据库的用户名。在MySQL中需要通过Host和User来唯一标识一
个用户。
2.创建用户itcast, 只能够在当前主机localhost访问, 密码123456
create user 'itcast'@'localhost' identified by '123456';
3.创建用户heima, 可以在任意主机访问该数据库, 密码123456
create user 'heima'@'%' identified by '123456';
4.修改用户heima的访问密码为1234
alter user 'heima'@'%' identified with mysql_native_password by '1234';
5.删除 itcast@localhost 用户
drop user 'itcast'@'localhost';
2.5.2 权限控制
MySQL中定义了很多种权限,但是常用的就以下几种:
权限 | 说明 |
---|---|
ALL, ALL PRIVILEGES | 所有权限 |
SELECT | 查询数据的权限 |
INSERT | 插入数据的权限 |
UPDATE | 修改数据的权限 |
DELETE | 删除数据的权限 |
ALTER | 修改表的权限 |
DROP | 删除数据库/表/视图的权限 |
CREATE | 创建数据库/表的权限 |
上述只是简单罗列了常见的几种权限描述,其他权限描述及含义,可以直接参考官方文档。
权限控制的操作汇总如下:
# 查询权限
SHOW GRANTS FOR '用户名'@'主机名';# 授予权限
GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名';# 撤销权限
REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机名';
注:
1.多个权限之间,使用逗号分隔
2.授权时, 数据库名和表名可以使用*
进行通配,代表所有
案例:
1.查询 'heima'@'%'
用户的权限
show grants for 'heima'@'%';
2.授予 'heima'@'%'
用户itcast数据库所有表的所有操作权限
grant all on itcast.* to 'heima'@'%';
3.撤销 'heima'@'%'
用户的itcast数据库的所有权限
revoke all on itcast.* from 'heima'@'%';
3. 函数
函数:是指一段可以直接被另一段程序调用的程序或代码。
MySQL中的函数主要分为以下四类: 字符串函数、数值函数、日期函数、流程函数。
3.1 字符串函数
常用的字符串函数如下:
函数 | 功能 |
---|---|
CONCAT(S1,S2,...Sn)
|
字符串拼接,将S1,S2,… Sn拼接成一个字符串 |
LOWER(str)
|
将字符串str全部转为小写 |
UPPER(str)
|
将字符串str全部转为大写 |
LPAD(str,n,pad)
|
左填充,用字符串pad对str的左边进行填充,达到n个字符串长度 |
RPAD(str,n,pad)
|
右填充,用字符串pad对str的右边进行填充,达到n个字符串长度 |
TRIM(str)
|
去掉字符串头部和尾部的空格 |
SUBSTRING(str,start,len)
|
返回从字符串str从start位置起的len个长度的字符串 |
# concat : 字符串拼接
select concat('Hello' , ' MySQL'); -- Hello MySQL# lower : 全部转小写
select lower('Hello'); -- hello# upper : 全部转大写
select upper('Hello'); -- HELLO# lpad : 左填充
select lpad('01', 5, '-'); -- ---01# rpad : 右填充
select rpad('01', 5, '-'); -- 01---# trim : 去除空格
select trim(' Hello MySQL '); -- Hello MySQL# substring : 截取子字符串
select substring('Hello MySQL',1,5); -- Hello
案例:
由于业务需求变更,企业员工的工号,统一为5位数,目前不足5位数的全部在前面补0。比如: 1号员工的工号应该为00001。
update emp set workno = lpad(workno, 5, '0');
3.2 数值函数
常用的数值函数如下:
函数 | 功能 |
---|---|
CEIL(x)
|
向上取整 |
FLOOR(x)
|
向下取整 |
MOD(x,y)
|
返回x/y的模 |
RAND()
|
返回0~1内的随机数 |
ROUND(x,y)
|
求参数x的四舍五入的值,保留y位小数 |
# ceil:向上取整
select ceil(1.1); -- 2# floor:向下取整
select floor(1.9); -- 1# mod:取模
select mod(7,4); -- 3# rand:获取随机数
select rand(); -- 0.6382742446272394# round:四舍五入
select round(2.344,2); -- 2.34
案例:
通过数据库的函数,生成一个六位数的随机验证码
思路: 获取随机数可以通过rand()函数,但是获取出来的随机数是在0-1之间的,所以可以在其基础
上乘以1000000,然后舍弃小数部分,如果长度不足6位,补0
select lpad(round(rand()*1000000 , 0), 6, '0');
3.3 日期函数
常用的日期函数如下:
函数 | 功能 |
---|---|
CURDATE()
|
返回当前日期 |
CURTIME()
|
返回当前时间 |
NOW()
|
返回当前日期和时间 |
YEAR(date)
|
获取指定date的年份 |
MONTH(date)
|
获取指定date的月份 |
DAY(date)
|
获取指定date的日期 |
DATE_ADD(date, INTERVAL expr type)
|
返回一个日期/时间值加上一个时间间隔expr后的时间值 |
DATEDIFF(date1,date2)
|
返回起始时间date1 和 结束时间date2之间的天数 |
# curdate:当前日期
select curdate(); -- 2023-05-16# curtime:当前时间
select curtime(); -- 22:55:32# now:当前日期和时间
select now(); -- 2023-05-16 22:55:39# YEAR , MONTH , DAY:当前年、月、日
select YEAR(now()); -- 2023
select MONTH(now()); -- 5
select DAY(now()); -- 16# date_add:增加指定的时间间隔
select date_add(now(), INTERVAL 70 YEAR ); -- 2093-05-16 22:56:04# datediff:获取两个日期相差的天数
select datediff('2021-10-01', '2021-12-01'); -- -61
案例:
查询所有员工的入职天数,并根据入职天数倒序排序。
思路: 入职天数,就是通过当前日期 - 入职日期,所以需要使用datediff函数来完成。
select name, datediff(curdate(), entrydate) as 'entrydays' from emp order by entrydays desc;
3.4 流程函数
流程函数:是很常用的一类函数,可以在SQL语句中实现条件筛选,从而提高语句的效率。
常用的流程函数如下:
函数 | 功能 |
---|---|
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默认值 |
1.if
select if(true, 'Ok', 'Error'); -- Ok
select if(false, 'Ok', 'Error'); -- Error
2.ifnull
select ifnull('Ok','Default'); -- Ok
select ifnull('','Default'); -- 空字符串
select ifnull(null,'Default'); -- Default
select ifnull(null, 0); -- 0
3.case when then else end
需求: 查询emp表的员工姓名和工作地址 (北京/上海 ----> 一线城市 , 其他 ----> 二线城市)
select name,(case workaddresswhen '北京' then '一线城市'when '上海' then '一线城市'else '二线城市' end) as '工作地址'
from emp;
案例:根据学科的分数来评定等级,其中分数在85(包含)以上为优秀,60(包含)以上为及格,低于60不及格。
准备数据如下:
-- 创建学员成绩表
create table score
(id int comment 'ID',name varchar(20) comment '姓名',math int comment '数学',english int comment '英语',chinese int comment '语文'
) comment '学员成绩表';-- 插入数据
insert into score(id, name, math, english, chinese)
VALUES (1, 'Tom', 67, 88, 95),(2, 'Rose', 23, 66, 90),(3, 'Jack', 56, 98, 76);
SQL语句如下:
select id,name,(case when math >= 85 then '优秀' when math >= 60 then '及格' else '不及格' end) '数学',(case when english >= 85 then '优秀' when english >= 60 then '及格' else '不及格' end) '英语',(case when chinese >= 85 then '优秀' when chinese >= 60 then '及格' else '不及格' end) '语文'
from score;
4. 约束
4.1 基本约束
约束的概念:约束是作用于表中字段上的规则,用于限制存储在表中的数据。
目的:保证数据库中数据的正确、有效性和完整性。
分类:
关键字 | 约束 | 描述 |
---|---|---|
NOT NULL
|
非空约束 | 限制该字段的数据不能为null |
UNIQUE
|
唯一约束 | 保证该字段的所有数据都是唯一、不重复的 |
PRIMARY KEY
|
主键约束 | 主键是一行数据的唯一标识,要求非空且唯一 |
DEFAULT
|
默认约束 | 保存数据时,如果未指定该字段的值,则采用默认值 |
CHECK
|
检查约束(8.0.16版本之后) | 保证字段值满足某一个条件 |
FOREIGN KEY
|
外键约束 | 用来让两张表的数据之间建立连接,保证数据的一致 |
注:约束是作用于表中字段上的,可以在创建表/修改表的时候添加约束。
以下对除了外键约束以外的约束进行演示:
案例:根据需求,完成表结构的创建,需求如下
根据以上约束,对应的建表语句如下:
CREATE TABLE tb_user
(id int AUTO_INCREMENT PRIMARY KEY COMMENT 'ID唯一标识',name varchar(10) NOT NULL UNIQUE COMMENT '姓名',age int check (age > 0 and age <= 120) COMMENT '年龄',status char(1) default '1' COMMENT '状态',gender char(1) COMMENT '性别'
);
验证约束是否生效:
insert into tb_user(name, age, status, gender)values ('Tom1', 19, '1', '男'), ('Tom2', 25, '0', '男');
insert into tb_user(name, age, status, gender) values ('Tom3', 19, '1', '男');
insert into tb_user(name, age, status, gender) values (null, 19, '1', '男');
insert into tb_user(name, age, status, gender) values ('Tom3', 19, '1', '男');
insert into tb_user(name, age, status, gender) values ('Tom4', 80, '1', '男');
insert into tb_user(name, age, status, gender) values ('Tom5', -1, '1', '男');
insert into tb_user(name, age, status, gender) values ('Tom5', 121, '1', '男');
insert into tb_user(name, age, gender) values ('Tom5', 120, '男');
4.2 外键约束
4.2.1 介绍
外键:用来让两张表的数据之间建立连接,从而保证数据的一致性和完整性。
举例如下:
左侧的emp表是员工表,里面存储员工的基本信息,包含员工的ID、姓名、年龄、职位、薪资、入职日期、上级主管ID、部门ID,在员工的信息中存储的是部门的ID dept_id,而这个部门的ID是关联的部门表dept的主键id,那emp表的dept_id就是外键,关联的是另一张表的主键。
注:
1.目前上述两张表,只是在逻辑上存在这样一层关系;在数据库层面,并未建立外键关联,所以是无法保证数据的一致性和完整性的。
2.添加外键约束的表为子表(从表),外键所关联的表为父表(主表)
在没有数据库外键关联的情况下,能够保证一致性和完整性呢?我们来测试一下。
准备数据:
-- 删除dept表
drop table if exists dept;-- 创建dept表
create table dept
(id int auto_increment comment 'ID' primary key,name varchar(50) not null comment '部门名称'
) comment '部门表';-- 向dept表插入数据
INSERT INTO dept (id, name)
VALUES (1, '研发部'),(2, '市场部'),(3, '财务部'),(4, '销售部'),(5, '总经办');-- 删除emp表
drop table if exists emp;-- 创建emp表
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 '员工表';-- 向emp表插入数据
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);
得到表数据如下:
接下来,我们可以做一个测试,删除id为1的部门信息。
结果,我们看到删除成功,而删除成功之后,部门表不存在id为1的部门,而在emp表中还有很多的员工,关联的为id为1的部门,此时就出现了数据的不完整性。 而要想解决这个问题就得通过数据库的外键约束。
4.2.2 语法
1.添加外键
-- 创建表时,添加外键
CREATE TABLE 表名(字段名 数据类型,...[CONSTRAINT] [外键名称] FOREIGN KEY (外键字段名) REFERENCES 主表 (主表列名));-- 修改表时,添加外键
ALTER TABLE 表名ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名) REFERENCES 主表 (主表列名);
例:为emp表的dept_id字段添加外键约束,关联dept表的主键id。
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id);
添加了外键约束之后,我们再到dept表(父表)删除id为1的记录,然后看一下会发生什么现象。 此时
将会报错,不能删除或更新父表记录,因为存在外键约束。
delete from dept where id = 1;
2.删除外键
ALTER TABLE 表名 DROP FOREIGN KEY 外键名称;
例:删除emp表的外键fk_emp_dept_id。
alter table emp drop foreign key fk_emp_dept_id;
4.2.3 删除/更新行为
添加了外键之后,再删除父表数据时产生的约束行为,我们就称为删除/更新行为。
具体的删除/更新行为有以下几种:
行为 | 描述 |
---|---|
NO ACTION
|
当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除/更新(默认行为) |
RESTRICT
|
当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除/更新 (与 NO ACTION 一致) |
CASCADE
|
当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有,则也删除/更新外键在子表中的记录 |
SET NULL
|
当在父表中删除对应记录时,首先检查该记录是否有对应外键,如果有则设置子表中该外键值为null(这就要求该外键允许取null) |
SET DEFAULT
|
父表有变更时,子表将外键列设置成一个默认的值 (Innodb不支持) |
语法:
ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段)
REFERENCES 主表名 (主表字段名) ON UPDATE CASCADE ON DELETE CASCADE;
由于NO ACTION
是默认行为,我们前面语法演示的时候,已经测试过了,就不再演示了,这里我们再演示其他的两种行为:CASCADE
、SET NULL
。
1.CASCADE
alter table emp add constraint fk_emp_dept_id foreign key (dept_id)
references dept(id) on update cascade on delete cascade ;
例1:修改父表id为1的记录,将id修改为6
我们发现,原来在子表中dept_id值为1的记录,现在也变为6了,这就是cascade级联的效果。
注:在一般的业务系统中,不会修改一张表的主键值。
例2:删除父表id为6的记录
我们发现,父表的数据删除成功了,但是子表中关联的记录也被级联删除了。
2.SET NULL
在进行测试之前,我们先需要删除上面建立的外键 fk_emp_dept_id。然后再通过数据脚本,将
emp、dept表的数据恢复了。
alter table emp add constraint fk_emp_dept_id foreign key (dept_id)
references dept(id) on update set null on delete set null ;
接下来,我们删除id为1的数据,看看会发生什么样的现象。
我们发现父表的记录是可以正常的删除的,父表的数据删除之后,再打开子表 emp,我们发现子表emp的dept_id字段,原来dept_id为1的数据,现在都被置为NULL了。
5. 多表查询
5.1 多表关系
项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,基本上分为三种:
- 一对多(多对一)
- 多对多
- 一对一
5.1.1 一对多
- 案例:部门 与 员工的关系
- 关系:一个部门对应多个员工,一个员工对应一个部门
- 实现:在多的一方建立外键,指向一的一方的主键
5.1.2 多对多
- 案例:学生 与 课程的关系
- 关系:一个学生可以选修多门课程,一门课程也可以供多个学生选择
- 实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
对应的SQL脚本:
-- 创建学生表
create table student
(id int auto_increment primary key comment '主键ID',name varchar(10) comment '姓名',no varchar(10) comment '学号'
) comment '学生表';-- 向学生表插入数据
insert into student
values (null, '黛绮丝', '2000100101'),(null, '谢逊', '2000100102'),(null, '殷天正', '2000100103'),(null, '韦一笑', '2000100104');-- 创建课程表
create table course
(id int auto_increment primary key comment '主键ID',name varchar(10) comment '课程名称'
) comment '课程表';-- 向课程表插入数据
insert into course
values (null, 'Java'),(null, 'PHP'),(null, 'MySQL'),(null, 'Hadoop');-- 创建学生课程关系表
create table student_course
(id int auto_increment comment '主键' primary key,studentid int not null comment '学生ID',courseid int not null comment '课程ID',constraint fk_courseid foreign key (courseid) references course (id),constraint fk_studentid foreign key (studentid) references student (id)
) comment '学生课程中间表';-- 向学生课程关系表插入数据
insert into student_course
values (null, 1, 1),(null, 1, 2),(null, 1, 3),(null, 2, 2),(null, 2, 3),(null, 3, 4);
5.1.3 一对一
- 案例:用户 与 用户详情的关系
- 关系:一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情字段放在另一张表中,以提升操作效率
- 实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE)
对应的SQL脚本:
-- 创建用户基本信息表
create table tb_user
(id int auto_increment primary key comment '主键ID',name varchar(10) comment '姓名',age int comment '年龄',gender char(1) comment '1: 男 , 2: 女',phone char(11) comment '手机号'
) comment '用户基本信息表';-- 向用户基本信息表插入数据
insert into tb_user(id, name, age, gender, phone)
values (null, '黄渤', 45, '1', '18800001111'),(null, '冰冰', 35, '2', '18800002222'),(null, '码云', 55, '1', '18800008888'),(null, '李彦宏', 50, '1', '18800009999');-- 创建用户教育信息表
create table tb_user_edu
(id int auto_increment primary key comment '主键ID',degree varchar(20) comment '学历',major varchar(50) comment '专业',primaryschool varchar(50) comment '小学',middleschool varchar(50) comment '中学',university varchar(50) comment '大学',userid int unique comment '用户ID',constraint fk_userid foreign key (userid) references tb_user (id)
) comment '用户教育信息表';-- 向用户教育信息表插入数据
insert into tb_user_edu(id, degree, major, primaryschool, middleschool, university, userid)
values (null, '本科', '舞蹈', '静安区第一小学', '静安区第一中学', '北京舞蹈学院', 1),(null, '硕士', '表演', '朝阳区第一小学', '朝阳区第一中学', '北京电影学院', 2),(null, '本科', '英语', '杭州市第一小学', '杭州市第一中学', '杭州师范大学', 3),(null, '本科', '应用数学', '阳泉第一小学', '阳泉区第一中学', '清华大学', 4);
5.2 多表查询概述
5.2.1 数据准备
1.删除之前 emp, dept表的测试数据
-- 删除emp表
drop table if exists emp;-- 删除dept表
drop table if exists dept;
2.执行如下脚本,创建emp表与dept表并插入测试数据
-- 创建dept表,并插入数据
create table dept
(id int auto_increment comment 'ID' primary key,name varchar(50) not null comment '部门名称'
) comment '部门表';INSERT INTO dept (id, name)
VALUES (1, '研发部'),(2, '市场部'),(3, '财务部'),(4, '销售部'),(5, '总经办'),(6, '人事部');-- 创建emp表,并插入数据
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 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, '会计', 4800, '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);
5.2.2 概述
多表查询就是指从多张表中查询数据。
原来查询单表数据,执行的SQL形式为:select * from emp;
,现在要执行多表查询,就只需要使用逗号分隔多张表即可,如: select * from emp , dept;
,具体的执行结果如下:
此时,我们看到查询结果中包含了大量的结果集,总共102条记录,而这其实就是员工表emp所有的记录
(17条) 与 部门表dept所有记录(6条) 的所有组合情况,这种现象称之为笛卡尔积。接下来,就来简单
介绍下笛卡尔积。
笛卡尔积: 笛卡尔乘积是指在数学中,两个集合A集合 和 B集合的所有组合情况。
而在多表查询中,我们是需要消除无效的笛卡尔积的,只保留两张表关联部分的数据。
在SQL语句中,如何来去除无效的笛卡尔积呢? 我们可以给多表查询加上连接查询的条件即可。
select * from emp , dept where emp.dept_id = dept.id;
由于id为17的员工,没有dept_id字段值,所以在多表查询时,根据连接查询的条件并没有查询到。
5.2.3 分类
多表查询分为连接查询和子查询:
子查询:SQL语句中嵌套SELECT语句,称为嵌套查询,又称子查询。
连接查询:
- 内连接:相当于查询A、B交集部分数据
- 左外连接:查询左表所有数据,以及两张表交集部分数据
- 右外连接:查询右表所有数据,以及两张表交集部分数据
- 自连接:当前表与自身的连接查询,自连接必须使用表别名
5.3 内连接
内连接查询的是两张表交集部分的数据(也就是绿色部分的数据),内连接的语法分为两种: 隐式内连接和显式内连接。
语法:
-- 隐式内连接
SELECT 字段列表 FROM 表1 , 表2 WHERE 条件 ... ;-- 显式内连接
SELECT 字段列表 FROM 表1 [ INNER ] JOIN 表2 ON 连接条件 ... ;
案例:
例1:查询每一个员工的姓名 , 及关联的部门的名称 (隐式内连接实现)
表结构: emp , dept
连接条件: emp.dept_id = dept.id
select emp.name , dept.name from emp , dept where emp.dept_id = dept.id ;
-- 为每一张表起别名,简化SQL编写
select e.name,d.name from emp e,dept d where e.dept_id = d.id;
注:一旦为表起了别名,就不能再使用表名来指定对应的字段了,此时只能够使用别名来指定字段。(因为执行顺序
from > where
)
例2:查询每一个员工的姓名 , 及关联的部门的名称 (显式内连接实现)
表结构: emp , dept
连接条件: emp.dept_id = dept.id
select e.name,d.name from emp e inner join dept d on e.dept_id = d.id;
5.4 外连接
外连接分为两种,分别是:左外连接 和 右外连接。
语法:.
-- 左外连接
SELECT 字段列表 FROM 表1 LEFT [ OUTER ] JOIN 表2 ON 条件 ... ;-- 右外连接
SELECT 字段列表 FROM 表1 RIGHT [ OUTER ] JOIN 表2 ON 条件 ... ;
注
1.左外连接相当于查询表1(左表)的所有数据,当然也包含表1和表2交集部分的数据。
2.右外连接相当于查询表2(右表)的所有数据,当然也包含表1和表2交集部分的数据。
案例:
例1:查询emp表的所有数据, 和对应的部门信息
由于需求中提到,要查询emp的所有数据,所以是不能内连接查询的,需要考虑使用外连接查询。
表结构:emp, dept
连接条件:emp.dept_id = dept.id
select e.*,d.name from emp e left join dept d on e.dept_id = d.id;
注:当出现需要查询某张表的所有数据时,要想到左外连接。
例2:查询dept表的所有数据, 和对应的员工信息(右外连接)
表结构:emp, dept
连接条件:emp.dept_id = dept.id
select d.*,e.* from emp e right join dept d on e.dept_id = d.id;
右外连接也可以用左外连接来实现,只需要调换两张表的位置即可:
select d.*,e.* from dept d left join emp e on e.dept_id = d.id;
注:左外连接和右外连接是可以相互替换的,而我们在日常开发使用时,更偏向于左外连接。
5.5 自连接
5.5.1 自连接查询
自连接查询,顾名思义,就是自己连接自己,也就是把一张表连接查询多次;
对于自连接查询,可以是内连接查询,也可以是外连接查询。
语法如下:
SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件 ... ;
案例:
例1:查询员工 及其 所属领导的名字
表结构: emp
-- 此时把a表视为员工表,b表视为领导表
select a.name '员工',b.name '领导' from emp a,emp b where a.managerid = b.id;
注:
1.此处的特殊之处在于,领导也是员工,所以员工表和领导表是一张表
2.连接条件a.managerid = b.id
,表示员工表的领导的id,就是领导表的id
3.a.managerid = b.id
该连接条件不能写成a.id= b.managerid
,否则此时a就成了领导表,b为员工表
例2:查询所有员工 emp 及其领导的名字 emp , 如果员工没有领导, 也需要查询出来
表结构: emp a , emp b
select a.name '员工',b.name '领导' from emp a left join emp b on a.managerid = b.id;
注:在自连接查询中,必须要为表起别名,否则我们将不清楚所指定的条件、返回的字段,到底是哪一张表的字段。
5.5.2 联合查询
对于 union
查询,就是把多次查询的结果合并起来,形成一个新的查询结果集。
语法:
SELECT 字段列表 FROM 表A ...
UNION [ ALL ]
SELECT 字段列表 FROM 表B ....;
注:
1.对于联合查询的多张表的列数必须保持一致,字段类型也需要保持一致,如果不一致会报错
2.union all
会将全部的数据直接合并在一起,union
会对合并之后的数据去重
案例:将薪资低于 5000 的员工 , 和 年龄大于 50 岁的员工全部查询出来。
当前对于这个需求,我们可以直接使用多条件查询,使用逻辑运算符 or 连接即可。 那这里呢,我们
也可以通过 union/union all
来联合查询
select * from emp where salary < 5000
union all
select * from emp where age > 50;
由此可见,union all查询出来的结果,仅仅进行简单的合并,并未去重。
select * from emp where salary < 5000
union
select * from emp where age > 50;
union 联合查询,会对查询出来的结果进行去重处理。
5.6 子查询
5.6.1 概述
概念:SQL语句中嵌套SELECT语句,称为嵌套查询,又称子查询
SELECT * FROM t1 WHERE column1 = ( SELECT column1 FROM t2 );
注:子查询外部的语句可以是
INSERT / UPDATE / DELETE / SELECT
的任何一个
分类:
根据子查询结果不同,分为:
- 标量子查询(子查询结果为单个值)
- 列子查询(子查询结果为一列)
- 行子查询(子查询结果为一行)
- 表子查询(子查询结果为多行多列)
根据子查询位置,分为:
- WHERE之后
- FROM之后
- SELECT之后
5.6.2 标量子查询
标量子查询:子查询返回的结果是单个值(数字、字符串、日期等),最简单的形式,这种子查询称为标量子查询。
常用的操作符:= <> > >= < <=
案例:
例1:查询 “销售部” 的所有员工信息
完成这个需求时,我们可以将需求分解为两步:
① 查询销售部的信息(部门ID)
select id from dept where name = '销售部';
② 根据 “销售部” 部门ID,查询员工信息
select * from emp where dept_id = 4;
由于4
是由 select id from dept where name = '销售部'
查询得到的,所以将4
替换成该语句即可。
select * from emp where dept_id = ( select id from dept where name = '销售部' );
注:做子查询的题目时,需要将需求分解,分步查询然后再合并。
例2:查询在 “方东白” 入职之后的员工信息
① 查询 方东白 的入职日期
select entrydate from emp where name = '方东白';
② 查询指定入职日期之后入职的员工信息
select * from emp where entrydate > '2009-02-12';
由于2009-02-12
是由 select entrydate from emp where name = '方东白'
查询得到的,所以将2009-02-12
替换成该语句即可。
select * from emp where entrydate > ( select entrydate from emp where name = '方东白' );
5.6.3 列子查询
子查询返回的结果是一列(可以是多行),这种子查询称为列子查询。
常用的操作符:IN 、NOT IN 、 ANY 、SOME 、 ALL
操作符 | 描述 |
---|---|
IN
|
在指定的集合范围之内,多选一 |
NOT IN
|
不在指定的集合范围之内 |
ANY
|
子查询返回列表中,有任意一个满足即可 |
SOME
|
与ANY等同,使用SOME的地方都可以使用ANY |
ALL
|
子查询返回列表的所有值都必须满足 |
案例:
例1:查询 “销售部” 和 “市场部” 的所有员工信息
① 查询 “销售部” 和 “市场部” 的部门ID
select id from dept where name in('销售部','市场部');
② 根据部门ID, 查询员工信息
select * from emp where dept_id in(2,4);
select * from emp where dept_id in ( select id from dept where name in('销售部','市场部') );
例2:查询比 财务部 所有人工资都高的员工信息
① 查询所有 财务部 人员工资
-- 查询财务部的部门id
select id from dept where name = '财务部';-- 查询财务部所有员工工资
select salary from emp where dept_id = ( select id from dept where 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 > ( select max(salary) from emp where dept_id = ( select id from dept where name = '财务部' ));
例3:查询比研发部其中任意一人工资高的员工信息
① 查询研发部所有人工资
-- 查询研发部的部门id
select id from dept where name = '研发部';-- 查询研发部所有员工工资
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 = '研发部' ) );
比研发部其中任意一人工资高,等价于比研发部最低工资的人高就行,因此也可以改成如下语句:
select * from emp
where salary > ( select min(salary) from emp where dept_id = ( select id from dept where name = '研发部' ) );
5.6.4 行子查询
子查询返回的结果是一行(可以是多列),这种子查询称为行子查询。
常用的操作符:= 、<> 、IN 、NOT IN
案例:查询与 “张无忌” 的薪资及直属领导相同的员工信息
① 查询 “张无忌” 的薪资及直属领导
select salary,managerid from emp where name = '张无忌';
② 查询与 “张无忌” 的薪资及直属领导相同的员工信息
select * from emp where (salary,managerid) = ('12500',1);
select * from emp where (salary,managerid) = ( select salary,managerid from emp where name = '张无忌' );
5.6.5 表子查询
子查询返回的结果是多行多列,这种子查询称为表子查询。
常用的操作符:IN
案例:
例1:查询与 “鹿杖客” , “宋远桥” 的职位和薪资相同的员工信息
① 查询 “鹿杖客” , “宋远桥” 的职位和薪资
select job,salary from emp where name in('鹿杖客','宋远桥');
② 查询与 “鹿杖客” , “宋远桥” 的职位和薪资相同的员工信息
select * from emp where (job,salary) in (('职员',3750),('销售',4600));
select * from emp where (job,salary) in ( select job,salary from emp where name in('鹿杖客','宋远桥') );
例2:查询入职日期是 “2006-01-01” 之后的员工信息 , 及其部门信息
① 入职日期是 “2006-01-01” 之后的员工信息
select * from emp where entrydate > '2006-01-01';
② 查询这部分员工,对应的部门信息
select e.*, d.name
from (select * from emp where entrydate > '2006-01-01') e left join dept d on e.dept_id = d.id;
注:
select * from emp where entrydate > '2006-01-01'
查询出来的结果为新的子表,然后再对子表再进行新的查询
5.7 综合案例
数据准备:
-- 创建薪资等级表
create table salgrade
(grade int, -- 薪资等级losal int, -- 最低薪资hisal int -- 最高薪资
) comment '薪资等级表';-- 插入数据
insert into salgrade values (1, 0, 3000); -- 表示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);
注:以下例题中有
*
的需要重点去看
1.查询员工的姓名、年龄、职位、部门信息 (隐式内连接)
表: emp , dept
连接条件: emp.dept_id = dept.id
select e.name,e.age,e.job,d.name from emp e,dept d where e.dept_id = d.id;
2.查询年龄小于30岁的员工的姓名、年龄、职位、部门信息(显式内连接)
表: emp , dept
连接条件: emp.dept_id = dept.id
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、部门名称 *
解法一:内连接
表: emp , dept
连接条件: emp.dept_id = dept.id
select distinct d.id , d.name from emp e , dept d where e.dept_id = d.id;
解法二:子查询
-- 查询拥有部门ID的员工,并对部门ID去重,得到的就是拥有员工的部门ID
select distinct dept_id from emp where dept_id is not null;-- 根据得到的部门ID,去查部门信息
select id,name from dept where id in( select distinct dept_id from emp where dept_id is not null );
4.查询所有年龄大于40岁的员工, 及其归属的部门名称; 如果员工没有分配部门, 也需要展示出来(外连接)
表: emp , dept
连接条件: emp.dept_id = dept.id
select e.*,d.name from emp e left join dept d on e.dept_id = d.id where e.age > 40;
5.查询所有员工的工资等级 *
表: emp , salgrade
连接条件 : emp.salary >= salgrade.losal and emp.salary <= salgrade.hisal
-- 方式一
select e.*,s.grade,s.losal,s.hisal from emp e ,salgrade s where e.salary >= s.losal and e.salary <= s.hisal;-- 方式二
select e.*,s.grade,s.losal,s.hisal from emp e ,salgrade s where e.salary between s.losal and s.hisal;
6.查询 “研发部” 所有员工的信息及 工资等级 *
解法一:内连接 + 查询条件
表: emp , salgrade , dept
连接条件 : emp.salary between salgrade.losal and salgrade.hisal ,emp.dept_id = dept.id
查询条件 : dept.name = ‘研发部’
select e.*,d.name,s.grade from emp e,dept d,salgrade s
where e.dept_id = d.id and ( e.salary between s.losal and s.hisal ) and d.name = '研发部';
解法二:子查询 + 内连接
子查询: 研发部员工子表 (select * from emp where dept_id = ( select id from dept where name = ‘研发部’ )) emp
表: emp , salgrade , dept
连接条件 : emp.salary between salgrade.losal and salgrade.hisal ,emp.dept_id = dept.id
-- 查询研发部的部门id
select id from dept where name = '研发部';-- 根据部门id,查询研发部的员工信息,得到研发部员工子表
select * from emp where dept_id = ( select id from dept where name = '研发部' );-- 再从研发部员工子表中查询信息
select e.*,d.name,s.grade
from ( select * from emp where dept_id = ( select id from dept where name = '研发部' ) ) e
inner join dept d inner join salgrade s on e.dept_id = d.id and ( e.salary between s.losal and s.hisal );
7.查询 “研发部” 员工的平均工资
解法一:内连接 + 查询条件
表: emp , dept
连接条件 : emp.dept_id = dept.id
查询条件 : dept.name = ‘研发部’
select avg(e.salary) from emp e, dept d where e.dept_id = d.id and d.name = '研发部';
解法二:子查询
-- 查询研发部的部门id
select id from dept where name = '研发部';-- 根据部门id,查询研发部的员工平均薪资
select avg(salary) from emp where dept_id = ( select id from dept where name = '研发部' );
8.查询工资比 “灭绝” 高的员工信息
-- 查询"灭绝"的工资
select salary from emp where name = '灭绝';-- 查询比指定工资高的员工信息
select * from emp where salary > ( select salary from emp where name = '灭绝' );
9.查询比平均薪资高的员工信息
-- 查询员工平均工资
select avg(salary) from emp;-- 查询比指定工资高的员工信息
select * from emp where salary > ( select avg(salary) from emp );
10.查询低于本部门平均工资的员工信息 *
解法一:自连接 + 子查询
表: emp e1,emp e2
连接条件 : e1.dept_id = e2.dept_id
-- 查询指定部门平均薪资
select avg(e1.salary) from emp e1 where e1.dept_id = 1;
select avg(e1.salary) from emp e1 where e1.dept_id = 2;-- 查询低于本部门平均工资的员工信息
select * from emp e2 where e2.salary < ( select avg(e1.salary) from emp e1 where e1.dept_id = e2.dept_id );
解法二:子查询 + 内连接 + 查询条件
子查询: 部门平均工资子表,( select dept_id,avg(salary) avg_sal from emp group by dept_id ) d
表: emp e,d
连接条件 : e.dept_id = d.dept_id
查询条件:e.salary < d.avg_sal
-- 查询各部门的平均工资(以部门分组),得到部门平均工资子表
select dept_id,avg(salary) avg_sal from emp group by dept_id;-- 查询低于本部门平均工资的员工信息
select e.* from emp e inner join ( select dept_id,avg(salary) avg_sal from emp group by dept_id ) d
on e.dept_id = d.dept_id where e.salary < d.avg_sal;
11.查询所有的部门信息, 并统计部门的员工人数 *
解法一:
select d.id, d.name , ( select count(*) from emp e where e.dept_id = d.id ) '人数' from dept d;
解法二:
-- 统计部门的员工人数,得到部门人数子表
select dept_id,count(*) emp_count from emp group by dept_id;-- 使用左连接,加上ifnull(d2.emp_count,0),当dept_id为null时显示为0,否则显示d2.emp_count
select d1.id,d1.name,ifnull(d2.emp_count,0) '人数' from dept d1
left join ( select dept_id,count(*) emp_count from emp group by dept_id ) d2 on d1.id = d2.dept_id;
12.查询所有学生的选课情况, 展示出学生名称, 学号, 课程名称 *
-- 方式一
select s.name,s.no,c.name from student s,course c,student_course sc
where s.id = sc.studentid and c.id = sc.courseid;-- 方式二
select s.name,s.no,c.name from student s inner join course c inner join student_course sc
on s.id = sc.studentid and c.id = sc.courseid;
总结:
在编写SQL语句时,先
select * from 表名
,在编写完from > where > group by > having
这些子句后,然后再看应该把*
改成哪张表的什么字段。多表查询时,当题中出现
对应
、关联
、及其
等字眼时,要考虑此时是外连接,然后列出表结构和连接条件,如表结构:emp, dept
,连接条件:emp.dept_id = dept.id
。在确定了是外连接的情况下,当出现需要查询某张
表的所有数据(信息)时
,要想到左外连接。相比内、外、自连接编写的SQL语句,使用子查询的方式编写的SQL语句会更为复杂。
6. 事务
6.1 事务简介
概念:事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
就比如: 张三给李四转账1000块钱,张三银行账户的钱减少1000,而李四银行账户的钱要增加1000。 这一组操作就必须在一个事务的范围内,要么都成功,要么都失败。
正常情况: 转账这个操作, 需要分为以下这么三步来完成 , 三步完成之后, 张三减少1000, 而李四增加1000, 转账成功。
异常情况: 转账这个操作, 也是分为以下这么三步来完成 , 在执行第三步是报错了, 这样就导致张三减少1000块钱, 而李四的金额没变, 这样就造成了数据的不一致, 就出现问题了。
为了解决上述的问题,就需要通过数据的事务来完成,我们只需要在业务逻辑执行之前开启事务,执行完毕后提交事务。如果执行过程中报错,则回滚事务,把数据恢复到事务开始之前的状态。
注意: 默认MySQL的事务是自动提交的,也就是说,当执行完一条DML语句时,MySQL会立即隐式的提交事务。
6.2 事务操作
6.2.1 未控制事务
数据准备:
-- 删除账户表
drop table if exists account;-- 创建账户表
create table account
(id int primary key AUTO_INCREMENT comment 'ID',name varchar(10) comment '姓名',money double(10, 2) comment '余额'
) comment '账户表';-- 向账户表插入数据
insert into account(name, money) VALUES ('张三', 2000), ('李四', 2000);
1.测试正常情况
-- 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 = '李四';
测试完毕之后检查数据的状态, 可以看到数据操作前后是一致的:
2.测试异常情况
先执行如下语句,恢复为初始数据:
-- 恢复为初始数据
update account set money = '2000' where name in('张三','李四');
执行以下异常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 = '李四';
测试完毕之后检查数据的状态, 发现数据在操作前后不一致了:
6.2.2 控制事务
有两种控制事务的方式:
- 修改事务的提交方式:
SET @@autocommit = 0
- 开启事务:
START TRANSACTION 或 BEGIN
1.修改事务的提交方式
-- 查看/设置事务提交方式
SELECT @@autocommit;
SET @@autocommit = 0; -- 1为自动提交,0为手动提交-- 提交事务
COMMIT;-- 回滚事务
ROLLBACK;
注意:上述的这种方式,我们是修改了事务的自动提交行为, 把默认的自动提交修改为了手动提交, 此时我们执行的DML语句都不会提交, 需要手动的执行commit进行提交。
2.开启事务
-- 开启事务
START TRANSACTION; -- 或BEGIN-- 提交事务
COMMIT;-- 回滚事务
ROLLBACK;
转账案例:
-- 开启事务
start transaction;-- 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;-- 如果执行过程中报错, 则回滚事务
rollback;
6.3 事务四大特性
事务的四大特性,简称 ACID
:
- 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
- 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
6.4 并发事务问题
并发处理事务时,可能会引起如下问题:
- 赃读
- 不可重复读
- 幻读
1.赃读:一个事务读到另外一个事务还没有提交的数据。
比如B读取到了A未提交的数据。
2.不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读。
事务A两次读取同一条记录,但是读取到的数据却是不一样的。
3.幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了 “幻影”。
6.5 事务隔离级别
为了解决并发事务所引发的问题,在数据库中引入了事务隔离级别。主要有以下几种:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read uncommitted | √ | √ | √ |
Read committed | × | √ | √ |
Repeatable Read(默认) | × | × | √ |
Serializable | × | × | × |
注:
1.√
代表会引发该现象,×
代表解决了该现象。
2.事务隔离级别越高,数据越安全,但是性能越低。
-- 查看事务隔离级别
SELECT @@TRANSACTION_ISOLATION;-- 设置事务隔离级别
SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL
{ READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE };
【MySQL】MySQL基础知识详解相关推荐
- R语言基础知识详解及概括
R语言基础知识详解及概括 目录 R语言基础知识详解及概括 R数据可视化示例 R语言进行数据创建
- R语言可视化绘图基础知识详解
R语言可视化绘图基础知识详解 图形参数:字体.坐标.颜色.标签等: 图像符号和线条: 文本属性: 图像尺寸及边界: 坐标轴.图例自定义等: 图像的组合: #install.packages(c(&qu ...
- 计算机网络相关知识 参考博客 子网掩码怎么理解 网关及网关的作用 路由器基础知识详解
子网掩码怎么理解 https://blog.csdn.net/farmwang/article/details/64132723 网关及网关的作用 https://blog.csdn.net/zhao ...
- RabbitMQ基础知识详解
RabbitMQ基础知识详解 2017年08月28日 20:42:57 dreamchasering 阅读数:41890 标签: RabbitMQ 什么是MQ? MQ全称为Message Queue, ...
- Android随机点名器,Excel基础知识-详解随机点名器
说道制作个案例纯粹意外,我多少有点选择恐惧症,为了不在"选择"上纠结,就自己小玩了一下,就用了程序做了个选择器,其实很简单,就是有小时候玩的"点兵点将",稍微变 ...
- Python基础知识详解 从入门到精通(八)魔法方法
目录 Python基础知识详解 从入门到精通(八)魔法方法 什么是魔法方法 基础魔法方法(较为常用) 比较操作符 算数运算符 反运算(类似于运算方法) 增量赋值运算 一元操作符 类型转换 上下文管理( ...
- 网络管理之基础知识详解
网络管理之基础知识详解 目录 3.1 网络的特征 3.2 拓扑结构 4.1 OSI简介 4.2 数据传输过程 4.3 分层作用 4.4 PDU 5.1 单播 5.2 多播 5.3 广播 5.4 三种通 ...
- 工业相机基础知识详解
工业相机基础知识详解 工业相机是机器视觉系统的一个最关键的组件.他的功能很简单,就是将被检测的物体拍摄下来,然后转换成电脑可以识别的图像,以便以后进行图像处理,从而完成检测任务.工业相机俗称工业摄像机 ...
- 小白入门!网络安全基础知识详解(附知识问答)
小白入门!网络安全基础知识详解(附知识问答) 一.引论 提到网络安全,一般人们将它看作是信息安全的一个分支,信息安全是更加广义的一个概念:防止对知识.事实.数据或能力非授权使用.误用.篡改或拒绝使用所 ...
- 【BLE】OTA基础知识详解
[BLE]OTA基础知识详解 一. 概念 1. 缩写 BIM Boot Image Manager , the software bootloader CRC cyclic redundancy ch ...
最新文章
- 招聘:兼职ASP 高级工程师
- 一文看懂机器学习中的常用损失函数
- centos下加入mysql环境变量
- Java类加载的那些事
- java PageUtil + stream 手动计算List 分页
- 计算机应用基础教学改革参考文献,关于计算机应用基础论文范文资料 与计算机应用基础课程教学改革分析有关论文参考文献...
- 安卓手机上跑_第一名易主?看看安兔兔公布的二月份安卓手机性能跑分排行
- radio选中事件怎么绑定_Vue双向绑定
- python读取坐标文本文件_使用python读取txt坐标文件生成挖空矿山_探矿批量
- 学生成绩abcde怎样划分_7月学考成绩出来啦!
- 三维点云学习(4)2-mean shift dbscan
- 学习V神的手把手教你写脚本引擎 一
- python数据导出excel_python实现数据导出到excel的示例
- 从阿尔法元的诞生看终极算法的可能性
- 庞皓计量经济学第四版_庞皓计量经济学第4版笔记和课后答案
- java 进度条 不更新_java进度条不动怎么解决?
- stata抓取html,Stata 网页表格爬取示例
- 双耳节拍 枕头_枕头2.2.1发布
- Excel常用电子表格公式大全1-2
- SylixOS命令行下内存操作/测试工具
热门文章
- 提升企业销售业绩的关键:“行动派”CRM系统!
- long型转换为int型
- 三星c7pro android版本,三星c7pro和c9pro哪个值得买?三星c7pro和c9pro详细区别点对比评测图解...
- android webview m3u8,M3U8视频,在系统自带浏览器可以播放,但是在uni-app打开同样的网页就无法播放...
- 手机显示无法启动服务器失败是什么意思啊,绝地求生进不去显示启动服务器失败怎么回事 | 手游网游页游攻略大全...
- 从数据库到固态硬盘的读取过程
- 数据结构-------字符串和多维数组
- 怎样把几张照片合成一张动态照片,照片拼图制作
- qq游戏大厅2015官方下载正式版 v3.6 免费版​
- _Ob_:Observer 怎么遍历