基础部分

1.1 MySQL简介

什么叫做数据库(DBS)、数据库管理系统(DBMS)、SQL

名称全称简称
数据库存储数据的仓库,对数据进行有组织的管理DataBase(DB)
数据库管理系统操纵和管理数据库的大型软件DataBase Management System (DBMS)
SQL操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准Structured Query Language (SQL)

安装MySQL,其实就是在自己的平台上安装 MySQL 的客户端和服务器,更确切的说,该数据库服务的本质是一个网络服务器进程(本质上是一个软件),所以它也应有自己对应的端口号和其所遵守的协议:

服务器,数据库,表关系

  • 首先我们需要连接 MySQL 服务器,然后在 MySQL 客户端中输入对数据进行操作的 SQL 语句,并交给服务器端完成。
  • 同时我们可以创建多个数据库,为了保存应用中实体的数据,一般会在数据库中创建多张表,以保存程序中实体的数据。

数据库服务连接!!

net start mysql80  # 启动服务
net stop mysql80   # 关闭服务

连接数据库

mysql [-h IP地址] [-P 3306] -u 用户名 -p

-h :Mysql服务所在的主机IP
-u :Mysql数据库用户名
-P :Mysql服务端口号,默认3306
-p :数据库用户对应的密码

查看设置数据库存储路径

# 查看数据库存储路径,进行更改或查看自己的数据库内容
show variables like '%datadir%'
select @@'datadir'

系统数据库介绍

数据库含义
mysql存储MySQL服务器正常运行所需要的各种信息 (时区、主从、用户、权限等)
information_schema提供了访问数据库元数据的各种表和视图,包含数据库、表、字段类型及访问权限等
performance_schema为MySQL服务器运行时状态提供了一个底层监控功能,主要用于收集数据库服务器性能参数
sys包含了一系列方便 DBA 和开发人员利用 performance_schema 性能数据库进行性能调优和诊断的视图

1.2 数据库查询语言

DDL(存储、维护数据库结构)、DML(维护数据)、DQL(查询数据)、DCL(数据控制,权限和事务,管理数据库及用户)

1.2.1 DDL

DDL【data definition language】: 数据定义语言,用来维护存储数据的结构 代表指令: create、drop、alter

数据库操作的语法

1、查询

/*  查询所有数据库  */
show databases;
/*  查询当前数据库  */
select database();

2、创建

/*  创建数据库  */
create database [if not exists] 数据库名 
[default charset 字符集] [collate 排序规则]

/*  创建表   */
create table 表名(字段1[field] 数据类型[type] comment "注释");  //注释可省略

3、删除

/*  删除指定数据库  */
drop database [if exists] 数据库名;

4、使用

use 数据库名;

5、查询当前数据库所有表

show tables;

6、查询表结构

desc 表名;

7、查询指定表的建表语句

show create table 表名;

8、修改表结构,更改字段

/*1、增加字段*/
alter table 表名 add 字段名 类型(长度) [comment 注释][约束];
/*2、修改数据类型*/
alter table 表名 modify 字段名 新数据类型(长度);
/*3、修改字段名和字段类型*/
alter table 表名 change 旧字段名 新字段名 类型(长度) [注释][约束];
/*4、删除字段*/
alter table 表名 drop 字段名;
/*5、修改表名*/
alter table 表名 rename to 新表名;

数据类型

/*     数字类型      */        
tinyint :字节
smallint : 短整型
mediumint :占用3字节
int / integer : 整型4字节
float : 单精度4字节
double : 双精度8字节
decimal(小数的,十进制数的) : 
/*    字符串类型   */
char : 1字节 (0-255)
varchar : 0-65535 ( 变长字符串)
# 日期时间类型字段
date 只包含年月日
datetime  包含年月日 时分秒 

运算符使用

in  and  or not  <>  like [%:匹配0或多个字符]  [between and]   
名字描述
<> , !=不相等运算符
<=>NULL安全等于运算符
is NULLNULL 值测试
between …… and ……值是否在范围之内
not between and值是否不在范围之内
in()区间取值

1.2.2 DML

DML【data manipulation language】 :数据操纵语言,用来维护数据本身 代表指令: insert、delete、update

update 表名 set [字段] where ...
delete from [表名] where 条件
insert into [表名](字段列表,可省略代表全字段插入记录) value(记录值)  # 插入单条记录
insert into [表名](字段列表,可省略代表全字段插入记录) values(记录值) # 插入多条记录

1.2.3 DQL

DQL【Data Query Language】:数据查询语言,用来查询数据库中表的记录

select distinct……[as 别名] from 表名 where …… group by 分组字段列表 having 分组后条件列表
order by 排序方式 (asc升序 desc 降序) limit 分页参数(起始索引,每页展示记录数)
-- 起始索引 = (页码 - 1)* 每页展示的记录数
# 去重查询
select distinct [字段列表] from 表名;

联合查询:将多个表的查询结果拼接在一起,形成一个新的查询结果集

  • 对于联合查询的多张表的列数必须保持一致,字段类型也需要保持一致。
  • union all 会将全部的数据直接合并在一起,union 会对合并之后的数据去重。
# union all 不会删除重复记录
select 字段列表 from 表1……
union [all] 
select 字段列表 from 表2……; 

分页查询

select 字段列表 from 表名 limit 起始索引,查询记录数;
从第一页开始查询,起始索引可以省略
# 查询前多少条记录
select 字段列表 from 表名 limit 要取的数量 offset 要跳过的数量

子查询

1、列子查询:查询结果返回一列或多列

常用操作符:in、not in、any(任意一个满足)、some(与any一致)、all(列表所有值都满足)

2、行子查询:查询返回结果是一行或者多行

3、标量子查询

# 查询薪资 和 领导id一致的员工信息
select * from emp where (salary,managerid) = (select salary,managerid from emp)

多表查询

表结构之间的关系:一对多(多对一) ,多对多, 一对一

是员工表emp所有的记录 (17) 与 部门表dept所有记录(6) 的所有组合情况,这种现象称之为笛卡尔积。

即所有的记录数为: $17 \times 6 = 102$

笛卡尔积: 笛卡尔乘积是指在数学中,两个集合 A 和 B 的所有组合情况

  • 一对一:一个表中字段作为另一个表中的外键,且这个字段为唯一(唯一:字段不重复)

  • 一对多:设置一个表中字段作为另一个表中的外键

  • 多对多:通过建立第三张表分别令第三张表字段作为其他表的外键,以此建立对两张表的约束关系

连接查询:内连接、外连接 以及 子查询

(1)内连接:相当于查询 A、B 交集部分数据

分为:隐式内连接、显式外连接

1、隐式内连接
 SELECT  字段列表   FROM   表1 , 表2 [别名]  WHERE   条件 ... ;
2、显示内连接
 SELECT  字段列表   FROM   表1  [ INNER ]  JOIN 表2  ON  连接条件 ... ;

(2)外连接:左连接、右连接、自连接

左:查询左表所有数据,以及两张表交集部分数据

右:查询右表所有数据,以及两张表交集部分数据

自连接:当前表与自身的连接查询,自连接必须使用表别名(可以是内连接查询,也可以是外连接查询。)

