MySQL教程

一、数据库相关概念

1.1 数据库

1
2
1.数据库即存储数据的仓库,数据是有组织的存储
2.英文:DataBase,简称:DB

1.2 数据库软件

1
2
1.数据库软件即管理数据库的软件
2.英文:DataBase Management System,简称:DBMS

1.3 SQL

1
2
3
1.英文:Structured Query Langeage,简称:SQL,结构化查询语言
2.操作关系型数据库的编程语言
3.定义操作所有关系型数据库的统一标准

二、常见的关系型数据库管理系统

1
2
3
4
5
6
7
8
MySQL: 开源免费的中小型数据库
Oracel: 收费的大型数据库
SQL Server: MicroSoft公司收费的的中型数据库
DB2: IBM公司的大型收费数据库
MariaDB: 开源免费的中小型数据库
PostgreSQL: 开源免费的中小型数据库
SQLite: 嵌入式的微型数据库,如作为Android内置数据库
……

三、MySQL数据库

3.1 安装MySQL

1
2
3
在Centos7中,通过Docker安装MySQL(8.0.27),注:目前学习阶段未对mysql的数据目录做数据卷的处理,删除容器后数据也会消失,因此删除容器前注意备份数据。

安装步骤如下:
  1. 拉取镜像
1
docker pull mysql:8.0.27
  1. 运行镜像,产生容器
1
docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root --name=mysql mysql

3.2 设置Root账户可远程访问数据库

在 MySQL 8.0 中,要设置 root 用户可以从远程主机访问,你需要执行几个步骤。以下是如何实现的步骤:

  1. 登录MySQL

首先,你需要以 root 用户身份登录到 MySQL 服务器。这通常是在服务器上本地完成的。

1
mysql -u root -p

输入密码后,你将进入 MySQL 提示符。

  1. 创建远程 root 用户(如果还没有的话)

默认情况下,MySQL 的 root 用户可能只允许从 localhost 访问。要允许从任何主机访问,你需要为该用户指定一个 ‘%’ 通配符,但这并不安全。建议只允许来自特定主机的连接。但如果你只是想测试或临时允许从任何主机访问,可以这样做:

1
2
CREATE USER 'root'@'%' IDENTIFIED BY 'YOUR_PASSWORD';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;

注意:YOUR_PASSWORD 需要替换为你的密码。出于安全考虑,强烈建议仅为受信任的主机创建用户,而不是使用 ‘%’。

  1. 刷新权限

为了让新的权限设置立即生效,你需要刷新 MySQL 的权限表:

1
FLUSH PRIVILEGES;
  1. 开放3306端口
1
2
firewall-cmd --zone=publish --add-port=3306/tcp --permanent
firewall-cmd --reload
  1. 测试远程连接

从远程主机使用 MySQL 客户端工具(如 mysql 命令行工具或任何图形界面工具)尝试连接到 MySQL 服务器。确保使用正确的 IP 地址、端口和凭据。

1
mysql -h YOUR_SERVER_IP -u root -p

输入你在步骤 2 中设置的密码。

注意:允许从远程主机访问 MySQL 的 root 用户可能会带来安全风险。确保你了解这些风险,并仅在必要时采取此操作。在生产环境中,通常建议创建具有适当权限的专用用户来管理远程连接。

3.3 卸载MySQL

1
通过Docker安装的MySQL,卸载十分的方便,卸载MySQL即删除对应的容器,注:目前学习阶段未对mysql的数据目录做数据卷的处理,删除容器后数据也会消失,因此删除容器前注意备份数据。

删除容器的命令

1
docker rm 容器Id

3.4 MySQL的数据模型

关系型数据库

1
关系型数据库即通过多张能够相互连接的二维表来存储数据的数据库

数据模型

image-20240601100207516

1
2
3
4
5
一个MySQL数据库管理系统中可以创建多个数据库,一个数据库中可以创建多张数据表,一个表中可以存放多条数据。

数据库:文件夹的方式存在
数据表:以fmf结尾的文件
数据:以MY0结尾的文件

四、SQL

通过SQL,来告诉数据库管理系统做什么操作。
根据MySQL的数据模型可以看出,需要通过SQL来操作数据库、数据表、以及数据表中的数据。操作的类型分为:增、删、改、查

4.1 SQL通用语法

1
2
3
4
5
6
1.SQL语句可以单行或多行书写,以分号结尾
2.SQL语句不区分大小写,关键字建议使用大写
3.注释:
-- 单行注释内容
/* 多行注释内容 */
# 单行注释内容(MySQL 特有)

4.2 SQL分类

1
2
3
4
DDL: 数据定义语言,用于定义数据库对象: 数据库、表、视图、索引、存储过程等(操作数据库和、数据表、视图、索引、存储过程)
DML: 数据库操作语言,用于对数据库中的表的数据进行增删改的操作(操作表中的数据)
DQL: 数据库查询语言,用于对数据库中的表的数据进行查询操作(操作表中的数据)
DCL: 数据库控制语言,用于定义数据库的访问权限和安全级别(用户、用户权限、事务等操作)