select 字段列表 from 表1 left [outer] join 表2 on 条件……; #左外连接
select 字段列表 from 表1 right [outer] join 表2 on 条件……; #右外连接

(3)子查询

概念:sql语句中嵌套CRUD语句,称为嵌套查询,又称子查询(子查询外的语句可以是增删改查任意一个).

  • 标量子查询:查询结果为单个值,常用操作符(<> = > < )

  • 列子查询:结果为一列(in、 not in、 any、 some、 all)

  • 行子查询:结果为一行(= 、<> 、in 、not in)

  • 表子查询:结果为多行多列

分类:where之后出现的子查询、from之后出现、select之后出现

#1、where 之后出现的子查询
select name, age, job from emp where salary > (select avg(salary) from emp);

#2、from 之后出现的子查询
-- 查询入职日期在 2006/1/1 之后的员工信息,及其部门信息
select e.*,d.*
from (select * from emp where entrydate > '2006/1/1') e left join
    dept d on e.dept_id = d.id;
    
#3、select 之后出现的子查询 
select (select name from emp where salary > 10000) from emp……;

1.2.4 DCL

DCL【data control language】:数据控制语言,主要负责权限管理和事务,管理数据库用户、控制数据库的访 问权限。 代表指令: grant、revoke、commit

/*  创建用户 */
主机名默认是localhost
create User '用户名'@'主机名' identified by '密码';
任意主机可以访问该用户
create user '用户名'@'%' ……
修改用户密码
alter user '用户名' @'主机名' identified with '新密码';
删除用户: drop user '用户名' @'主机名';

权限控制

1、授予权限
grant 权限列表  on 数据库名.表名 to '用户名' @'主机名';
2、查询权限 show grants for '用户名' @'主机名';
3、撤销权限 remove 权限列表 on 数据库名.表名 to '用户名' @'主机名';
/*  授权时,可用*代替数据库名和表名  */

1.3 MySQL常用函数

MySQL中的函数主要分为以下四类: 字符串函数、数值函数、日期函数、流程函数,另有聚合函数,窗口函数

/*字符串函数*/
拼接 concat(s1,s2……);
左填充 lpad(str,n,pad); 用pad对str的左边进行填充,达到n个字符串长度
去掉字符串头部和尾部空格 trim(str);
substring(str,start,len);  返回字符串从start开始的len个字符长度
substring_index(要截取的字段名, '分割符' , pos截取位置) # pos:1左侧开头开始,-1右侧开始
/*数值函数 */
ceil(x) 向上取整
floor(x) 向下取整
round(值,保留小数位数);   
/*日期函数*/
day(date);  获取日期
month(date) : 获取月份 year(date):年份
curdate() : 返回当前日期
now()    : 返回当前日期和时间
#向日期添加指定的时间间隔
date_add(date,interval expr type) date:日期表达式,expr:时间间隔 ,type:时、秒、分、日、月……

datediff(date1,date2); 返回d1和d2之间的日期差
/*流程函数*/
if(value,t,f); 正确返回t,错误返回f
ifnull(v1,v2); 不为空返回v1,否则v2
case 字段 when 过滤 then 否则是... 

流程控制函数

# if(value,t,f) : 如果value为true 返回t,否则返回f
# ifnull(value1,value2) : 如果value 不为空返回value1,否则value2 【类似三目运算符】
# case when then语句
-- (1)
case 
	when age is null then '其他'   # 每一次选择when后无 ,
	when age < 20 then '...'
	when ……
end as age_cnt [将上述语句 判断完记录的值新建字段存入]

-- (2)
case 字段
	when result1 then ''
	when result2 then ''
	else ''
end as '' 别名字段

聚合函数

之前所使用的查询大多数都是横向查询,它根据条件一行一行的进行判断,而使用聚合函数进行查询是 将一列数据作为一个整体,进行纵向计算,常搭配as对查询出的数据进行重命名,null值不被聚合函数所计算

常见聚合函数:count、max、min、avg、sum……

where不能对聚合函数进行判断(因为聚合函数所统计出的是一个数据,进行判断没有意义),而having是分组之后的结果,是多组数据,可以再次进行过滤筛选,比如说分组过后仍然存在多组数据,再进行条件过滤进一步在分组的条件上,筛选出符合条件的数据.

select [count(字段名) | max() | avg() | sum()……] from 表名

窗口函数

也被称为分析函数,处理相对复杂的报表统计分析场景

什么叫做窗口?可以理解为记录集合,窗口函数也就是在满足某种条件的记录集合上执行的特殊函数。

对于每条数据都要在此窗口内执行函数,窗口的大小都是固定的,这种属于静态窗口; 而不同的记录对应不同的窗口,这种叫做 滑动窗口

# 基本语法
函数名([expr]) over子句

其中,over是关键字,指定函数执行的窗口范围,包含三个分析子句,分组partition by、排序oerder by、窗口row,若over()

括号中什么也不写,则窗口包含满足where条件的所有行,窗口函数基于所有行计算

函数名([expr]) over(partition by <要分的组> order by <要排序的列> rows betewwn <数据范围>)
# 对于数据取值的解释,演示
rows between 2 proceding and current row  # 代表取当前行和前2行
rows between unbounded prodeding and current row   # 取当前行和之前所有行 unbounded:无限的
rows between n proceding and m follwing     # 取前n行,下m行,共m+n行

-- 若 缺少窗口子句,默认 rows between unbounded prodeding and current row
-- 若少 order by 和 窗口子句,默认 rows between unbounded proceding and unbounded follwing

窗口函数一般可以分为两种:专有窗口函数、聚合类窗口函数

-- 1、专有窗口函数
rank() 、dense_rank() 、row_number()、ntile(n)
-- row_number(),生成行的编号
-- rank() ,      生成排名,根据生成的编号,比如前3条数据排名一致,则为1,第4条排名则为4(跳跃排序)
-- dense_rank(), 生成排名,根据实际的排名1,2,3……(连续排序)
-- ntile(n)  , n为切分的片数,即将所有记录平均分成n片,适用于 将各个阶段的记录编号,或者取前xxx的数据
-- 2、聚合类窗口函数
sum() 、 count() avg() ……

普通场景下,聚合函数和group by 一起使用,在窗口环境下,聚合函数也可以使用,这时被称为 聚合类窗口函数

和 聚合类窗口函数 不同的是,窗口函数每条记录都会执行,即执行过后记录数和执行前一样

1.4 字段约束

非空约束:值不能为null

唯一约束:数据唯一,不能重复

主键约束:一行数据的唯一标识,要求非空且唯一

默认约束:保存数据时,如果未指定字段值,采取默认值

检查约束:保证字段满足某个条件

外键约束:用来让两个数据表建立连接,保证数据的一致性和完整性

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

/* 约束的类型 */
1、 not null # 非空约束
2、 unique   # 唯一约束
3、 primary key #主键约束
4、 check     # 检查约束
5、 foreign key # 外键约束
/*外键约束 -- 操作  */
# 1、已建表,添加外键约束
alter table 表名 add constraint(约束,限制) 外键名称 foreign key references(关联,提及) 主表字段名
# 2、创建表时:
create table 表名{
	字段 数据类型,
	……
	[constraint] 外键名称 foreign key 外键字段名 references 主表字段
};