image-20240601112506390

4.3 DDL

4.3.1 数据库相关操作

增加数据库

1
2
3
4
5
6
7
# 创建数据库
create database 数据库名;
# 创建数据库,当指定的数据库不存在时才执行
create database if not exists 数据库名;
# 在创建数据库的同时指定数据库的字符集(数据存储在数据库中采用的编码格式 utf8 gbk)
create database 数据库名 character set utf8;
create database if not exists 数据库名 character set utf8;

删除数据库

1
2
3
4
# 删除数据库
drop database 数据库名;
# 删除数据库,当指定的数据库存在时才执行
drop database if exists 数据库名;

修改数据库

1
2
# 修改数据库的字符集
alter database 数据库名 character set utf8;

查询数据库

1
2
3
4
5
6
# 显示当前mysql数据库中的数据库列表
show databases;
# 查看当前使用的数据库
select database();
# 显示指定名称的数据库创建的SQL指令
show create database 数据库名;

使用数据库

1
use 数据库名;

4.3.2 数据表相关操作

增加表

注:字段的最后一行尾不能加逗号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建表
create table 表名(
字段名1 数据类型1 [约束] [comment 字段描述],
字段名2 数据类型2 [约束] [comment 字段描述],
……
字段名n 数据类型n [约束] [comment 字段描述]
);

# 创建表,当表不存在时才执行
create table if not exists 表名(
字段名1 数据类型1 [约束] [comment 字段描述],
字段名2 数据类型2 [约束] [comment 字段描述],
……
字段名n 数据类型n [约束] [comment 字段描述]
);

删除表

1
2
3
4
# 删除表
drop table 表名;
# 删除表,当表存在时才执行
drop table if exists 表名;

修改表

1
2
3
4
5
6
7
8
9
10
11
12
# 修改表名:
alter table 表名 rename to 新表名;
# 修改表的字符集
alter table 表名 character set utf8;
# 添加列:
alter table 表名 add 列名 数据类型 [约束] [comment 字段描述];
# 删除字段:
alter table 表名 drop 列名;
# 修改列的数据类型:
alter table 表名 modify 列名 新的数据类型;
# 修改列的列名和数据类型:
alter table 表名 change 列名 新列名 新数据类型

查询表

1
2
3
4
# 查询当前数据库下的所有表的名称:
show tables;
# 查询表结构:
desc 表名称;

4.3.3 数据类型

数值类型

类型 内存空间大小 范围 说明
tinyint 1byte 有符号 -128127, 无符号 0255 特小型整数(年龄)
smallint 2byte 有符号 -3276832767, 无符号 065535 小型整数
mediumint 3byte 有符号 -2^312^31-1, 无符号 02^32-1 中型整数
int/integer 4byte 整数
bigint 8byte 大型整数
float 4byte 单精度
double 8byte 双精度
decimal 一般情况: 第一个参数+2 decimal(10,2),表示数值一共10为位,小数位有2位

字符串类型

1
2
3
4
5
在数据库中存储图片或者视频音频等内容,一般是存储,文件在服务器上的路径,当然如
果非要存储就需要将图片等数据转成二进制进行存储,所以blob类型是可以存储所有类型的,但
是前提是需要转换成二进制,所以此类型用的很少。

longtext类型一般用于varchar类型储存不下的时候。
类型 字符长度 说明
char 0~255字节 定长字符串,最多可以存储255个字符;当我们指定数据表字段为char(n)时,此列中的数据最长为n,如果添加的数据少于n,则补’\u0000’至n长度
varchar 0~65535字节 可变长度字符串,此类型的列最大长度为65535
tinyblob 0~255字节 存储二进制字符串
blob 0~65535字节 存储二进制字符串
mediumblob 0~1677215字节 存储二进制字符串
longblob 0~4294967295字节 存储二进制字符串
tinytext 0~255字节 文本数据(字符串)
text 0~65535字节 文本数据(字符串)
mediumtext 0~1677215字节 文本数据(字符串)
longtext 0~4294967295字节 文本数据(字符串)

日期类型

类型 格式 说明
date yyyy-MM-dd 日期,只存储年月日
time HH:mm:ss 时间,只存储时分秒
datetime yyyy-MM-dd HH:mm:ss 日期+时间,存储年月日时分秒
year yyyy 年份
timestamp 时间戳 日期+时间 (时间戳)

4.4 DML

4.4.1 增加数据

1
insert into 表名(列名1,列名2,列名3,……) values(值1,值2,值3,……)
1
insert into 表名 values(值1,值2,值3,……)
1
insert into 表名(列名1,列名2,列名3,……) values(值1,值2,值3,……),(值1,值2,值3,……)……;
1
insert into 表名 values(值1,值2,值3,……),(值1,值2,值3,……),

4.4.2 删除数据

1
delete from 表名 [where 条件]

4.4.3 修改数据

1
update 表名 set 列名1=1,列名2=2,…… [where 条件]

4.5 DQL

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
select 
字段列表
from
表名列表
where
条件列表
group by
分支字段列表
having
分组后的条件列表
order by
排序字段列表
limit
分页限定

查询分类

1
2
3
4
5
基础查询
条件查询(where)
分组查询(group by)
排序查询(order by)
分页查询(limit)

4.5.1 基础查询

格式

1
select 字段列表 from 表名
1
select * from 表名

去除重复记录

1
select distinct 字段列表 from 表名

起别名

1
2
select 字段 as 字段别名 from 表名
select 字段 字段别名 from 表名

4.5.2 条件查询

格式

1
select 字段列表 from 表名 where 条件列表

条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>
<
=
>=
<=
!=或<>
between …… and ……
in(……)
like
is null
is not null


and
or
not

4.5.3 分组查询

聚合函数

image-20240601131353025

格式

1
select 字段列表 from 表名 [where 条件列表] group by 分组字段名 [having 分组后的过滤条件]

注意事项

1
分组后查询的字段为 聚合函数和分组字段,查询其他字段无任何意义

where与having的区别

1
2
3
4
1.执行时机不一样: where是在分组之前进行限定,不满足where条件的数据,不参与分组,而having是分组之后对结果进行过滤
2.判断条件不一样: where不能对聚合函数进行判断,having可以

执行顺序: where > 聚合函数 > having

4.5.4 排序查询

格式

1
select 字段列表 from 表名 order by 排序字段名1 [排序方式1],排序字段名2 [排序方式2]……

排序方式

1
2
3
asc : 升序(默认值)
desc : 降序
注:如果有多个排序条件,当前面的条件值一样时,才会根据第二条件排序

4.5.5 分页查询

格式

1
select 字段列表 from 表名 limit 起始索引,查询条目数

注意事项

1
起始索引: 从0开始
1
计算公式: 起始索引=(当前页码-1)*每页显示的条数
1
2
3
分页查询limit是MySQL数据库的方言
Oracel分页查询使用rownumber
SQL SERVER分页查询使用top

五、约束

5.1 约束的概念

1
2
约束是作用于表中列上的规则,用于现状加入表中的数据
约束的存在保证了数据库中的数据的完整性、有效性、正确性

5.2 约束分类

image-20240601143403572

注:MySQL不支持检查约束

5.3 自增长

1
2
3
条件: 当列是数字类型的时候可以加自增长

auto_increment

5.4 添加约束(非外键约束)

5.4.1 创建表的时候添加

1
2
3
4
5
6
create table 表名(
列名 数据类型 约束,
列名 数据类型 约束,
……
列名 数据类型 约束
)

5.4.2 创建完表之后添加

1
alter table 表名 modify 字段 数据类型 约束;

5.5 删除约束(非外键约束)

1
alter table 表名 modify 字段 数据类型;

5.6 添加外键约束

5.6.1 创建表的时候添加

1
2
3
4
5
6
7
create table 表名(
列名 数据类型,
列名 数据类型,
……
列名 数据类型,
constraint 外键名称 foreign key(外键列名) references 主表(主表列名)
)

5.6.2 创建完表之后添加

1
alter table 表名 add constraint 外键名称 foreign key(外键列名) references 主表(主表列名)

5.7 删除外键约束

1
alter table 表名 drop foreign key 外键名称

六、数据库设计

6.1 数据库设计的概念

1
数据库设计即针对某个系统的需求进行设计,确定需要哪些表、表中有那些字段以及表于表之间的关系

6.2 表与表之间的关系

6.2.1 一对一

1
2
3
4
5
6
一对一
如:人与身份证号
一个人拥有一个身份证号,一个身份证号对应一个人
实现方式:在任意一方加外键,指向另一方的主键,且在外键字段上添加 唯一约束

一对一的表关系一般用于表的拆分,将常用的字段放在一张表,不常用的字段放在另一张表,用于提升查询性能

6.2.2 一对多

1
2
3
4
一对多
如:部门和员工
一个部门对应多个员工,一个员工对应一个部门
实现方式:在多的一方建立外键,指向一的一方的主键

6.2.3 多对多

1
2
3
4
多对多:
如:订单和商品
一个订单可以包含多个商品,一种商品可以属于多个订单
实现方式:建立第三种中间表,中间表至少包含两个外键,分别关联两方的主键

七、多表查询

7.1 笛卡尔积

笛卡尔积,取A、B集合所有组合情况,最终查询出来的数据条数为多张表的分别记录条数的乘积,这种方式的查询会存在许多无效的数据,因此通常不会使用下面的查询方式

1
select 字段列表 from A,B……

7.2 连接查询

7.2.1 内连接

内连接:相当于查询A、B交集数据

隐式内连接

1
select 字段列表 from A,B where A.id = B.id

显示内连接

1
select 字段列表 from A [inner] join B on A.id = B.id

7.2.2 外连接

左外连接

左外连接:相当于查询A表所有数据和交集部分数据

1
select 字段列表 from A left [outer] join B on A.id = B.id

右外连接

左外连接:相当于查询B表所有数据和交集部分数据