删除/更新行为:在添加外键之后,删除父表数据时产生的约束行为

1、no action   /*当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,
                     如果有则不允许删除/更新。 (与 RESTRICT 一致) 默认行为  */
                     
2、restrict	  /*  当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则不
                 允许删除/更新。 (与 NO ACTION 一致) 默认行为 */
                 
3、cascade   /*当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有,则
						也删除/更新外键在子表中的记录。*/   
4、set null      /* 当在父表中删除对应记录时,首先检查该记录是否有对应外键,如果有则设置子表
				中该外键值为null(这就要求该外键允许取null)。 */
5、set default     /* 父表有变更时,子表将外键列设置成一个默认的值 (Innodb不支持) */

1.5 事务操作

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

事务操作过程:

1、开启事务

2、如果执行过程中,代码抛出异常,则回滚事务

3、提交事务

MySQL的事务默认自动提交,执行一条DML操纵(增删改)语句,立即隐式的提交事务

-- 事务操作方式
1、是否开启事务操作
2、更改事务提交方式 
#事务操作
-- 1、查看/设置事务提交方式
select @@autocommit;
set @@autocommit=0;   设置为手动提交
-- 2、开启事务
start transaction(begin);
begin ;     # 另一种简便方式
-- 3、提交事务
commit;
-- 4、回滚事务
rollback;

事务四大特性(ACID):

1、原子性:事务不可分割,全成功或全失败(A)

2、一致性:事务完成,数据必须保持一致状态(C)

3、隔离性:数据库系统提供的隔离机制,保证事务在不受外部并发操作的环境运行(I)

4、持久性:事务提交或回滚,对数据改变是永久的(D)

并发事务问题

1、脏读:一个事务读取到另外一个事务还没有提交的数据

2、不可重复读:一个事务先后读取同一条记录,但两次读取的数据不一致

3、幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现“幻影”

-- A事务还没完全执行完成,未提交,B读取到了A还没有提交的数据(脏读)
-- A事务正在执行select语句,B事务同时更新数据,并进行提交,然后就发生A前后读取不一致
-- A事务先进行查询id为1的语句,未查找到,B事务同时进行插入id为1的数据,然后A事务再次进行插入id=1数据,插入失败,再次进行查询id=1数据仍无法找到
image-20260107202410186 image-20260107202457134 image-20260107202715737

事务隔离级别

-- (RU) Read uncommitted 读未提交 脏读、不可重复读、幻读d都会出现
-- (RC) Read committed   不可重复读、幻读会出现
-- (RR) Repeatable Read(Mysql默认隔离级别) 幻读会出现
-- (S)  Serializable     都不会出现

1、查看事务隔离级别
select @@transaction_isolation;  查看@@(系统变量信息)
2、设置事务隔离级别
set [session(会话级别,仅在当前窗口有效)/ global] transaction isolation level
[read ……]
# 事务隔离级别越高,数据越安全,但是性能越低。
image-20260107202950124

用户权限管理

用户管理

MySQL 是多用户的数据库系统,按权限分为两种:root,超级管理员,和由root用户创建的普通用户

# 创建用户
create user username identified by 'password';
# 查看用户
select user ,host from mysql.user;
image-20260130173639322

权限管理

新用户创建完后无法登录,需要分配权限

grant  权限 on 数据库.表 to 用户名@主机名 identified by 'password'

登录主机:

字段含义
%匹配所有主机
localhostlocalhost 不会被解析成 IP 地址,直接通过 UNIXsocket 连接
127.0.0.1会通过 TCP/ IP 协议连接,并且只能在本机访问
::1兼容 ipv6 版本的,表示同 ipv4 的 127.0.0.1

权限列表:

权限作用范围作用
all [ privileges ]服务器所有权限
select表、列选择行
insert表、列插入行
update表、列更新行
delete表删除行
create数据库、表、索引创建
drop数据库、表、视图删除
shutdown服务器关闭服务
process服务器查看进程信息
reload服务器允许使用 flush 语句
file服务器文件操作
grant option数据库、表、存储过程授权
references数据库、表外键约束的父表
………………
# 示例:为 u_sxt 用户分配只能查询 tlias库中的emp表,并且只能在本机登录的权限,密码为:yyy
grant select on tlias.emp to u_sxt@'localhost' identified by 'yyy';

刷新权限

每当权限调整后,需要刷新权限,保证新权限的正常运行

flush privileges;

删除用户

drop user username@localhost;

存储引擎

2.1 MySQL体系结构

image-20250809235954691

1、连接层:是一些客户端和链接服务,主要完成类似链接处理、授权认证、及相关安全方案校验用户名。服务器也会为安全接入的每个用户验证它具有的操作权限

2、服务层:大多数的数据库服务都在这一层完成

3、引擎层:存储引擎控制数据的存储方式,数据库服务器通过API 与其进行交互,不同存储引擎具有不同的功能,(索引在该层实现)

4、存储层:主要将数据存储在文件系统之上,并完成与存储引擎的交互

2.2 存储引擎

存储引擎是存储数据、建立索引、更新/查询数据等技术的实现方式

存储引擎是基于表的,不是基于库的,所以存储引擎也可被称为表类型

# 查询建表语句
show create table dept;
# 建表时指定存储引擎
engine = InnoDB;

若未指定查询引擎,自动默认InnoDB引擎,如下图

image-20250810001017244

(1)InnoDB 存储引擎

介绍:是一种兼顾高可靠性和高性能的通用存储引擎,是MySQL默认的存储引擎

  • DML操作遵循ACID模型(事务四特性),支持事务

  • 行级锁,提高并发访问性能

  • 支持外键约束,保证数据完整性,正确性

文件.idb :innodb引擎的每张表都会对应一个表空间文件,存储该表的表结构(sdi)、数据、索引

参数:innodb_file_per_table 决定多张表是否对应一个表空间文件,或者一张表对应一个

# 通过sql查询变量,看是否一张表对应一个表空间文件
show variables like 'innodb_file_per_table';
image-20250810002340641

在MySQL文件夹下查看当前数据库中表的ibd文件,一个ibd文件对应一张表,不仅存放表结构、数据,还会存放该表对应的 索引信息,可以通过以下命令查看该表的信息

#由于ibd文件是二进制存储,无法直接查看内容,利用指令查看
ibd2sdi 文件名
image-20260108103930776

2.2.1 存储引擎的逻辑结构

image-20250810003302780

一行中存储的信息:trx_id(最后一次事务操作),roll_pointer(指针),以及字段

磁盘中分为:表空间、段、区、页、行(大小依次递减)

区是空间表结构的单元结构,固定大小为1M(包含64页) InnoDB存储引擎页大小为 16KB, 即一个区中一共有64个连续的页

页 : 页是组成区的最小单元,页也是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB(16384 B)。为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区

行 : InnoDB 存储引擎是面向行的,也就是说数据是按行进行存放的,在每一行中除了定义表时 所指定的字段以外,还包含两个隐藏字段(后面会详细介绍)

(2)Memory

Memory引擎的表数据时存储在内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为 临时表或缓存使用。

  • 内存开放
  • hash索引(默认)