1
select 字段列表 from A right [outer] join B on A.id = B.id

7.3 子查询

子查询:查询中嵌套查询,嵌套查询为子查询。

1
2
3
4
子查询根据查询结果不同,作用不同,可分为:
单行单列
多行单列
多行多列

7.3.1 单行单列

单行单列: 作为条件值,使用>、=、<等进行条件判断

1
select 字段列表 from 表 where 字段名 = (子查询)

7.3.2 多行单列

多行单列: 作为条件值,使用in关键字进行条件判断

1
select 字段列表 from 表 where 字段名 in (子查询)

7.3.3 多行多列

多行多列: 作为虚拟表

1
select 字段列表 from (子查询) where 条件

八、事务

8.1 事务简介

1
2
3
数据库的事务(Transaction)是一种机制,一个操作序列,包含了一组数据库操作命令。
事务把所有的命令看作一个整体一起向系统提交或撤销操作请求,即这组数据库命令要么同时成功,要么同时失败。
事务是一个不可分割是工作逻辑单元

8.2 事务操作

开启事务

开启事务:即告诉系统,下面的数据库操作命令为临时的操作,不会真正的操作数据库中的数据,直到提交事务时才真正的对数据库进行操作

1
2
3
start transaction;

begin;

提交事务

提交事务:即告诉系统,所有的临时操作已经编写完成,可以按照临时操作对数据库中的数据进行真正的操作

1
commit;

回滚事务

回滚事务:即出现异常时,告诉系统撤销临时操作,回到开启事务前的状态

1
rollback;

8.3 事务四大特性

原子性 (Atomicity): 事务是不可分割的最小操作单元,要么同时成功,要么同时失败

一致性(Consistency): 事务完成时,必须使所有的数据都保持一致状态

隔离性(Isolation): 多个事务之间相互隔离,操作的可见性

持久性(Durability): 事务一旦提交或回滚,它对数据库中的数据的改变就是永久的

8.4 MySQL事务的提交方式

1
2
3
4
5
6
7
8
select @@autocommit

查询结果为1,表示自动提交,MySQL事务默认自动提交
查询结果为0,表示需手动提交

修改事务的提交方式:
set @@autocommit = 1;
set @@autocommit = 0;

8.5 事务的隔离级别

1
数据库允许多个事务并行,多个事务之间是隔离的、相互独立的;如果事务之间不相互隔离并且操作同一数据时,可能会导致数据的一致性被破坏。

8.5.1 读未提交(read uncommitted)

1
2
T2可以读取T1执行但未提交的数据;可能会导致出现脏读;
脏读,一个事务读取到了另一个事务中未提交的数据

image-20241013150847083

8.5.2 读已提交(read committed)

1
2
3
T2只能读取T1已经提交的数据;避免了脏读,但可能会导致不可重复度(虚读)
不可重复度(虚读): 在同一个事务中,两次查询操作读取到数据不一致
例如:T2进行第一次查询之后在第二次查询之前,T1修改并提交了数据,T2进行第二次查询时读取到的数据和第一次查询读取到数据不一致。

image-20241013151027691

8.5.3 可重复读(repeatable read)

1
2
T2执行第一次查询之后,在事务结束之前其他事务不能修改对应的数;避免了不可重复读(虚读),但可能会导致幻读
幻读,T2对数据表中的数据进行修改然后查询,在查询之前T1向数据表中新增了一条数据,就导致T2以为修改了所有数据,但却查询出了与修改不一致的数据(T1事务新增的数据)

image-20241013151201835

8.5.4 串行化(serializable)

1
同时只允许一个事务对数据表进行操作;避免了脏读、虚读、幻读问题

image-20241013151244042

8.5.5 设置数据库事务隔离级别

1
2
3
我们可以通过设置数据库默认的事务隔离级别来控制事务之间的隔离性;
也可以通过客户端与数据库连接设置来设置事务间的隔离性(在应用程序中设置--Spring);
MySQL数据库默认的隔离级别为 可重复读
  • 查看MySQL数据库默认的隔离级别

    1
    2
    3
    4
    -- 在MySQL8.0.3 之前
    select @@tx_isolation;
    -- 在MySQL8.0.3 之后
    select @@transaction_isolation;
  • 设置MySQL默认隔离级别

    1
    set session transaction isolation level 隔离级别;

九、存储过程

9.1 SQL指令执行过程

image-20241011214033100

1
2
3
4
5
从SQL执行执行的流程中我们分析存在的问题:
1. 如果我们需要重复多次执行相同的SQL,SQL指令都需要通过连接传递到MySQL,并且需要
经过编译和执行的步骤;
2. 如果我们需要连续执行多个SQL指令,并且第二个SQL指令需要使用第一个SQL指令执行的
结果作为参数;

9.2 存储过程介绍

image-20241011214142677

1
2
3
存储过程:
将能够完成特定功能的SQL指令进行封装(SQL指令集),编译之后存储在数据库服务器
上,并且为之取一个名字,客户端可以通过名字直接调用这个SQL指令集,获取执行结果。

9.3 存储过程优缺点分析

存储过程优点

1
2
3
4
5
6
1. SQL指令无需客户端编写,通过网络传送,可以节省网络开销,同时避免SQL指令在网络传
输过程中被恶意篡改保证安全性;
2. 存储过程经过编译创建并保存在数据库中的,执行过程无需重复的进行编译操作,对SQL指
令的执行过程进行了性能提升;
3. 存储过程中多个SQL指令之间存在逻辑关系,支持流程控制语句(分支、循环),可以实现
更为复杂的业务;

存储过程缺点

1
2
3
4
5
1. 存储过程是根据不同的数据库进行编译、创建并存储在数据库中;当我们需要切换到其他的
数据库产品时,需要重写编写针对于新数据库的存储过程;
2. 存储过程受限于数据库产品,如果需要高性能的优化会成为一个问题;
3. 在互联网项目中,如果需要数据库的高(连接)并发访问,使用存储过程会增加数据库的连
接执行时间(因为我们将复杂的业务交给了数据库进行处理)

9.4 创建存储过程

语法

1
2
3
4
create procedure 存储过程名称([IN/OUT args])
begin
 -- SQL
end;

示例

1
2
3
4
5
-- 创建一个存储过程实现加法运算:
create procedure proc_test1(IN a int,IN b int,OUT c int)
begin
  SET c = a+b;
end;

9.5 调用存储过程

语法

1
call 存储过程名称(args……)

示例

1
2
3
4
5
6
7
-- 调用存储过程
-- 定义变量@m
set @m = 0;
-- 调用存储过程,将3传递给a,将2传递给b,将@m传递给c
call proc_test1(3,2,@m);
-- 显示变量值(dual系统表,无需创建,定义变量的值都会在这里)
select @m from dual;

9.6 存储过程中的变量

存储过程中的变量分为两种:局部变量 和 用户变量

9.6.1 定义局部变量

语法

1
2
-- 局部变量要定义在存储过程中,而且必须定义在存储过程开始
declare 参数名 参数类型 [default 默认值];

示例

1
2
3
4
5
6
7
8
create procedure proc_test2(IN a int,OUT r int)
begin
declare x int default 0;  -- 定义x int类型,默认值为0
declare y int default 1;  -- 定义y
set x = a*a;
set y = a/2;
set r = x+y;
end;

9.6.2 定义用户变量

用户变量:相当于全局变量,定义的用户变量可以通过 select @attrName from dual 进行查询

语法

1
2
3
-- 用户变量会存储在mysql数据库的数据字典中(dual)
-- 用户变量定义使用set关键字直接定义,变量名要以@开头
set @n=1;

9.6.3 给变量赋值

无论是局部变量还是用户变量,都是使用 set 关键字修改值

语法

1
set 变量名=

示例

1
2
3
set @n=1;
call proc_test2(6,@n);
select @n from dual;

9.6.4 将查询结果赋值给变量

1
在存储过程中使用select..into..给变量赋值

示例

1
2
3
4
5
6
7
8
9
10
-- 查询学生数量
-- 注意在储存过程中使用SQL语句需要将结果赋值给变量,那么就需要使用into关键字来进
行赋值
create procedure proc_test3(OUT c int)
begin
  select count(stu_num) INTO c from students; -- 将查询到学生数量赋值给c
end;
-- 调用存储过程
call proc_test3(@n);
select @n from dual;

9.6.5 用户变量使用注意事项

1
因为用户变量相当于全局变量,可以在SQL指令以及多个存储过程中共享,在开发中建议尽量少使用用户变量,用户变量过多会导致程序不易理解、难以维护。

9.7 存储过程的参数

MySQL存储过程的参数一共有三种:IN \ OUT \ INOUT

9.7.1 IN 输入参数

输入参数——在调用存储过程中传递数据给存储过程的参数(在调用的过程必须为具有实际值的 变量 或者 字面值)

示例

1
2
3
4
5
6
7
-- 创建存储过程:添加学生信息
create procedure proc_test4(IN snum char(8),IN sname varchar(20), IN gender char(2), IN age int, IN cid int, IN remark varchar(255))
begin
 insert into students(stu_num,stu_name,stu_gender,stu_age,cid,remark)
values(snum,sname,gender,age,cid,remark);
end;
call proc_test4('20220108','小丽','女',20,1,'aaa');

9.7.2 OUT 输出参数

输出参数——将存储过程中产生的数据返回给过程调用者,相当于Java方法的返回值,但不同的是一个存储过程可以有多个输出参数

示例

1
2
3
4
5
6
7
8
-- 创建存储过程,根据学生学号,查询学生姓名
create procedure proc_test5(IN snum char(8),OUT sname varchar(20))
begin
  select stu_name INTO sname from students where stu_num=snum;
end;
set @name='';
call proc_test5('20220107',@name);
select @name from dual;

9.7.3 INOUT 输入输出参数

输入输出参数——该类型的参数既是输入参数也是输入参数。注意:此方式不建议使用,一般我们输入就用 IN 输出就用OUT,此参数代码可读性低,容易混淆。