(3)MyISAM :MySQL早期的默认存储引擎。

  • 不支持事务,不支持外键
  • 支持表锁,不支持行锁
  • 访问速度快

2.2.2 存储引擎的区别

特点InnoDBMyISAMMemory
存储限制64TB有有
事务安全支持--
锁机制行锁表锁表锁
B + Tree索引支持支持支持
Hash 索引--支持
全文索引支持(5.6版本后)支持-
空间使用高低N/A
内存使用高低中等
批量插入速度低高高
支持外键支持--

2.2.3 存储引擎的选择

  • InnoDB:对事务的完整性有较高要求,在并发条件下要求数据一致性,数据需要进行修改操作
  • MyISAM:以读、插入操作为主,只有少量的更新删除操作,且对并且对事务的完 整性、并发性要求不是很高
  • Memory::将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是 对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性。

索引

3.1 索引概述

索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足 特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构 上实现高级查找算法,这种数据结构就是索引。

无索引情况:在无索引情况下,就需要从第一行开始扫描,一直扫描到最后一行,我们称之为全表扫描,性能很低。 有索引情况:如果采用树状结构进行构建索引,那么查找数据的时间复杂度会提升为 $log(n)$ 级别,大大提升了数据的查找效率,如下图

image-20260108201402260

3.2 索引的结构

MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的索引结构(数据结构),主要包含以下几种:

3.2.1 索引的分类:

  • 主键索引:针对于表中主键创建的索引,默认自动创建, 只能有一个(Primary)
  • 唯一索引:避免同一个表中某数据列中的值重复,可以有多个(Unique)
  • 常规索引:快速定位特定数据,可以有多个
  • 全文索引:全文索引查找的是文本中的关键词,而不是比较索引中的值,可以有多个(Fulltext)

3.2.2 聚集索引&二级索引

分类含义特点
聚集索引(Clustered Index)每个结点下挂的是主键的值
将索引与数据存储到一起,索引的叶子节点保存了行数据,即该主键值对应的记录,且叶子节点有序
必须有,而且只有一个
(主键的索引)
二级索引(Secondary Index)将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键可以存在多个

聚集索引选取原则:

  • 如果存在主键,主键索引就是聚集索引。
  • 如果不存在主键,将使用第一个唯一UNIQUE索引作为聚集索引。
  • 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。
image-20260109142501642

回表查询:先到二级索引中查找数据,找到主键值,然后再到聚集索引中根据主键值,获取 数据的方式,就称之为回表查询。

3.2.3 索引语法

# 1、创建索引
create [unique | primary] index [index_name] on [表名]
# 2、查看索引
show index from [表名]
# 3、删除索引
drop index [index_name] on [表名]
# eg 为user表创建关于 email、age的联合索引
create index in_union on user(email,age)

3.2.4 SQL 性能分析

1、sql执行频率

# 查看当前数据库执行 增删改 的执行次数
--  session 查看当前会话    global   查询全局会话
show [session | global] status

show global status like 'Com%';

通过该命令查询,可以查看当前数据库是以查询为主或以修改为主,从而为数据库优化提供参考依据

如果是以增删改为主,我们可以考虑不对其进行索引的优化。 如果是以 查询为主,那么就要考虑对数据库的索引进行优化了。

2、慢查询日志

该日志记录了所有执行时间超过指定参数(long_query_time单位 s,默认为10秒)的所有SQL语句日志,MySQL默认没有开启,需要写配置文件/etc/my.cnf

慢查询日志在 Linux 下存放位置 /var/lib/mysql/localhost-slow.log

若在查询之后,针对查询耗时长的SQL语句来进行优化

3、查看耗时情况

# profiles 能在SQL优化时帮我们了解 时间在哪里耗时大,have_profiles参数,查看当前MySQL是否支持
# 具体语法
# 1、查看当前数据库是否支持profiles
select @@have_profiling
# 2、默认情况profiling是关闭的(数值为0),需要开启,可以指定会话或者全局级别
set [session | global] profiling = 1
image-20260117164453890
# 3、进行一系列查询,然后查看耗时
select * from tb_user;
select count(*) from tb_user;
show profiles;
show profile for query [id];  # 查看指定id的SQL语句耗时
show profile cpu for query [id]  # 查看指定id的SQL 语句 cpu使用情况

4、explain 执行计划

可以查看表是否使用了索引,表的连接情况,连接顺序等

# 在select 语句之前加上关键字explain / desc
explain select 字段列表 from 表名 where 条件;

下面为explain查询出的各个字段值及其含义:

字段含义
idselect 查询的序列号,表示查询中执行 select 子句或者是操作表的顺序
id相同,执行顺序从上到下;id不同,值越大,越先执行
select_type表示 SELECT 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接 或者子查询)、PRIMARY(主查询,即外层的查询)、 UNION(UNION 中的第二个或者后面的查询语句)、 SUBQUERY(SELECT/WHERE之后包含了子查询)等
type表示连接类型,性能由好到差的连接类型为 NULL、system、const、 eq_ref、ref、range、 index、all 。
NULL一般为不访问任何表,system 访问系统表,const 根据唯一索引或主键访问的表,ref 使用非唯一索引访问
possible_key显示可能应用在这张表上的索引,一个或多个
key实际使用的索引,如果为NULL,则没有使用索引
key_len表示索引中使用的字节数, 该值为索引字段最大可能长度,并非实际使用长 度,在不损失精确性的前提下, 长度越短越好
rowsMySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值, 可能并不总是准确的
filtered表示返回结果的行数占需读取行数的百分比, filtered 的值越大越好

注:查询时 type字段尽量不要出现all,此为无索引情况下进行全表扫描,性能低

3.3 索引的使用

3.3.1 验证索引的效率

1、索引对于查询效率的提升

(1)准备好大规模的测试数据

mysql在加载大规模数据时会非常耗费CPU及内存 ,已经拆分为5个部分 ,每一个部分为200w数据 ,load 5次即可

使用load data infile语句(SQL优化中insert优化):该语句允许您从本地文件或远程服务器上的文件中将数据加载到MySQL表中。这比使用INSERT语句逐行插入数据更快。示例:

load data load infile [本地路径]
into table [表名]
fields terminated by ','
lines terminated by '\n';
# terminated:结束  filed:字段

(2)在400w 测试数据下

# 根据id(主键)字段进行查询
select * from tb_sku where id = 1;  # 测试耗时 11ms
# 根据 无索引字段 sn 进行查询
select * from tb_sku where name = 'xxx';  # 测试耗时3s 669ms
# 为 sn 字段添加索引,再次查询查看耗时
create index idx_sku_sn on tb_sku(sn);
select * from tb_sku where name = 'xxx';   # 耗时如下图 5ms

image-20260117224915901

image-20260117225008865

3.3.2 索引的使用

以下是几种导致索引可能失效的情况

1、最左前缀法制:如果索引了多列(联合索引),查询从索引最左列开始,并且不跳过索引中的列。若跳过某一列,则索引将部分失效(后面字段的索引失效),这是由于 B+Tree 的特性,部分有序