示例

1
2
3
4
5
6
7
create procedure proc_test6(INOUT str varchar(20))
begin
  select stu_name INTO str from students where stu_num=str;
end;
set @name='20220108';
call proc_test6(@name);
select @name from dual;

9.8 存储过程中的流程控制

9.8.1 分支语句

  • if-then-else
1
2
3
4
5
6
7
8
9
10
11
-- 单分支:如果条件成立,则执行SQL
if conditions then
-- SQL
end if;
-- 如果参数a的值为1,则添加一条班级信息
create procedure proc_test7(IN a int)
begin
if a=1 then
insert into classes(class_name,remark) values('Java2209','test');
end if;
end;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 双分支:如果条件成立则执行SQL1,否则执行SQL2
if conditions then
-- SQL1
else
-- SQL2
end if;
-- 如果参数a的值为1,则添加一条班级信息;否则添加一条学生信息
create procedure proc_test7(IN a int)
begin
if a=1 then
insert into classes(class_name,remark) values('Java2209','test');
else
   insert into students(stu_num,stu_name,stu_gender,stu_age,cid,remark) values('20220110','小花','女',19,1,'...');
end if;
end;
  • case
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- case
create procedure proc_test8(IN a int)
begin
 case a
when 1 then  
-- SQL1   如果a的值为1 则执行SQL1
insert into classes(class_name,remark) values('Java2210','wahaha');
when 2 then
-- SQL2   如果a的值为2 则执行SQL2
insert into students(stu_num,stu_name,stu_gender,stu_age,cid,remark) values('20220111','小刚','男',21,2,'...');
else
-- SQL (如果变量的值和所有when的值都不匹配,则执行else中的这个SQL)
update students set stu_age=18 where stu_num='20220110';
 end case;
end;

9.8.2 循环语句

  • while
1
2
3
4
5
6
7
8
9
10
11
12
13
-- while
create procedure proc_test9(IN num int)
begin
 declare i int;
set i = 0;
while i<num do
-- SQL
insert into classes(class_name,remark) values( CONCAT('Java',i)
,'....');
set i = i+1;
end while;
end;
call proc_test9(4);
  • repeat
1
2
3
4
5
6
7
8
9
10
11
12
13
-- repeat
create procedure proc_test10(IN num int)
begin
declare i int;
set i = 1;
repeat
-- SQL
insert into classes(class_name,remark) values( CONCAT('Python',i)
,'....');
set i = i+1;
until i > num end repeat;
end;
call proc_test10(4);
  • loop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- loop
create procedure proc_test11(IN num int)
begin
  declare i int ;
set i =0;
myloop:loop
-- SQL
insert into classes(class_name,remark) values( CONCAT('HTML',i)
,'....');
set i = i+1;
# 结束循环的条件
if i=num then
  # 离开循环
  leave myloop;
end if;
end loop;
end;
call proc_test11(5);

9.9 查询存储过程

存储过程是属于某个数据库的,也就是说当我们将存储过程创建在某个数据库之后,只能在当前数据库中调用此存储过程。
查询存储过程:查询某个数据库中有哪些存储过程

1
2
3
4
5
6
-- 查询所有存储过程
show procedure;
-- 根据数据库名,查询当前数据库中的存储过程
show procedure status where db='db_test2';
-- 查询存储过程的创建细节
show create procedure db_test2.proc_test1;

9.10 修改存储过程

修改存储过程指的是修改存储过程的特征/特性

1
alter procedure 存储过程名称 特征1 [特征2 特征3 ....]

存储过程的特征参数:

  • CONTAINS SQL 表示子程序包含 SQL 语句,但不包含读或写数据的语句

  • NO SQL 表示子程序中不包含 SQL 语句

  • READS SQL DATA 表示子程序中包含读数据的语句

  • MODIFIES SQL DATA 表示子程序中包含写数据的语句

  • SQL SECURITY { DEFINER |INVOKER } 指明谁有权限来执行

  • DEFINER 表示只有定义者自己才能够执行

  • INVOKER 表示调用者可以执行

  • COMMENT ‘string’ 表示注释信息

9.11 删除存储过程

1
2
3
4
-- 删除存储过程
-- drop 删除数据库中的对象 数据库、数据表、列、存储过程、视图、触发器、索引....
-- delete 删除数据表中的数据
drop procedure 存储过程名称;

9.12 游标

问题:如果我们要创建一个存储过程,需要返回查询语句查询到的多条数据,该如何实现呢?

9.12.1 游标的概念

游标可以用来依次取出查询结果集中的每一条数据——逐条读取查询结果集中的记录

9.12.2 游标使用步骤

声明游标

1
2
3
4
5
-- 声明游标语法
DECLARE 游标名称 CURSOR FOR select_statement;

-- 示例
declare mycursor cursor for select class_id,class_name from classes;

打开游标

1
2
3
4
5
-- 语法
open 游标名称;

-- 示例
open mycursor;

使用游标