# 假设现在存在一个联合索引 idx_pro_age_stu 
select * from tb_sku where profession = 'xxx' and age = 18 and stus = 'x';  # 使用了索引
select * from tb_sku where age = 18;   # 联合索引失效,因为跳过了最左侧的profession字段
select * from tb_sku where profession = 'xxx' and stus = 'x'  # 仅stus索引部分失效,因为跳过了age字段

2、范围查询:联合索引中,若出现范围查询(<,>),范围查询右侧的列索引失效

select * from tb_sku where profession = 'xxx' and age > 30 and stus = 'x';  # 索引stus失效
# 使用 >= 、<= 、 between 可以避免索引失效
select * from tb_sku where profession = 'xxx' and age >= 30 and stus = 'x';

3、索引列运算

尽量不要在索引列上进行运算,否则索引会失效,引起全表扫描

可能写SQL时会写错类型,如字符串未加引号,导致了隐式类型转换索引失效

# 1、使用函数进行计算导致索引失效
select * from tb_sku where substring(sn,10,2) = '15';
# 2、写错类型导致索引失效
select * from tb_sku where sn = xxx;    # 未加引号索引失效

image-20260117234850659

4、模糊查询 和 or 连接条件

# 1、使用模糊查询
-- 模糊尾部索引不失效,大数据量时避免头部模糊
explain select * from tb_sku where sn like '%11';    # 模糊查询首部模糊会索引失效
select * from tb_sku where sn like '110%';           # 索引不失效,type = range
# 2、or 连接条件
-- or 前后必须都存在索引,否则都不会使用索引
select * from tb_sku where id = 10 or num = 300;

5、数据分布影响

如果MySQL评估使用索引比全表扫描更慢,则不使用索引(大概率在小数据量时出现)

3.3.3 SQL提示

通常数据库会根据查询语句自动判断使用哪个索引,但有些时候使用特定索引会使查询效率更高,因此需要SQL提示

SQL提示是优化数据库的重要手段,就是在SQL语句中加入一些人为提示来达到优化数据库的目的

-- use index  使用索引
explain select * from tb_user use index(idx_user_pro) where profession='xxx';
-- ignore index 忽略索引
explain select * from tb_user ignore index(idx_user_pro) where profession='xxx';
-- force index 必须使用的索引
explain select * from tb_user force index(idx_user_pro) where profession='xxx';

3.3.4 覆盖索引

覆盖索引不是一个特殊的索引类型,而是在一个SQL查询语句中,查询的字段是否需要进行回表查询,简单来说就是能否通过一次的索引扫描查询出所需要的数据。因此在写SQL查询语句中,尽量避免使用select *

下面是一组例子,帮助更好的理解什么是覆盖索引

image-20260118203132179

在上图中,第三条SQL查询,因为从二级索引 / 辅助索引 中未能查找到所有需要的列,因此进行了回表查询,效率降低

下面存在一个实际构建索引的问题:

一张表, 有四个字段(id, username, password, status), 由于数据量大, 需要对 以下SQL语句进行优化该如何进行才是最优方案: select id,username,password from tb_user where username =  'itcast';

因为考虑到覆盖索引的问题,需要尽可能避免回表查询,再根据二级索引叶子结点挂载主键值这一特点,所以需要建立一个联合索引,username,password这样查询时只会查询一次二级索引

3.3.5 前缀索引

当查询的字段为varchar、test等时,建立的索引是很长的字符串,会让索引变得很大,空间存储压力很大,浪费磁盘IO,影响查询效率。此时可以将字符串的一部分前缀,建立索引。

# 前缀索引的语法
create index idx_name on 表名(字段名(n))   -- n为截取该字段的前缀长度

前缀长度如何取定?

可以根据索引字段的选择性决定,索引的选择性越高,查询效率越高

索引字段选择性max = 1,性能是最好的

# 选择性:该字段不重复的值占所有记录的比值
count(distinct 字段名) / count(*)      -- 完整字段选择性为1
count(distinct substring(字段,开始,结束)) / count(*)   -- 截取前缀,计算选择性值

通常时候,不一定选取 选择性取最高的前缀,可能会平衡索引的长度和选择性高低

比如在一张数据表中,user(id,name,email) 现在需要对 email 字段建立索引

现在前缀为 10 的索引,选择性为1,前缀为 5 的索引,选择性为 0.95,那平衡索引长度和选择性会将索引建立为前缀5位

3.3.6 索引设计原则

  • 针对数据量大,查询比较频繁的表建立索引(数据量 100w+)
  • 针对常见查询条件,排序,分组字段建立索引where、order by、group by
  • 尽量选择区分度高的列作为索引,尽量建立唯一索引
  • 字符串类型字段,长度较长,建立前缀索引(目前所用 倒排索引)
  • 尽量使用联合索引,减少单列索引,避免回表查询
  • 控制索引数量,维护索引结构需要代价,可能影响增删改效率
  • 若索引列不能存储 NULL 值,使用 NOT NULL 约束该字段

SQL优化

4.1 insert 优化

# 插入少量数据
insert into [表名] values(),()……
# 手动控制事务提交
start transaction;
insert into ……
commit;
# 主键顺序插入
乱序: 8 1 9 12 77……
顺序: 1 2 3 4  5……
# 插入大量数据 100w+
-- 1、连接服务端时
mysql --local-infile -uroot -p 
-- 2、设置全局参数开关
set global local_infile=1;
-- 3、加载大量数据脚本文件
load data load infile 'xxx'
into table xxx
fields terminated by 'x'
lines terminated by 'x';

4.2 主键优化

首先介绍 数据插入数据库表结构的流程

(1)页分裂

页可以为空,可以填充 一半,可以填充 100%,每个页包含 2~N 行数据,根据主键排列

如果只有一行数据,相当于一个链表,不允许这样存储。一行数据若过大(超过某个阈值)则会行溢出

主键顺序插入:首先申请页,将主键顺序插入,写满第一页后,继续写第二个页,页之间使用双向指针连接

image-20260118232720435

主键乱序插入:申请页,因为需要插入的50应该写入47之后,但第一页写不下,将第一页的一半往后分裂,写入第三页中,50接在第三页的47之后写入,再重新对连接页指针排序

image-20260119211658010 image-20260119211932154 image-20260119211955910

(2)页合并

当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并且它的空间 变得允许被其他记录声明使用。

当页中删除的记录达到 MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前 或 后)看看是否可以将两个页合并以优化空间使用。

image-20260119212327319

主键设计原则:

  • 尽量降低主键长度
  • 尽量选择顺序插入,选择auto_increment自增主键
  • 尽量避免使用 UUID做主键,如身份证号
  • 业务操作,避免对主键的修改

4.3 order by优化

Using filesort : 通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。

Using index : 通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要 额外排序,操作效率高。

对于以上的两种排序方式,Using index的性能高,而Using filesort的性能低,我们在优化排序 操作时,尽量要优化为 Using index。

针对多字段排序,order by 采用联合索引需要根据顺序来判断索引是否失效,排序规则使用哪种方式

create index idx_user_age_phone on tb_user(age,phone);
explain select id,age,phone from tb_user order by age,phone;   -- 索引生效,且排序方式 using index 
explain select id,age,phone from tb_user order by phone,age;  -- 索引失效,全表扫描
# Backward index scan,这个代表反向扫描索
explain select id,age,phone from tb_user order by age,phone desc;  