1
2
3
4
5
6
-- 语法
-- 使用游标:提取游标当前指向的记录(提取之后,游标自动下移)
fetch 游标名称 into xxx,yyy;

-- 示例
fetch mycursor into cid,cname;

关闭游标

1
2
3
4
5
-- 语法
CLOSE 游标名称;

-- 示例
CLOSE mycursor;

9.12.3 游标使用案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
-- MySQL中, Concat_WS()  函数 用来通过指定符号,将2个或多个字段拼接在一起,返回拼接后的字符串。
create procedure proc_test12(out result varchar(200))
begin
# 游标变量
declare cid int;
# 游标变量
declare cname varchar(20);
# 计数变量
declare num int;
# 计数变量
declare i int;
# 每条数据
declare str varchar(100);
# 查询语句执行之后返回的是一个结果集(多条记录),使用游标遍历查询结果集
declare mycursor cursor for select class_id,class_name from classes;
# 记录总数据量
select count(*) into num from classes;
# 打开游标
open mycursor;
set i = 0;
# 开始遍历游标
while i<num do
# 提取游标中的数据,并将结果赋值给游标变量
fetch mycursor into cid,cname;
set i = i+1;
# set str=concat_ws('~',cid,cname); 不同的写法
select concat_ws('~',cid,cname) into str;
set result = concat_ws(',',result,str);
end while;
close mycursor;
end;
# 案例测试
set @r = '';
call proc_test12(@r);
select @r from dual;

十、触发器

10.1 触发器介绍

1
2
触发器,就是一种特殊的存储过程。触发器和存储过程一样是一个能够完成特定功能、存储在数据库服务器上的SQL片段,但是触发器无需调用,当对数据表中的数据执行DML操作时自动触发
这个SQL片段的执行,无需手动调用。在MySQL,只有执行insert\delete\update操作才能触发触发器的执行

10.2 创建触发器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 语法
create trigger 触发器名称
<before|after>                     -- 定义触发时机
<insert|delete|update>             -- 定义DML类型
ON 表名
for each row   -- 声明为行级触发器(只要操作一条记录就触发触发器执行一次)
sql_statement                   -- 触发器操作


-- 示例
create trigger tri_test1
after insert on students
for each row
insert into stulogs(time,log_text) values(now(), concat('添加',NEW.stu_num,'学生信息'));

10.3 查看触发器

1
show triggers;

10.4 删除触发器

1
drop trigger 触发器名称;

10.5 NEW与OLD

1
2
3
触发器用于监听对数据表中数据的insert、delete、update操作,在触发器中通常处理一些DML的关联操作;我们可以使用 NEW 和 OLD 关键字在触发器中获取触发这个触发器的DML操作的数据
NEW: 在触发器中用于获取insert操作添加的数据、update操作修改后的记录
OLD: 在触发器中用于获取delete操作删除前的数据、update操作修改前的数据

NEW

  • insert操作中:NEW表示添加的新记录
1
2
3
4
create trigger tri_test1
after insert on students
for each row
insert into stulogs(time,log_text) values(now(), concat('添加',NEW.stu_num,'学生信息'));
  • update操作中:NEW 表示修改后的数据
1
2
3
4
-- 创建触发器 : 在监听update操作的触发器中,可以使用NEW获取修改后的数据
create trigger tri_test2
after update on students for each row
insert into stulogs(time,log_text) values(now(), concat('修改学生信息为:',NEW.stu_num,NEW.stu_name));

OLD

  • delete操作中:OLD表示删除的记录
1
2
3
create trigger tri_test3
after delete on students for each row
insert into stulogs(time,log_text) values(now(), concat('删除',OLD.stu_num,'学生信息'));
  • update操作中:OLD表示修改前的记录
1
2
3
create trigger tri_test2
after update on students for each row
insert into stulogs(time,log_text) values(now(), concat('将学生姓名从【',OLD.stu_name,'】修改为【',NEW.stu_name,'】'));

10.6 触发器使用终结

优点

1
2
3
触发器是自动执行的,当对触发器相关的表执行响应的DML操作时立即执行;
触发器可以实现表中的数据的级联操作(关联操作),有利于保证数据的完整性;
触发器可以对DML操作的数据进行更为复杂的合法性校验(比如校验学员年龄)

缺点

1
2
3
使用触发器实现的业务逻辑如果出现问题将难以定位,后期维护困难;
大量使用触发器容易导致代码结构杂乱,增加了程序的复杂;
当触发器操作的数据量比较大时,执行效率会大大降低。

使用建议

1
2
在互联网项目中,应避免使用触发器;
对于并发量不大的项目可以选择使用存储过程,但是在互联网引用中不提倡使用存储过程(原因:存储过程时将实现业务的逻辑交给数据库处理,一则增减了数据库的负载,二则不利于数据库的迁移)

十一、视图

11.1 视图的概念

1
视图,就是由数据库中一张表或者多张表根据特定的条件查询出得数据构造成得虚拟表