注:若不可避免出现filesort,大数据量排序,可以适当增大排序缓冲区大小 sort_buffer_size(默认256k)。

4.4 group by 优化

索引对于分组操作的影响

# 在没有索引情况下执行,出现 using temporary 使用了临时表
explain select profession,count(*) from tb_user group by profession;

# 创建联合索引之后,using index
create index isx_user_profession_age on tb_user(profession,age);
explain select profession,count(*) from tb_user group by age;   -- 仍使用临时表,因为不满足最左前缀法则

如果仅仅根据age分组,就会出现 Using temporary ;而如果是 根据 profession,age两个字段同时分组,则不会出现 Using temporary。原因是因为对于分组操作, 在联合索引中,也是符合最左前缀法则的。

4.5 limit 优化

使用场景:数据量较大时,limit 分页起始索引越高,查询越慢,下图是不同索引下的查询时间

image-20260120020932351

优化思路:创建 覆盖索引 + 子查询 形式优化

# 举个例子,现在需要查询第 200w 索引开始的10条数据,怎样优化

-- 子查询
select id from tb_sku order by id limit(2000000,10);  -- 查询到的所需临时表
-- 完整查询(使用了自连接查询,隐式)
select s.* from tb_sku s, ( select id from tb_user order by id limit 2000000,10 ) a 
where s.id = a.id;   

select * from tb_sku where id > 3500000 limit 1000;  # 查询速度极快QAQ

4.6 count 优化

在数据量极大时,在 InnoDB 存储引擎中,是很费时的。

  • MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个 数,效率很高; 但是如果是带条件的count,MyISAM也慢。
  • InnoDB 引擎就麻烦了,它执行 count(*) 的时候,需要把数据一行一行地从引擎里面读出 来,然后累积计数。

count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加,最后返回累计值。

用法::count(*)、count(主键)、count(字段)、count(数字)

count用法含义
主键InnoDB 引擎会遍历整张表,把每一行的 主键id 值都取出来,返回给服务层。 服务层拿到主键后,直接按行进行累加
字段没有not null 约束 : InnoDB 引擎会遍历整张表把每一行的字段值都取出 来,返回给服务层,服务层判断是否为null,不为null,计数累加。
有not null 约束:InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加。
数字InnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数字“1” 进去,直接按行进行累加。
*InnoDB引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接 按行进行累加。

效率上 count(字段) < count(主键) < count(1) ≈ count(*) , 尽量使用count( *)

4.7 update 优化

update course set name = 'xxx' where id = 1

执行 update 语句更新记录时,会锁定 id = 1这一行的数据,然后事务提交之后,行锁释放。

但是当更新一条记录,若根据无索引字段的条件来更新时,会触发表级锁,导致性能效率降低

-- 当前的 name 字段未建立索引
update course set name = 'xxx' where name = 'xxx'  

注:InnoDB的行锁是针对索引加的锁,不是针对记录加的锁 ,并且该索引不能失效,否则会从行锁 升级为表锁 。

存储对象

包含:视图、存储过程、存储函数、触发器

5.1 视图

视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视 图的查询中使用的表,并且是在使用视图时动态生成的。

简单来说,视图只保存了查询的SQL逻辑,不保存查询结果。所以我们在创建视图的时候,主要的工作 就落在创建这条SQL查询语句上。

(1)创建视图

create [or replace] view 视图名称[列名列表] as [select 语句]   with [ cascaded  |  local ]  check  option ]
-- or replace : 替换视图,可省略
-- with cascaded check option : 检查选项,用于通过视图往表中插入修改数据是否满足视图查找条件

(2)查询视图

# 1、查看创建视图语句
show create view 视图名称;
# 2、查看视图数据:可以当作查看表数据
select * from 视图名称 ……;

(3)修改视图

# 两种修改方式
create or replace  view 视图名称[列名列表] as select语句 with [ cascaded  |  local ]  check  option ]
alter view 视图名称[列名列表] as select语句 with [ cascaded  |  local ]  check  option ]

(4)删除

drop view[if exists] 视图名称

视图检查选项

举个例子,在定义视图时,若指定了条件在插入、修改、删除数据时,是否可以做到必须满足 条件才能操作,否则不能够操作呢?

简单说就是比如插入数据时,若不满足创建视图的 select 语句条件,是否可以成功插入数据到表中,这就是检查选项的作用了

当使用with check option子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如 插入,更新,删除,使其符合视图的定义。

MySQL允许基于另一个视图创建视图,它还会检查依赖视 图中的规则以保持一致性。

为了确定检查的范围,mysql提供了两个选项: cascaded和 local ,默认值为 cascaded 。

1 )cascaded:级联,在计算机中指多个对象之间的映射关系,建立数据之间的级联关系提高管理效率

视图 v3 依赖于 v2 视图,v2视图依赖于 v1 视图,由此在创建v3视图时,因为本身没有指定检查选项,只会检查它所依赖的视图即 v1,v2

而创建 v2 时,它本身指定了检查选项,所以本身的条件也会被检查

image-20260120184642677

2)local本地

比如,v2 视图是基于 v1视图的,如果在 v2 视图创建的时候指定了检查选项为 local ,但是v1视图创 建时未指定检查选项。 则在执行检查时,知会检查v2,不会检查v2的关联视图v1,简单说就是 local 选项只会关注本身的条件及其来源视图的条件检查。

image-20260120191017671

视图的更新

视图中的行与基础表中的行 存在 一对一 关系,如果视图包含以下任何一 项,则该视图不可更新:

聚合函数 或 窗口函数(sum、count 等)、group by 、having、union 或 union all

视图的作用

  • 简单:视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视 图,从而使得用户不必为以后的操作每次指定全部的条件。

  • 安全:数据库可以授权,但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见 到的数据

  • 数据独立:视图可帮助用户屏蔽真实表结构变化带来的影响。

比如现在有 学生 和 课程 以及 他们的中间表,现在需要拿到 其中学生的(姓名,学号,所选课程)封装成一个视图,【在后续使用时可以隔离隐私信息,而且起到临时表的作用,简化操作】

# 创建视图使用的 SQL 语句,注意在出现两张表中字段名一致时,需要起别名
select s.name student_name,s.no,c.name course_name from tb_student s,tb_course c,tb_student_course sc
where s.id = sc.student_id and  sc.course_id = c.id;
# 创建视图
create view tb_stu_course_v [上述sql查询语句];
# 查看视图
select * from tb_stu_course_v;

5.2 存储过程

介绍:存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程可以简化应用开发 人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。 存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用。类似于函数和方法。

存储过程的特点:封装 复用、可以接受参数,且可以返回数据、减少网络交互提升效率

5.2.1 基本语法

(1)存储过程的创建

create procedure 名称([参数列表])
begin
	-- SQL 语句
end;

(2)调用

call 存储过程名称([参数]);

(3)查看

select * from information_schema.routines where routine_schema = 'xxx' -- 查询指定的存储过程信息
show create procedure 存储过程名称;    -- 查询某存储过程定义

(4)删除

drop procedure [if exists] 存储过程名称;

注:命令行中执行创建存储过程SQL,需要指定delimiter结束符,如delimiter ;是代表语句以分号为结束标志

5.2.2 变量

MySQL中变量的三种类型:系统变量、用户定义变量、局部变量

(1)系统变量:属于服务器层面,非用户定义的,分为全局和会话global、session

mysql 服务启动后,默认会话变量,若想全局参数不失效,编辑配置文件./my.conf

# 1、查看系统变量
show [session | global] variables [like ‘'xxx'];  -- 可以通过模糊查询
select @@[session | global] 系统变量名;
# 2、设置
set [session | global] 系统变量名 = 值;  -- 也可以使用@@方式

(2)用户定义变量:用户变量不用提前声明,在用的时候直接用 @变量名 使用就可以。其作用域为当前连接。

# 1、赋值(可以使用 = 或者 :=)
set @var_name = 'xxx' , @var_name1 = xxx ……
# 2、使用
select @var_name;

(3)局部变量: 是根据需要定义的在局部生效的变量,访问之前,需要 declare声明。可用作存储过程内的 局部变量和输入参数,局部变量的范围是在其内声明的begin ... end块。

# 声明,变量类型即为数据库字段的类型,default为初始化值
declare 变量名 变量类型 [default ...];  
# 赋值
select 字段名 into 变量名 from 表名;
# 示例:
create procedure p1()
begin
	declare stu_count int default 0;         
	select count(*) into stu_count from tb_stu;      
	select stu_count;
end;
call p1();

5.2.3 if 流程控制

-- elseif 、 else 均为可选
if 条件1 then
	...
elseif 条件2 then
	...
else
	...
end if;

5.2.4 参数

参数分为以下三种:in、out、inout

类型含义备注
in该类参数作为输入,也就是需要调用时传入值默认
out该类参数作为输出,也就是该参数可以作为返回值
inout既可以作为输入参数,也可以作为输出参数
# 语法
create procedure 存储过程名称([in/out/inout 参数名 参数类型],[]……)
begin 
	-- SQL
end;
# 根据传入分数,返回等级
create procedure p1([in score int],[out result varchar(10)])
begin
	if score >= 80 then
		set result = '优秀';
    elseif score >= 60 thens
    	set result = '及格';
    else set result = '不及格';
    end if;
end;
-- 定义用户变量,@result来接受返回数据
set @score = 99,result = '';
call p1(@score,@result);

5.2.5 case

case结构及作用,和我们在基础篇中所讲解的流程控制函数很类似。有两种语法格式:

-- 当case_val 值匹配,执行对应语句
case case_val
	when [val1] then [],
	when [val2] then [],
	……
	else []
end case;
-- 当条件成立时,执行对应语句
case
	when [condition1] then [],
	when [condition2] then [],
	……
	else []
end case;

5.2.6 循环

while 循环是有条件的循环控制语句。

while 条件 do
	-- SQL语句
end while;

repeat 是带有条件的循环控制语句,当满足until声明的条件的时候,则退出循环 。

--  先执行一次逻辑,然后判定UNTIL条件是否满足,如果满足,则退出。如果不满足,则继续下一次循环
repeat
	-- SQL语句
	until 条件
end repeat;

loop 循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。

# begin_label、end_label 是自定义的两个标记
[begin_label:] loop
	-- SQL
end loop [end_label];
leave label;     -- 退出指定标记的循环体
iterate label;  -- 直接进入下一次循环
# 例:计算1+……+n 值,n为传入参数
create procedure p1(in n int)
begin 
	declare tol int default 0;
	sum:loop      -- 循环开始标志
		if n<= 0 then
			leave sum;
         end if;
         set tol = tol + n;
         set n = n - 1;   
     end loop sum;   -- 循环结束标志
     select tol;
 end;

5.2.7 游标

游标(CURSOR)是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用游标对结果集进 行循环的处理。游标的使用包括游标的声明、OPEN、FETCH 和 CLOSE,其语法分别如下。

可以类比于 程序语言中的指针

# 声明游标
declare 游标名称 cursor for 查询语句;
# 打开游标
open 游标名称;
# 获取游标记录,一个变量可以接收一个字段的一个值
fetch 游标名称 into [变量1,变量2……];
# 关闭游标
close 游标名称;

若希望在调用存储过程中,使用循环 配合 游标进行数据的读取以及修改等操作,由于没有退出条件导致游标无法结束,需要借助 MySQL中提供的 条件处理程序 Handler 来解决。(类似于 编程语言中的捕捉异常并处理)

DECLARE  handler_action handler for [condition_val……] [statement];

-- handler_action 的取值:  
	CONTINUE: 继续执行当前程序
	EXIT: 终止执行当前程序
-- condition_value 的取值: 
	SQLSTATE  sqlstate_value: 状态码,如 02000
	SQLWARNING: 所有以01开头的SQLSTATE代码的简写
	NOT FOUND: 所有以02开头的SQLSTATE代码的简写
	SQLEXCEPTION: 所有没有被SQLWARNING 或 NOT FOUND捕获的SQLSTATE代码的简写   

5.2.8 存储函数

存储函数是具有返回值的存储过程,且存储函数的参数类型只能是in类型的

create function 存储函数名称(参数列表)
returns type [characteristic]
begin
	-- SQL语句
	return ……;
end;
-- characteristic说明
-- deterministic:相同的输入参数产生相同的结果
-- no SQL:不包含SQL语句
-- reads SQL data:包含读取数据的语句,但不包含写入数据的语句

5.3 触发器

触发器是与表有关的数据库对象,,指在insert/update/delete之前(before)或之后(after),触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性 ,日志记录,数据校验等操作。

使用别名old和new来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句级触发。

行级触发:执行一条 update 语句,影响了5行,触发器被触发了5次

语句级触发:不管影响了多少行,执行几条SQL被触发几次

触发器类型new 和 old
insert 型触发器new 表示将要或者已经新增的数据
update 型触发器old 表示修改之前的数据 ,new 表示将要或已经修改后的数据
delete 型触发器old 表示将要或者已经删除的数据
# 触发器创建
create trigger 触发器名称
[before | after] [insert|update|delete]
on 表名 for each row    -- 行级触发器
begin
	-- 执行的SQL语句
end;
# 查看触发器
show triggers;
# 删除触发器
drop trigger [schema_name.]触发器名称;  -- 不加数据库名称,默认删除当前数据库的触发器
-- 示例:通过触发器记录 tb_user 表的数据变更日志,将变更日志插入到日志表user_logs中,insert
-- 准备工作 : 日志表 user_logs
create table user_logs(
	id int(11) not null auto_increment,
	operation varchar(20) not null comment '操作类型, insert/update/delete',
	operate_time datetime not null comment '操作时间',	
	operate_id int(11) not null comment '操作的ID',
	operate_params varchar(500) comment '操作参数',
	primary key(`id`)
)engine=innodb default charset=utf8;
-- 变更日志,insert触发,update,delete大致相同
create trigger tb_user_add_trigger after insert on tb_user for each row
begin
	insert into user_logs values(
    	null,'insert',now(),new.id,
        concat('新插入的数据为:id=',new.id,',name=',new.name,',phone=',new.phone,',email=',new.email
              ',profession=',new.profession)
    );
end;

锁

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、 RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个 角度来说,锁对数据库而言显得尤其重要,也更加复杂。