11.2 视图的作用

  • 安全性:如果我们直接将数据表授权给用户操作,那么用户可以CRUD数据表中所有数据,加入我们想要对数据表中的部分数据进行保护,可以将公开的数据生成视图,授权用户访问视图;用户通过查询视图可以获取数据表中公开的数据,从而达到将数据表中的部分数据对用户隐藏。

  • 简单性:如果我们需要查询的数据来源于多张数据表,可以使用多表连接查询来实现;我们通过视图将这些连表查询的结果对用户开放,用户则可以直接通过查询视图获取多表数据,操作更便捷。

11.3 创建视图

1
2
3
create view 视图名称
AS
select_statement

11.4 查看视图

1
2
-- 查询视图结构
desc 视图名称;

11.5 修改视图

1
2
3
4
5
6
7
8
9
10
-- 方式1
create OR REPLACE view 视图名称
AS
select_statement


-- 方式2
alter view 视图名称
AS
select_statement

11.6 删除视图

1
drop view 视图名称;

11.7 视图数据的特性

1
视图是虚拟表,查询视图的数据是来源于数据表的。当对视图进行操作时,对原数据表中的数据是否由影响呢?

查询操作:如果在原表中添加了新的数据,而且这个数据满足创建视图时查询语句的条件,通过查询视图也可以查询出新增的数据;当删除原表中满足查询条件的数据时,也会从视图中删除。

新增数据:如果在视图中添加数据,数据会被添加到原数据表

删除数据:如果从视图删除数据,数据也将从原表中删除

修改操作:如果通过修改数据,则也将修改原数据表中的数据

视图的使用建议 : 对复杂查询简化操作,并且不会对数据进行修改的情况下可以使用视图。

十二、索引

1
数据库是用来存储数据,在互联网应用中数据库中存储的数据可能会很多(大数据),数据表中数据的查询速度会随着数据量的增长逐渐变慢 ,从而导致响应用户请求的速度变慢——用户体验差,我们如何提高数据库的查询效率呢?

12.1 索引介绍

1
2
3
索引,就是用来提高数据表中数据的查询效率的。
索引,就是将数据表中某一列/某几列的值取出来构造成便于查找的结构进行存储,生成数据表的目录
当我们进行数据查询的时候,则先在目录中进行查找得到对应的数据的地址,然后再到数据表中根据地址快速的获取数据记录,避免全表扫描。

image-20241013140940147

12.2 索引分类

  • 主键索引:在数据表的主键字段创建的索引,这个字段必须被primary key修饰,每张表只能有一个主键
  • 唯一索引:在数据表中的唯一列创建的索引(unique),此列的所有值只能出现一次,可以为NULL
  • 普通索引:在普通字段上创建的索引,没有唯一性的限制
  • 组合索引:两个及以上字段联合起来创建的索引

说明:

1
2
1. 在创建数据表时,将字段声明为主键(添加主键约束),会自动在主键字段创建主键索引;
2. 在创建数据表时,将字段声明为唯一键(添加唯一约束),会自动在唯一字段创建唯一索引;

12.3 创建索引

1
2
3
4
5
6
7
8
9
10
11
12
-- 创建唯一索引: 创建唯一索引的列的值不能重复
create unique index 索引名 on 表名(列名);

-- 创建普通索引: 不要求创建索引的列的值的唯一性
create index 索引名 on 表名(列名);

-- 创建组合索引
create index 索引名 on 表名(列名1,列名2...);


-- 全文索引(了解即可),MySQL 5.6 版本新增的索引,可以通过此索引进行全文检索操作,因为MySQL全文检索不支持中文,因此这个全文索引不被开发者关注,在应用开发中通常是通过搜索引擎(数据库中间件)实现全文检索
create fulltext index 索引名 on 表名(字段名);

12.4 查看索引

1
2
3
4
5
6
7
8
-- 查询索引
show keys from 表名;

-- 查询数据表的索引
show indexes from 表名;

-- 通过创建表的语句查看索引
show create table 表名;

12.5 删除索引

1
2
-- 删除索引: 索引是建立在表的字段上的,不同的表中可能会出现相同名称的索引,因此删除索引时需要指定表名
drop index 索引名 on 表名;

12.6 索引的使用

1
2
索引创建完成之后无需调用,当根据创建索引的列进行数据查询的时候,会自动使用索引;
组合索引需要根据创建索引的所有字段进行查询时触发(例如: tid=250000 and name='aaa')。

在命令行窗口中可以查看查询语句的查询规划:

1
explain select_statement

12.7 索引的使用总结

优点

  • 索引大大降低了数据库服务器在执行查询操作时扫描的数据量,提高查询效率
  • 索引可以避免服务器排序、将随机IO变成顺序IO

缺点

  • 索引是根据数据表列的创建的,当数据表中数据发生DML操作时,索引页需要更新;
  • 索引文件也会占用磁盘空间;

注意事项

  • 数据表中数据不多时,全表扫面可能更快,不要使用索引;
  • 数据量大但是DML操作很频繁时,不建议使用索引;
  • 不要在数据重复度高的列上创建索引(性别);
  • 创建索引之后,要注意查询SQL语句的编写,避免索引失效。