在MySQL中,锁的类型分为

  • 全局锁:锁定数据库中的所有表
  • 表级锁:每次操作锁住整张表
  • 行级锁:每次操作锁住对应的行数据

6.1 全局锁

全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。

全局锁的应用场景:全库备份,锁定所有表,保证数据一致性。

如下图,若在备份数据时没有全库锁定,备份第一张表时,业务关联到库存,订单,日志,仅仅备份了在新业务来之前的库存数据,后续业务仍然写入,订单,日志增加了,但是库存却没有减少,造成了数据不一致,因此需要对全库锁定

image-20260124223153279

mysql 中的工具 mysqldump,可以备份数据库数据,锁定后数据只能读取,不能修改

# 1、锁定数据库
flush tables with read lock;
# 2、备份数据(注意该工具不是sql语句,在命令行下执行即可)
mysqldump -u root -p xxx [数据库名] > [备份的数据库文件.sql]
# 3、解锁
unlock tables;

数据库中加全局锁,是一个比较重的操作,存在以下问题:

  • 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
  • 如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导 致主从延迟。

6.2 表级锁

每次操作锁住整张表,锁的粒度(作用范围)大,发生锁冲突的概率最高,并发度最低。

存在三类表级锁:表锁、元数据锁(meta data lock,MDL)、意向锁

(1)表锁:共享读锁(read lock)、独占写锁(write lock)

# 加锁
lock tables 表名…… read/write
# 释放锁
unlock tables;   -- 或者客户端断开连接

对于读锁,会阻塞其他客户端的写入功能,对于写锁,会阻塞其他客户端的读和写功能(当前操作客户端不受影响)

(2)元数据锁

针对元数据加的一把锁,MDL

元数据:表名、字段名、字段类型,可以理解为表结构

元数据锁有4种类型锁:MDL_SHARED、MDL_SHARED_READ、MDL_SHARED_WRITE、MDL_EXCLUSIVE,这四种值分别对应

需要读取元数据,修改元数据,读取表数据,修改表数据

# 比如现在执行,这里只需要读取元数据,所以加的是 MDL_SHARED
create table t2 like t1;
-- MDL_SHARED_READ,需要读取元数据,表数据
select ……
-- MDL_SHARED_WRITE,需要读取元数据,修改表数据
insert | update | delete
-- MDL_EXCLUSIVE,需要修改元数据,修改表数据
DDL 语句

连接属于 MySQL层概念,事务属于 InnoDB 的概念,在 sql 执行时,优先执行MySQL层语句

一个连接给某张表加上了元数据锁,其他连接是否能操作这张表?这牵扯到 锁兼容 的概念

锁兼容:连接1 加上 A 锁,连接2想加 B锁,如果A和B锁兼容,则连接2加上B锁,否则需要等待连接1释放A锁

MDL_SHAREDMDL_SHARED_READMDL_SHARED_WRITEMDL_EXCLUSIVE
MDL_SHARED+++
MDL_SHARED_READ+++
MDL_SHARED_WRITE+++
MDL_EXCLUSIVE---

上表中,以横向为第一连接,纵向为第二连接,若修改了元数据,必定会影响其他操作,所以MDL_EXCLUSIVE和谁都不兼容

MDL 对所有 DML 语句 加 读锁,所有 DDL 语句 加 写锁(这里的锁都是表级的),读写锁阻塞关系(默认开启了事务):

  • 读锁和写锁会阻塞,即同一个表的 DML 和 DDL 相互阻塞
  • 写锁和写锁会阻塞,即两个Session不能同时对表定义更改,只能串行操作
  • 读锁和读锁不会阻塞,即 增删改查不会因为 MDL产生阻塞,可以并发执行
# 查看元数据锁
select object_type,object_schema,object_name,lock_type,lock_duration from 
	performance_schema.metadata_locks;

(3)意向锁

为了避免 DML 执行时,加的行锁和表锁的冲突,在 InnoDB 中引入意向锁,使得表锁不需要每行检查数据是否加锁,使用意向锁来减少表锁的检查。

意向共享锁(IS):与 表锁 read 兼容,与 write 互斥

意向排他锁(IX):与 read、write 都互斥

-- 添加 IS
select …… lock in share mode;
-- 添加 IX
insert | update | delete | select …… for update;

6.3 行级锁

行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在 InnoDB存储引擎中。

InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:

  • 行锁 Record Lock::锁定单个行记录的锁,防止其他事务对此行进行update和delete。在 RC、RR隔离级别下都支持
  • 间隙锁 Gap Lock:锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持
  • 临键锁 Next-Key Lock:行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。 在RR隔离级别下支持

在 InnoDB 中实现了两种行锁

共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。

排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。

SQL行锁类型说明
insert排他锁自动加锁
update排他锁自动加锁
delete排他锁自动加锁
select不加任何锁-

注:InnoDB 中的行锁是对于索引加的锁,而不走索引(或索引失效)检索数据,则会对全表加锁,即升级为表锁

# 以学生表(id,name,no)表为例,表中id为主键索引且无其他索引字段,执行update 加行锁的操作
-- 会话1
begin;
update tb_user set name = 'xxx' where name = 'yyy';
-- 会话2
begin;
update tb_user set name='23' where name = 'sdf';   -- 此时会话2中的update语句被阻塞,因为会话1对tb_user加了表锁

间隙锁

通常在加行级锁时,用到间隙锁最多,因为当在索引上扫描数据时,为防止其他事务插入间隙(造成幻读、不可重复读、脏读问题)

所以隔离级别 为 RR 都支持

且间隙锁是可以共存的,即多个事务可以在同一个间隙上加锁

MySQL 常用工具

这里所介绍的工具不是 mysql 服务,是指 mysql 的客户端工具

1、mysql

mysql [option] [database]
--
  -u : username
  -p : password
  -h : hostname (域名或 IP地址)
  -P : port (端口号)
  -e : execute  (执行SQL语句并退出)

2、mysqladmi

是一个执行管理操作的客户端程序,可以检查服务器配置和当前状态,创建并删除数据库等

mysqladmin [option] command
--
   -u : username
   -p : password
   -e : execute (执行SQL语句并退出)

3、mysqlbinlog

服务器生成的二进制日志文件 以 二进制格式保存,使用该工具查看日志或保存

mysqlbinlog log-file1 log-file2 ……
--
   -d : database
   -o : offset (忽略掉日志中的前n行命令)
   -r : result (将输出的文本格式日志输出到指定文件)
   -s : short  (显示简单格式,忽略一些信息)

4、mysqlshow

客户端对象查找工具,快速查找数据库、数据库表、表中索引等信息

mysqlshow -uroot -pxxx 
--
   -count   (显示数据库及表的统计信息,可以不指定数据库、表)

5、mysqldump

客户端用来备份数据库数据工具

mysqldump [option] database(待备份数据库) > 备份路径及备份文件名 
连接选项:
	-u : username
	-p : password
	-h
	-P
输出选项:
	-T : --tab=name (自动生成两个文件,一个.sql(表结构语句),另一个.txt(数据文件))

6、mysqlimport

客户端导入数据工具

mysqlimport [option] db_name testfile testfile2 ……