零,快速总结篇
# 1)、查询所有数据库 show databases ; # 2)、查询当前数据库 select database() ; # 3)、创建数据库 create database [ if not exists ] 数据库名 [ default charset 字符集 ] [ collate 排序规则 ] ; # 4)、删除数据库 drop database [ if exists ] 数据库名 ; # 5)、切换数据库 use 数据库名 ;
# 1. 查询当前数据库所有表 show tables; # 2. 查看指定表结构 desc 表名 ; # 3. 查询指定表的建表语句 show create table 表名 ;# 4. 创建表结构 create table 表名(字段1 字段1类型 [comment 字段1注释 ],字段2 字段2类型 [comment 字段2注释 ],字段3 字段3类型 [comment 字段3注释 ],......字段n 字段n类型 [comment 字段n注释 ] ) [ comment 表注释 ] ;# 5. 添加字段 alter table 表名 add 字段名 类型 (长度) [ comment 注释 ] [ 约束 ]; # 6. 修改数据类型 alter table 表名 modify 字段名 新数据类型 (长度); # 7. 修改字段名和字段类型 alter table 表名 change 旧字段名 新字段名 类型 (长度) [ comment 注释 ] # 8. 删除字段 alter table 表名 drop 字段名; # 9. 修改表名 alter table 表名 rename to 新表名;# 10. 删除表 drop table [ if exists ] 表名; # 11. 删除指定表, 并重新创建表 truncate table 表名;
# 1)、 给指定字段添加数据 insert into 表名 (字段名1, 字段名2, ...) values (值1, 值2, ...) ; # 2)、给全部字段添加数据 insert into 表名 values (值1, 值2, ...) ; # 3)、批量添加数据 insert into 表名 (字段名1, 字段名2, ...) values (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...) ; insert into 表名 values (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...) ;# 修改数据的具体语法为: update 表名 set 字段名1 = 值1 , 字段名2 = 值2 , .... [ where 条件 ] ;# 删除数据的具体语法为: delete from 表名 [ where 条件 ] ;
# **1、基本查询**(`不带任何条件`) # 1.1). 查询多个字段 select 字段1, 字段2, 字1 段3 ... from 表名 ; select * from 表名 ;# 1.2). 字段设置别名 select 字段1 [ as 别名1 ] , 字段2 [ as 别名2 ] ... from 表名; select 字段1 [ 别名1 ] , 字段2 [ 别名2 ] ... from 表名;# 1.3). 去除重复记录 select distinct 字段列表 from 表名;# **2、条件查询**(`where`) select 字段列表 from 表名 where 条件列表 ;# **3、聚合函数**(`count、max、min、avg、sum`) select 聚合函数(字段列表) from 表名 ;# **4、分组查询**(`group by`) select 字段列表 from 表名 [ where 条件 ] group by 分组字段名 [ having 分组后过滤条件 ];# **5、排序查询**(`order by`) select 字段列表 from 表名 order by 字段1 排序方式1(asc/desc) , 字段2 排序方式2(asc/desc) ;# **6、分页查询**(`limit`) select 字段列表 from 表名 limit 起始索引, 查询记录数 ;
# 1、管理用户 # 1.1). 查询用户 select * from mysql.user;# 1.2). 创建用户 create user '用户名'@'主机名' identified by '密码';# 1.3). 修改用户密码 alter user '用户名'@'主机名' identified with mysql_native_password by '新密码' ;# 1.4). 删除用户 drop user '用户名'@'主机名' ;# 2、权限控制 # 2.1). 查询权限 show grants for '用户名'@'主机名' ;# 2.2). 授予权限 grant 权限列表 on 数据库名.表名 to '用户名'@'主机名';# 2.3). 撤销权限 revoke 权限列表 on 数据库名.表名 from '用户名'@'主机名';
# 1、索引概述 索引是高效获取数据的(有序)数据结构;# 2、索引结构 B+Tree /*(所有的数据都会出现在叶子节点,并且叶子结点形成了一个双向链表)*/ Hash /*(Memory存储引擎当中支持的索引结构,就是一个哈希表【只支持精确匹配,不支持范围查询及排序】)*/# 3、索引分类 主键索引、唯一索引、常规索引、全文索引 聚集索引、二级索引# 4、索引语法 ## 4.1) 创建索引 create [ unique | fulltext ] index index_name on table_name (index_col_name,... /*一个索引可以关联多个字段*/) ; ## 4.2) 查看索引 show index from table_name; ## 4.3) 删除索引 drop index index_name on table_name;# 5、SQL 性能分析 执行频次、慢查询日志、profile、explain(/*使用最多*/) show global status like 'Com_______'; // 查看SQL执行频率 show variables like 'slow_query_log'; // 查看慢查询日志 SELECT @@have_profiling ; // profile详情 explain select 字段列表 from 表名 where 条件 ; // 直接在select语句之前加上关键字 explain / desc# 6、索引使用 联合索引 -- 最左前缀法则;>=、<= 索引失效 -- 函数运算、字符串不加引号、%模糊查询、or连接条件、数据分布影响 SQL 提示 -- use、ignore、force 覆盖索引 -- 需要返回的列,在该索引中已经全部能够找到,不需要回表查询(二级索引 —> id —> 聚集索引查找) 前缀索引 -- 字符串长度较长、大文本字段 单列/联合索引 -- 推荐使用联合索引,性能高、也能避免回表查询 # 7、索引设计原则 哪些表 -- 数据量大、查询频次高 哪些字段 -- where、order by、group by 建立什么样的索引 -- 唯一索引、联合索引、前缀索引
一,mysql的概念
1.1 、数据库相关概念
数据库、数据库管理系统、SQL:
Oracle
:大型的收费数据库,Oracle公司产品,价格昂贵。MySQL
:开源免费的中小型数据库,后来Sun公司收购了MySQL,而Oracle又收购了Sun公司。目前Oracle推出了收费版本的 MySQL,也提供了免费的社区版本。SQL Server
:Microsoft 公司推出的收费的中型数据库,C#、.net等语言常用。PostgreSQL
:开源免费的中小型数据库。DB2
:IBM公司的大型收费数据库产品。
而不论我们使用的是上面的哪一个关系型数据库,最终在操作时,都是使用 SQL 语言来进行统一操作,因为 SQL 语言,是操作关系型数据库的 统一标准。所以即使我们现在学习的是MySQL,假如我们以后到了公司,使用的是别的关系型数据库
如:Oracle、DB2、SQLServer,也完全不用担心,因为操作的方式都是一致的。
1.1.1 MySQL启动和停止
net start mysql80 --启动
net stop mysql80 --停止
1.2 、MySQL 客户端连接
- 方式一:使用 MySQL 提供的客户端命令行工具
- 方式二:使用系统自带的命令行工具执行指令
mysql [-h 127.0.0.1] [-P 3306] -u root -p
参数:
-h
: MySQL服务所在的主机 IP-P
: MySQL服务端口号,默认3306-u
: MySQL数据库用户名-p
: MySQL数据厍用户名对应的密码
[]
内为可选参数,如果需要连接 远程的 MySQL,需要加上这两个参数来指定远程主机IP、端口,如果连接本地的MySQL,则无需指定这两个参数。
注意: 使用这种方式进行连接时,需要安装完毕后配置path环境变量
。
1.3 、数据模型
⭐️ 1)、关系型数据库(RDBMS
)
概念:建立在关系模型基础上,由多张相互连接的 二维表 组成的数据库。
而所谓 二维表,指的是由行和列组成的表,如下图(就类似于Excel表格数据,有表头、有列、有行,还可以通过一列关联另外一个表格中的某一列数据)。
我们之前提到的 MySQL、Oracle、DB2、SQLServer 这些都是属于关系型数据库,里面都是基于二维表存储数据的。
简单说,基于二维表存储数据的数据库就成为 关系型数据库,不是基于二维表存储数据的数据库,就是 非关系型数据库。
特点:
- A. 使用表存储数据,格式统一,便于维护。
- B. 使用 SQL语言操作,标准统一,使用方便。
⭐️ 2)、数据模型
- MySQL是关系型数据库,是基于 二维表 进行数据存储的,具体的结构图下:
- 我们可以通过MySQL客户端连接 数据库管理系统 DBMS,然后通过DBMS操作数据库。
- 可以使用SQL语句,通过 数据库管理系统 操作数据库,以及操作数据库中的表结构及数据。
- 一个数据库服务器中可以创建多个数据库,一个数据库中也可以包含多张表,而一张表中又可以包含多行记录。
二、SQL
全称 Structured Query Language
,结构化查询语言。操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准 。
2.1、SQL通用语法
- SQL语句可以 单行 或 多行 书写,以 分号 结尾。
- SQL语句可以使用 空格/缩进 来增强语句的可读性。
- MySQL数据库的SQL语句 不区分大小写 ,关键字建议使用大写。
- 注释:
- 单行注释:
-- 注释内容
或# 注释内容
- 多行注释:
/* 注释内容 */
- 单行注释:
2.2、SQL分类
SQL语句,根据其功能,主要分为四类:DDL
、DML
、DML、DCL
。
DDL:数据定义语句 用来定义数据库(数据库,表,字段)
DML:数据库操作语言 用来对数据库表中的数据进行增删改
DML:数据库查询语言 用来查询数据库表中的数据
DCL:数据库控制语言 用来控制数据库的访问权限 创建数据库用户的
2.3、DDL
2.3.1 DDL — 数据库操作
⭐️ 1)、查询所有数据库: show databases ;
⭐️ 2)、查询当前数据库 show database();
⭐️ 3)、创建数据库
create database [ if not exists ] 数据库名 [ default charset 字符集 ] [ collate 排序规则 ] ;
- [] 内的是可选项。
- 字符集例如:utf8,b4;不建议使用 utf8, 其长度为3个字节,而 utf8mb4 长度是4个字节。
- 在同一个数据库服务器中,不能创建两个名称相同的数据库,否则将会报错。(可以通过 if not exists 参数来解决这个问题,数据库不存在, 则创建该数据库,如果存在,则不创建。)
⭐️ 4)、删除数据库 drop database [ if exists ] 数据库名 ;
⭐️ 5)、切换数据库 use 数据库名称;
2.3.1 DDL — 表操作
⭐️ 1)、表操作-查询创建
- 查询当前数据库所有表 show tables;
- 查看指定表结构 desc 表名; 通过这条指令,我们可以查看到指定表的字段,字段的类型、是否可以为
NULL
,是否存在默认值等信息。 - 查询指定表的建表语句 show cheate table 表名; 通过这条指令,主要是用来
查看建表语句
的,而有部分参数我们在创建表的时候,并未指定也会查询到,因为这部分是数据库的默认值,如:存储引擎、字符集等。 - 创建表结构
CREATE TABLE 表名(字段1 字段1类型 [COMMENT 字段1注释 ],字段n 字段n类型 [COMMENT 字段n注释 ] ) [ COMMENT 表注释 ] ;
⭐️ 2)、表操作-数据类型
- 在上述的建表语句中,我们在指定字段的数据类型时,用到了
int
,varchar
,那么在MySQL中除了以上的数据类型,还有哪些常见的数据类型呢? 接下来,我们就来详细介绍一下MySQL的数据类型。 - MySQL中的数据类型有很多,主要分为三类:
数值类型
、字符串类型
、日期时间类型
。
数值类型
根据数值取值范围的不同MySQL 中的整数类型可分为5种,分别是tinyint、smalunt、mediumint、int和 bigint。下图列举了 MySQL不同整数类型所对应的字节大小和取值范围而最常用的为int类型的,
数据类型 | 字节 | 无符号取值范围 | 有符号 |
tinyint | 1 | 0~255 =2的8次方 | 0为中间值 -128~127 |
smalunt | 2 | 0~65535 =2的16次方 | 同理 |
mediumint | 3 | 0~16777215=2的24次方 | 同理 |
int | 4 | 0~4294967295=2的32次方 | 同理 |
bigint | 8 | 0~18446744073709551615=2的34次方 | 同理 |
2.浮点数类型和定点数类型
在MySQL数据库中使用浮点数和定点数来存储小数。浮点数的类型有两种:单精度浮点数类型(float)和双精度浮点数类型(double)。而定点数类型只有一种即decimal类型。下图列举了 MySQL中浮点数和定点数类型所对应的字节大小及其取值范围:
数据类型 | 字节 | 有符号取值范围 |
float | 4 | -3.402823466E+38~-1.175494351E-38 |
double | 8 | -1.7976931348623157E+308~2.2250738585072014E-308 |
decimal | M+2 | -1.7976931348623157E+308~2.2250738585072014E-308 |
从上图中可以看出:DECIMAL类型的取值范围与DOUBLE类型相同。但是,请注意:DECIMAL类型的有效取值范围是由M和D决定的。其中,M表示的是数据的长 度,D表示的是小数点后的长度。比如,将数据类型为decimal(6,2)的数据6.5243 插入数据库后显示的结果为6.52
3.字符串类型
在MySQL中常用char和 varchar表示字符串。两者不同的是:VARCHAR存储可变长度的字符串。
当数据为CHAR(M)类型时,不管插入值的长度是实际是多少它所占用的存储空间都是M个字节;而VARCHAR(M)所对应的数据所占用的字节数为实际长度加1
插入值 | char(3) | 存储需求 | varchar(3) | 存储需求 |
---|---|---|---|---|
‘’ | ‘’ | 3个字节 | ‘’ | 1个字节 |
‘a’ | ‘a’ | 3个字节 | ‘a’ | 2个字节 |
‘ab’ | ‘ab’ | 3个字节 | ‘ab’ | 3个字节 |
‘abc’ | ‘ab’ | 3个字节 | ‘abc’ | 4个字节 |
‘abcd’ | ‘ab’ | 3个字节 | ‘abc’ | 4字节 |
4.文本类型
文本类型用于表示大文本数据,例如,文章内容、评论、详情等,它的类型分为如下4种:
数据类型 | 字节 | 储存范围 |
---|---|---|
tinytext | 1 | 0~255字节 |
text | 2 | 0~65535字节 |
mediumtext | 3 | 0~16777215字节 |
longtext | 4 | 0~4294967295字节 |
5.日期与时间类型
数据类型 | 字节 | 取值范围 | 日期格式 | 零值 |
year | 1 | 1901~2155 | YYYY | 0000 |
date | 4 | 1000-01-01~9999-12-31 | YYYY-MM-DD | 0000-00-00 |
time | 3 | -838:59:59~ 838:59:59 | HH-MM-SS | 00:00:00 |
datetime | 8 | 1000-01-01 00:00:00~9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 0000-00-00 00:00:00 |
timestamp | 4 | 1970-01-01 00:00:01~2038-01-19 03:14:07 | YYYY-MM-DD HH:MM:SS | 0000-00-00 00:00:00 |
6.二进制类型
在MySQL中常用BLOB存储二进制类型的数据,例如:图片、PDF文档等。BLOB类型分为如下四种:
数据类型 | 字节 | 存储范围 |
tinyblob | 1 | 0~255字节 |
blob | 2 | 0~65535字节 |
mediumblob | 3 | 0~16777215字节 |
longblob | 4 | 0~4294967295字节 |
⭐️ 表操作-案例
设计一张员工信息表,要求如下:
- 编号(纯数字)
- 员工工号 (字符串类型,长度不超过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 '员工表';
⭐️ 3)、表操作-修改
- 添加字段 alter table 表名 add 字段名 类型 (长度) [ comment 注释 ] [ 约束 ];
- 修改
数据类型 alter table 表名 modify 字段名 新数据类型 (长度);
- 修改
字段名和字段类型 alter table 表名 change 旧字段名 新字段名 类型 (长度) [ comment 注释 ]
- 删除字段 alter table 表名 drop 字段名;
- 修改表名 alter table 表名 rename to 新表名;
⭐️ 4)、表操作-删除
- 删除表 drop table [ if exists ] 表名;
- 删除指定表, 并重新创建表 truncate table 表名; 注意: 在删除表的时候,表中的全部数据也都会被删除。
2.4、DML
DML英文全称是Data Manipulation Language
(数据操作语言),用来对数据库中表的数据记录进行 增
、删
、改
操作。
- 添加数据(insert)
- 修改数据(update)
- 删除数据(
delect
)
2.4.1 DML——添加数据
⭐️ 1)、 给指定字段添加数据 insert into 表名 (字段名1, 字段名2, ...) values (值1, 值2, ...) ;
注意事项:
- 插入数据时,指定的字段顺序需要与值得顺序一 一对应。
- 字符串和日期型数据应该包含在引用中。
- 插入得数据大小,应该在字段的规定范围内。
⭐️ 2)、给全部字段添加数据 insert into 表名 values (值1, 值2, ...) ;
⭐️ 3)、批量添加数据
--格式1 insert into 表名 (字段名1, 字段名2, ...) values (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...) ; --格式二 insert into 表名 values (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...) ;
2.4.1 DML——修改数据
update 表名 set 字段名1 = 值1 , 字段名2 = 值2 , .... [ where 条件 ] ;
注意事项:
- 修改语句的条件可以有,也可以没有,如果没有条件,则会修改整张表的所有数据。
- delete 语句不能删除某一个字段的值(可以使用 update,将该字段值置为 null 即可)。
- 当进行删除全部数据操作时,datagrip 会提示我们,询问是否确认删除,我们直接点击Execute 即可。
2.4.1 DML——删除数据
delete from 表名 [ where 条件 ] ;
2.5、DQL
DQL
英文全称是 Data Query Language
(数据查询语言),数据查询语言,用来查询数据库中表的记录。
查询关键字: SELECT
在一个正常的业务系统中,查询操作的频次是要远高于增删改的,当我们去访问企业官网、电商网站,在这些网站中我们所看到的数据,实际都是需要从数据库中查询并展示的。而且在查询的过程中,可能还会涉及到条件、排序、分页等操作。
1 drop table if exists employee; 2 3 create table emp( 4 id int comment '编号', 5 workno varchar(10) comment '工号', 6 name varchar(10) comment '姓名', 7 gender char(1) comment '性别', 8 age tinyint unsigned comment '年龄', 9 idcard char(18) comment '身份证号', 10 workaddress varchar(50) comment '工作地址', 11 entrydate date comment '入职时间' 12 )comment '员工表'; 13 14 insert into emp (id, workno, name, gender, age, idcard, workaddress, entrydate) 15 values (1, '00001', '柳岩666', '女', 20, '123456789012345678', '北京', '2020-01-01'), 16 (2, '00002', '张无忌', '男', 18, '123456789012345670', '北京', '2021-09-01'), 17 (3, '00003', '韦一笑', '男', 38, '123456789712345670', '上海', '2021-08-01'), 18 (4, '00004', '赵敏', '女', 18, '123456757123845670', '北京', '2022-12-01'), 19 (5, '00005', '小昭', '女', 16, '123456769012345678', '上海', '2022-07-01'), 20 (6, '00006', '杨逍', '男', 28, '12345678931234567X', '北京', '2022-01-01'), 21 (7, '00007', '范瑶', '男', 40, '123456789212345670', '北京', '2022-05-01'), 22 (8, '00008', '黛绮丝', '女', 38, '123456157123645670', '天津', '2023-05-01'), 23 (9, '00009', '范凉凉', '女', 45, '123156789012345678', '北京', '2023-04-01'), 24 (10, '00010', '陈友谅', '男', 53, '123456789012345670', '上海', '2023-01-01'), 25 (11, '00011', '张士诚', '男', 55, '123567897123465670', '江苏', '2023-05-01'), 26 (12, '00012', '常遇春', '男', 32, '123446757152345670', '北京', '2004-02-01'), 27 (13, '00013', '张三丰', '男', 88, '123656789012345678', '江苏', '2020-11-01'), 28 (14, '00014', '灭绝', '女', 65, '123456719012345670', '西安', '2022-05-01'), 29 (15, '00015', '胡青牛', '男', 70, '12345674971234567X', '西安', '2023-04-01'), 30 (16, '00016', '周芷若', '女', 18, null, '北京', '2022-06-01');
2.5.1 基础语法
1 select 2 字段列表 3 from 4 表名列表 5 where 6 条件列表 7 group by 8 分组字段列表 9 having 10 分组后条件列表 11 order by 12 排序字段列表 13 limit 14 分页参数
- 基本查询(
不带任何条件
) - 条件查询(
where
) - 聚合函数(
count、max、min、avg、sum
) - 分组查询(
group by
) - 排序查询(
order by
) - 分页查询(
limit
)
2.5.2 基础查询
在基本查询的DQL语句中,不带任何的查询条件,查询的语法如下:
⭐️ 1). 查询多个字段
select 字段1, 字段2, 字段3 ... from 表名 ; select * from 表名 ;
注意 : *
号代表查询所有字段,在实际开发中尽量少用(不直观、影响效率)。
⭐️ 2). 字段设置别名
select 字段1 [ as 别名1 ] , 字段2 [ as 别名2 ] ... from 表名; select 字段1 [ 别名1 ] , 字段2 [ 别名2 ] ... from 表名;
⭐️ 3). 去除重复记录
select distinct 字段列表 from 表名;
2.5.3 条件查询
⭐️ 1). 语法
select 字段列表 from 表名 where 条件列表 ;
⭐️ 2). 条件
- 常用的比较运算符如下:
- 常用的逻辑运算符如下:
一般使用 and
, or
, not
。
2.5.4 聚合函数
⭐️ 1). 介绍
- 将一列数据作为一个整体,进行 纵向计算。
⭐️ 2). 常见的聚合函数
⭐️ 3). 语法
select 聚合函数(字段列表) from 表名 ; 注意 : NULL
值是不参与所有聚合函数运算的。
2.5.5 分组查询
⭐️ 1). 语法
select 字段列表 from 表名 [ where 条件 ] group by 分组字段名 [ having 分组后过滤条件 ];
⭐️ 2). where
与 having
区别
- 执行时机不同:
where
是 分组之前 进行过滤,不满足where
条件,不参与分组;而having
是 分组之后 对结果进行过滤。 - 判断条件不同:
where
不能对 聚合函数 进行判断,而having
可以。
注意事项:
- 分组之后,查询的字段一般为 聚合函数 和 分组字段,查询其他字段无任何意义。
- 执行顺序:
where
>聚合函数
>having
。 - 支持多字段分组, 具体语法为 :
group by columnA,columnB
2.5.6 排序查询
排序在日常开发中是非常常见的一个操作,有 升序排序,也有 降序排序。⭐️ 1). 语法
select 字段列表 from 表名 order by 字段1 排序方式1 , 字段2 排序方式2 ;
⭐️ 2).排序方式
ASC
: 升序(默认值
)DESC
: 降序
注意事项:
- 如果是升序, 可以不指定排序方式
ASC
; - 如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序 ;
2.5.7 分页查询
分页操作在业务系统开发时,也是非常常见的一个功能,我们在网站中看到的各种各样的分页条,后台都需要借助于数据库的分页操作。
⭐️ 1). 语法
select 字段列表 from 表名 limit 起始索引, 查询记录数 ;
注意事项:
- 起始索引从
0
开始,起始索引
=(查询页码 - 1)* 每页显示记录数
。 - 分页查询是数据库的方言,不同的数据库有不同的实现,MySQL中是
LIMIT
。 - 如果查询的是第一页数据,起始索引可以省略,直接简写为
limit 10
。
2.5.8 执行顺序
DQL语句在执行时是有执行顺序的,也就是先执行那一部分,后执行那一部分:
2.6、DCL
DCL英文全称是 Data Control Language
(数据控制语言),用来管理数据库用户、控制数据库的访问权限。
2.6.1 管理用户
⭐️ 1). 查询用户
select * from mysql.user;
- 其中
Host
代表当前用户访问的主机, 如果为localhost
, 仅代表只能够在当前本机访问,是不可以远程访问的。 User
代表的是访问该数据库的用户名。- 在MySQL中需要通过
Host
和User
来唯一标识一个用户。
⭐️ 2). 创建用户
# 只能够在当前主机localhost访问 create user '用户名'@'主机名' identified by '密码'; # 可以在任意主机访问该数据库 create user '用户名'@'%' identified by '密码';
⭐️ 3). 修改用户密码
alter user '用户名'@'主机名' identified with mysql_native_password by '新密码' ;
⭐️ 4). 删除用户
drop user '用户名'@'主机名' ;
注意事项:
- 在MySQL中需要通过
用户名@主机名
的方式,来唯一标识一个用户。 - 主机名可以使用
%
通配。 - 这类SQL开发人员操作的比较少,主要是
DBA
(Database Administrator
数据库管理员)使用。
2.6.2 权限控制
MySQL中定义了很多种权限,但是常用的就以下几种:
# 1、管理用户 # 1.1). 查询用户 select * from mysql.user;# 1.2). 创建用户 create user '用户名'@'主机名' identified by '密码';# 1.3). 修改用户密码 alter user '用户名'@'主机名' identified with mysql_native_password by '新密码' ;# 1.4). 删除用户 drop user '用户名'@'主机名' ;# 2、权限控制 # 2.1). 查询权限 show grants for '用户名'@'主机名' ;# 2.2). 授予权限 grant 权限列表 on 数据库名.表名 to '用户名'@'主机名';# 2.3). 撤销权限 revoke 权限列表 on 数据库名.表名 from '用户名'@'主机名';
注意事项:
- 多个权限之间,使用逗号分隔;
- 授权时, 数据库名和表名可以使用
*
进行通配,代表所有。
三、函数
函数 是指一段可以直接被另一段程序调用的程序或代码。 也就意味着,这一段程序或代码在MySQL中 已经给我们提供了,我们要做的就是在合适的业务场景调用对应的函数完成对应的业务需求即可。
那么,函数到底在哪儿使用呢?
3.1 字符串函数
MySQL中内置了很多字符串函数,常用的几个如下:
举个例子: 由于业务需求变更,企业员工的工号,统一为5位数,目前不足5位数的全部在前面补0。比如: 1号员工的工号应该为00001。
update emp set workno = lpad(workno, 5, '0');
3.2 数值函数
常见的数值函数如下:
举个例子: 通过数据库的函数,生成一个六位数的随机验证码。
思路: 获取随机数可以通过rand()函数,但是获取出来的随机数是在0-1之间的,所以可以在其基础上乘以1000000,然后舍弃小数部分,如果长度不足6位,补0
select lpad(round(rand()*1000000 , 0), 6, '0');
3.3 日期函数
常见的日期函数如下:
举个例子: 查询所有员工的入职天数,并根据入职天数倒序排序。
- 思路: 入职天数,就是通过当前日期 - 入职日期,所以需要使用
datediff
函数来完成。
select name, datediff(curdate(), entrydate) as 'entrydays' from emp order by entrydays desc;
3.4 流程函数
流程函数也是很常用的一类函数,可以在SQL语句中实现 条件筛选,从而提高语句的效率。
举个栗子: 需求: 查询emp表的员工姓名和工作地址 (北京/上海 ----> 一线城市 , 其他 ----> 二线城市)
selectname,( case workaddress when '北京' then '一线城市' when '上海' then '一线城市' else'二线城市' end ) as '工作地址' from emp;
## 3.1 字符串函数----------------------------------------------------- # 1. concat : 字符串拼接 select concat('1 Hello', ' MySQL'); # 2. lower : 全部转小写 select lower('Hello'); # 3. upper : 全部转大写 select upper('Hello'); # 4. lpad : 左填充,达到5个字符 select lpad('01', 5, '-'); # 5. rpad : 右填充,达到5个字符 select rpad('01', 5, '-'); # 6. trim : 去除头尾空格 select trim(' Hello MySQL '); # 7. substring : 截取子字符串,从第一个截取5个字符 select substring('r Hello MySQL', 1, 5);## 3.2 数值函数------------------------------------------------------- # 1. ceil:向上取整,值为2 select ceil(1.1); # 2. floor:向下取整,值为1 select floor(1.9); # 3. mod:取模,值为3 select mod(7, 4); # 4. rand:获取随机数 select rand(); # round:四舍五入,值为2.34 select round(2.344, 2);## 3.3 日期函数------------------------------------------------------- # 1. curdate:当前日期 select curdate(); # 2. curtime:当前时间 select curtime(); # 3. now:当前日期和时间 select now(); # 4. YEAR , MONTH , DAY:当前年、月、日 select year(now()); select month(now()); select day(now()); # 5. date_add:增加指定的时间间隔 select date_add(now(), interval 70 year ); # 6. datediff:获取两个日期相差的天数 select datediff('2021-10-01', '2021-12-01');## 3.4 流程函数------------------------------------------------------- # 1. if,返回 Error select if(false, 'Ok', 'Error'); # 2. ifnull select ifnull('Ok','Default'); # 返回 Ok select ifnull('','Default'); # 返回 ‘’ select ifnull(null,'Default'); # 返回 Default # 3. case when then else end selectname,( case workaddress when '北京' then '一线城市' when '上海' then '一线城市' else'二线城市' end ) as '工作地址' from emp;
四、约束
4.1 概述
- 概念:约束是作用于表中字段上的规则,用于限制存储在表中的数据。
- 目的:保证数据库中数据的正确、有效性和完整性。
- 分类:
primary key
:存在且唯一;unique
:只需唯一,不一定存在。
注意:约束是作用于表中字段上的,可以在创建表/修改表的时候添加约束。
测试数据:
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, '总经办');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 '员工表';INSERT INTO emp (id, name, age, job,salary, entrydate, managerid, dept_id) VALUES (1, '金庸', 66, '总裁',20000, '2020-01-01', null,5),(2, '张无忌', 20, '项目经理',12500, '2022-12-05', 1,1), (3, '杨逍', 33, '开发', 8400,'2019-11-03', 2,1),(4, '韦一笑', 48, '开发',11000, '2023-02-05', 2,1), (5, '常遇春', 43, '开发',10500, '2018-09-07', 3,1),(6, '小昭', 19, '程序员鼓励师',6600, '2022-10-12', 2,1);
4.2 约束演示
上面我们介绍了数据库中常见的约束,以及约束涉及到的关键字,那这些约束我们到底如何在创建表、修改表的时候来指定呢,接下来我们就通过一个案例,来演示一下。
案例需求: 根据需求,完成表结构的创建。需求如下:
--建表语句 create table tb_user(id int auto_increment primary key comment 'ID主键',name varchar(10) not null unique comment '姓名' ,age int check (age > 0 && age <= 120) comment '年龄' ,status char(1) default '1' comment '状态',gender char(1) comment '性别' ) comment '用户表';
4.3 外键约束
4.3.1 介绍
外键:用来让两张表的数据之间建立连接,从而保证数据的 一致性 和 完整性。
大家知道:建立外键是为了保证数据的完整和统一性。但是,如果主表中的数据被删除或修改从表中对应的数据该怎么办呢?很明显,从表中对应的数据也应该被删除,否则数据库中会存在很多无意义的垃圾数据。
4.3.2 语法
⭐️ 1). 添加外键
create table 表名(字段名 数据类型,...[constraint] [外键名称] foreign key (外键字段名) references 主表 (主表列名) );
alter table 表名 add constraint 外键名称 foreign key (外键字段名) references 主表 (主表列名) ;
⭐️ 2). 删除外键
alter table 表名 drop foreign key 外键名称;
4.3.3 删除/更新行为
添加了外键之后,再删除父表数据时产生的约束行为,我们就称为 删除/更新行为。具体的删除/更新行为有以下几种:
⭐️ 1). CASCADE
alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id) on update cascade on delete cascade ;
- 删除父表
id
为1的记录 - 父表的数据删除成功了,但是子表中关联的记录也被级联删除了。
⭐️ 2). SET NULL
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
了。
# 1. 非空约束 not null # 2. 唯一约束 unique # 3. 主键约束 primary key (/*自增:*/ auto_increment) # 4. 默认约束 default # 5. 检查约束 check # 6. 外键约束 foreign key
五、多表查询
在复习SQL语句 的时候,复习了了DQL语句,也就是数据查询语句,但是之前讲解的查询都是单表查询,而本章节我们要复习的则是多表查询操作,主要从以下几个方面进行复习。
5.1 多表关系
项目开发中,在进行数据库表结构设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表结构之间也存在着各种联系,基本上分为三种:
- 一对多(多对一)
- 多对多
- 一对一
# 1、一对多 在多的一方建立外键,指向一的一方的主键。# 2、多对多 建立第三张中间表,中间表至少包含两个外键,分别关联两方主键。# 3、一对一 在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(unique)。
5.1.1 一对多
- 案例: 部门 与 员工的关系
- 关系: 一个部门对应多个员工,一个员工对应一个部门
- 实现: 在多的一方建立外键,指向一的一方的主键
5.1.2 多对多
- 案例: 学生 与 课程的关系
- 关系: 一个学生可以选修多门课程,一门课程也可以供多个学生选择
- 实现: 建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
5.1.3 一对一
- 案例: 用户 与 用户详情的关系
- 关系: 一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他详情字段放在另一张表中,以提升操作效率
- 实现: 在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(
unique
)
一对一经常用作单表的拆分,当然也可以合并成一张表!
5.2 多表查询概述
测试数据:
-- 创建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, '2010-01-01', null,5),(2, '张无忌', 20, '项目经理',12500, '2015-12-05', 1,1),(3, '杨逍', 33, '开发', 8400,'2010-11-03', 2,1),(4, '韦一笑', 48, '开发',11000, '2012-02-05', 2,1),(5, '常遇春', 43, '开发',10500, '2014-09-07', 3,1),(6, '小昭', 19, '程序员鼓励师',6600, '2014-10-12', 2,1),(7, '灭绝', 60, '财务总监',8500, '2012-09-12', 1,3),(8, '周芷若', 19, '会计',48000, '2016-06-02', 7,3),(9, '丁敏君', 23, '出纳',5250, '2019-05-13', 7,3),(10, '赵敏', 20, '市场部总监',12500, '2014-10-12', 1,2),(11, '鹿杖客', 56, '职员',3750, '2016-10-03', 10,2),(12, '鹤笔翁', 19, '职员',3750, '2017-05-09', 10,2),(13, '方东白', 19, '职员',5500, '2019-02-12', 10,2),(14, '张三丰', 88, '销售总监',14000, '2014-10-12', 1,4),(15, '俞莲舟', 38, '销售',4600, '2014-10-12', 14,4),(16, '宋远桥', 40, '销售',4600, '2014-10-12', 14,4),(17, '陈友谅', 42, null,2000, '2021-10-12', 1,null);
5.2.1 概述
多表查询就是指从多张表中查询数据。
- 原来查询单表数据,执行的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 ;
5.2.3 分类
⭐️ 连接查询
- 内连接:相当于查询
A
、B
交集部分数据 - 外连接:
- 左外连接:查询左表所有数据,以及两张表交集部分数据;
- 右外连接:查询右表所有数据,以及两张表交集部分数据。
- 自连接:当前表与自身的连接查询,自连接必须使用表别名。
⭐️ 子查询
5.3 内连接
内连接查询的是两张表交集部分的数据。(也就是 绿色 部分的数据).
内连接的语法分为两种: 隐式内连接、显式内连接。先来学习一下具体的语法结构。
--隐式 select 字段列表 from 表1 , 表2 where 条件 ... ; --显式 select 字段列表 from 表1 [ inner ] join 表2 on 连接条件 ... ;
5.4 外连接
外连接分为两种,分别是:左外连接 和 右外连接。具体的语法结构为:
--左外连接 select 字段列表 from 表1 left [ outer ] join 表2 on 条件 ... ; --右外连接 select 字段列表 from 表1 right [ outer ] join 表2 on 条件 ... ;
- 左外连接相当于查询 表1(左表)的所有数据,当然也包含表1和表2交集部分的数据。
- 右外连接相当于查询 表2(右表)的所有数据,当然也包含表1和表2交集部分的数据。
注意事项:
- 左外连接和右外连接是可以相互替换的,只需要调整在连接查询时SQL中,表结构的先后顺序就可以了。而我们在日常开发使用时,更偏向于 左外连接 。
5.5 自连接
5.5.1 自连接查询
自连接查询,顾名思义,就是自己连接自己,也就是把一张表连接查询多次。我们先来学习一下自连接的查询语法:
select 字段列表 from 表A 别名A join 表A 别名B on 条件 ... ;
- 而对于自连接查询,可以是内连接查询,也可以是外连接查询。
例子:
查询员工 及其 所属领导的名字
- 表结构:
emp
select a.name, b.name from emp a, emp b where a.managerid = b.id; 就是自己这张表复制一份两个名字进行内连接的条件查询
注意事项:
- 在自连接查询中,必须要为表起别名,要不然我们不清楚所指定的条件、返回的字段,到底是哪一张表的字段。
5.5.2 联合查询
对于 union
查询,就是把多次查询的结果合并起来,形成一个新的查询结果集。
select 字段列表 from 表A ... union [ all ] select 字段列表 from 表B ....;
- 对于联合查询的多张表的 列数 必须保持一致,字段类型 也需要保持一致。
union all
会将全部的数据直接合并在一起,仅仅进行简单的合并,并未去重,union
会对合并之后的数据去重。
注意:
- 如果多条查询语句查询出来的结果,字段数量 不一致,在进行
union/union all
联合查询时,将会报错。
5.6 子查询
5.6.1 概述
⭐️ 1). 概念
SQL语句中 嵌套 SELECT
语句,称为 嵌套查询,又称 子查询。
select * from t1 where column1 = ( select column1 from t2 );
- 子查询外部的语句可以是
insert / update / delete / select
的任何一个。
⭐️ 2). 分类
根据子查询结果不同,分为:
- A. 标量子查询(子查询结果为单个值,只有一行一列)
- B. 列子查询 (子查询结果为一列)
- C. 行子查询 (子查询结果为一行)
- D. 表子查询 (子查询结果为多行多列)
根据子查询位置,分为:
- A.
where
之后 - B.
from
之后 - C.
select
之后
5.6.2 标量子查询
子查询返回的结果是单个值(数字、字符串、日期等),最简单的形式,这种子查询称为 标量子查询。
- 常用的操作符:
=
<>
>
>=
<
<=
例子: 查询 “销售部” 的所有员工信息
- ①. 查询 “销售部” 部门ID
- ②. 根据 “销售部” 部门ID, 查询员工信息
select * from emp where dept_id = (select id from dept where name = '销售部');
5.6.3 列子查询
子查询返回的结果是一列(可以是多行),这种子查询称为 列子查询。
- 常用的操作符:
in
、not in
、any
、some
、all
案例:
查询 “销售部” 和 “市场部” 的所有员工信息
- ①. 查询 “销售部” 和 “市场部” 的部门ID
- ②. 根据部门ID, 查询员工信息
select * from emp where dept_id in (select id from dept where name = '销售部' or name = '市场部');
5.6.4 行子查询
子查询返回的结果是一行(可以是多列),这种子查询称为行子查询。
- 常用的操作符:
=
、<>
、in
、not in
案例:查询与 “张无忌” 的薪资及直属领导相同的员工信息 ;
- ①. 查询 “张无忌” 的薪资及直属领导
- ②. 查询与 “张无忌” 的薪资及直属领导相同的员工信息 ;
select * from emp where (salary,managerid) = (select salary, managerid from emp where name = '张无忌');
5.6.5 表子查询
子查询返回的结果是 多行多列,这种子查询称为 表子查询。
- 常用的操作符:
in
- 经常出现在
from
之后,把表子查询返回的结果作为临时表,再和其他表进行联查操作。
案例::查询与 “鹿杖客” , “宋远桥” 的职位和薪资相同的员工信息
- ①. 查询 “鹿杖客” , “宋远桥” 的职位和薪资
- ②. 查询与 “鹿杖客” , “宋远桥” 的职位和薪资相同的员工信息
select * from emp where (job,salary) in ( select job, salary from emp where name = '鹿杖客' or name = '宋远桥' );
# 1、内连接 ## 1.1). 隐式内连接 select 字段列表 from 表1 , 表2 where 条件 ... ; ## 1.2). 显式内连接 select 字段列表 from 表1 [ inner ] join 表2 on 连接条件 ... ;# 2、外连接 ## 2.1). 左外连接 select 字段列表 from 表1 left [ outer ] join 表2 on 条件 ... ; ## 2.2). 右外连接 select 字段列表 from 表1 right [ outer ] join 表2 on 条件 ... ;# 3、自连接 select 字段列表 from 表A 别名A join 表A 别名B on 条件 ... ;# 4、联合查询 select 字段列表 from 表A ... union [ all ] # 注意是否需要去重 select 字段列表 from 表B ....;# 5、子查询/嵌套查询 select * from t1 where column1 = ( select column1 from t2 ); ## 5.1). 标量子查询 常用的操作符:= <> > >= < <= ## 5.2). 列子查询 常用的操作符:in 、not in 、 any 、some 、 all ## 5.3). 行子查询 常用的操作符:= 、<> 、in 、not in ## 5.4). 表子查询 常用的操作符:in
六、事务
6.1 事务简介
事务 是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
例子:就比如: 张三给李四转账1000块钱,张三银行账户的钱减少1000,而李四银行账户的钱要增加1000。 这一组操作就必须在一个事务的范围内,要么都成功,要么都失败。
- 正常情况: 转账这个操作, 需要分为以下这么三步来完成 , 三步完成之后, 张三减少1000, 而李四增加1000, 转账成功。
- 异常情况: 转账这个操作, 也是分为以下这么三步来完成 , 在执行第三步是报错了, 这样就导致张三减少1000块钱, 而李四的金额没变, 这样就造成了数据的不一致, 就出现问题了。
- 为了解决上述的问题,就需要通过数据的事务来完成,我们只需要在业务逻辑执行之前开启事务,执行完毕后提交事务。如果执行过程中报错,则回滚事务,把数据恢复到事务开始之前的状态。
注意: 默认MySQL的事务是自动提交的,也就是说,当执行完一条DML语句时,MySQL会立即隐式的提交事务。
6.2 事务操作
数据准备:
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);
6.2.1 未控制事务
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). 测试异常情况
-- 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 = '李四';
- 我们把数据都恢复到2000, 然后再次一次性执行上述的SQL语句(出错了… 这句话不符合SQL语法,执行就会报错),检查最终的数据情况, 发现数据在操作前后不一致了。
6.2.2 控制事务一
⭐️ 1). 查看/设置事务提交方式
select @@autocommit ; set @@autocommit = 0 ; -- 设置为手动提交,业务完成后也执行 commit 指令.
⭐️ 2). 提交事务 commit;
⭐️ 3). 回滚事务 rollback;
注意:上述的这种方式,我们是修改了事务的自动提交行为, 把默认的自动提交修改为了手动提交, 此时我们执行的DML语句都不会提交, 需要手动的执行 commit
进行提交。
6.2.3 控制事务二
⭐️ 1). 开启事务 start transaction 或 begin ;
⭐️ 2). 提交事务 commit;
⭐️ 3). 回滚事务 rollback;
6.3 事务四大特性
- 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
- 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
上述就是事务的四大特性,简称 ACID。
6.4 并发事务问题
⭐️ 1). 赃读:一个事务读到另外一个事务还没有提交的数据。
- 比如A读取到了B未提交的数据。
# 先设置隔离级别 set session transaction isolation level read uncommitted ;
read committed
可以解决脏读!
# 修改隔离级别 set session transaction isolation level read committed ;
⭐️ 2). 不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读。
- 事务A两次读取同一条记录,但是读取到的数据却是不一样的。
repeatable read
可以解决不可重复读!
# 修改隔离级别 set session transaction isolation level repeatable read ;
⭐️ 3). 幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了 “幻影”。
repeatable read
可以解决幻读!
# 修改隔离级别 set session transaction isolation level serializable ;
6.5 事务隔离级别
为了解决并发事务所引发的问题,在数据库中引入了事务隔离级别。主要有以下几种:
- 从上到下隔离级别越来越高,但性能越来越差。
⭐️ 1). 查看事务隔离级别 select @@transaction_isolation;
⭐️ 2). 设置事务隔离级别
set [ session | global ] transaction isolation level { read uncommitted | read committed | repeatable read | serializable }
注意:事务隔离级别越高,数据越安全,但是性能越低。
- 一般使用默认级别不会去修改!
# 1). 开启事务 start transaction 或 begin ;# 2). 提交/回滚事务 commit / rollback;# 3). 事务的四大特性(ACID) 原子性(Atomicity)、 一致性(Consistency)、 隔离性(Isolation)、 持久性(Durability)# 4). 并发事务问题 脏读、不可重复读、幻读# 5). 事务隔离级别 read uncommitted 、 read committed 、 repeatable read 、 serializable-- 设置事务隔离级别 set [ session | global ] transaction isolation level { read uncommitted | read committed | repeatable read | serializable }
七 存储引擎
1.1 MySQL 体系结构
1). 连接层
- 最上层是一些 客户端 和 链接服务,包含本地
sock
通信和大多数基于 客户端/服务端 工具实现的类似于TCP/IP
的通信。
- 主要完成一些类似于 连接处理、授权认证、及相关的安全方案。
- 在该层上引入了 线程池 的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于
SSL
的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
2). 服务层
- 第二层架构主要完成大多数的 核心服务功能,如 SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。
- 所有跨存储引擎的功能也在这一层实现,如
过程
、函数
等。
- 在该层,服务器会解析查询并创建相应的内部 解析树,并对其完成相应的优化如确定表的查询的顺序,是否利用索引等,最后生成相应的执行操作。
- 如果是
select
语句,服务器还会查询内部的缓存,如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
3). 引擎层
- 存储引擎层, 存储引擎 真正的负责了MySQL中数据的存储和提取,服务器通过
API
和存储引擎
进行通信。 - 不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。
- 数据库中的 索引 是在存储引擎层实现的。
4). 存储层
- 数据存储层, 主要是将数据(如:
redolog
、undolog
、数据
、索引
、二进制日志
、错误日志
、查询日志
、慢查询日志
等)存储在文件系统之上,并完成与存储引擎的交互。
和其他数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎上,插件式的存储引擎架构,将查询处理和其他的系统任务以及数据的存储提取分离。
这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
1.2 存储引擎介绍
大家可能没有听说过存储引擎,但是一定听过引擎这个词,引擎就是发动机,是一个机器的核心组件。
- 比如,对于舰载机、直升机、火箭来说,他们都有各自的引擎,是他们最为核心的组件。
- 而我们在选择引擎的时候,需要在合适的场景,选择合适的存储引擎,就像在直升机上,我们不能选择舰载机的引擎一样。
而对于存储引擎,也是一样,他是mysql数据库的核心,我们也需要在合适的场景选择合适的存储引擎。接下来就来介绍一下存储引擎。
- 存储引擎就是
存储数据
、建立索引
、更新/查询数据
等技术的实现方式 。 - 存储引擎是 基于表的,而不是基于库的,所以存储引擎也可被称为 表类型。我们可以在创建表的时候,来指定选择的存储引擎,如果没有指定将自动选择默认的存储引擎。(一个数据库有多个表,可以有多个存储引擎。)
1). 建表时指定存储引擎
create table 表名(字段1 字段1类型 [ comment 字段1注释 ] ,......字段n 字段n类型 [ comment 字段n注释 ] ) engine = InnoDB [ comment 表注释 ] ;
2). 查询当前数据库支持的存储引擎
show engines;
例子:
--创建表 my_memory , 指定 Memory 存储引擎 create table my_memory(id int,name varchar(10) ) engine = Memory ;
1.3 存储引擎特点
下面介绍较为重要的三种存储引擎 InnoDB
、MyISAM
、Memory
的特点。
⭐️ 1.3.1、InnoDB
1). 介绍
InnoDB
是一种兼顾 高可靠性 和 高性能 的通用存储引擎,在 MySQL 5.5 之后,InnoDB
是默认的
MySQL 存储引擎。
2). 特点
- DML操作遵循
ACID
模型,支持事务; - 行级锁,提高并发访问性能;
- 支持 外键
FOREIGN KEY
约束,保证数据的 完整性 和 正确性;
3). 文件
xxx.ibd
:xxx
代表的是表名,innoDB
引擎的每张表都会对应这样一个表空间文件,存储该表的表结构(frm
-早期的 、sdi
-新版的)、数据 和 索引。- 参数:
innodb_file_per_table
# 模糊匹配 show variables like 'innodb_file_per_table';
-
如果该参数开启,代表对于
InnoDB
引擎的表,每一张表都对应一个ibd
文件。
- 我们直接打开MySQL的数据存放目录:
C:\ProgramData\MySQL\MySQL Server 8.0\Data
, (ProgramData
是隐藏文件夹,设置查看隐藏文件), - 这个目录下有很多文件夹,不同的文件夹代表不同的数据库,我们直接打开
rmzh
文件夹。 - 可以看到里面有很多的
ibd
文件,每一个ibd
文件就对应一张表,比如:- 我们有一张表
account
,就有这样的一个account.ibd
文件,而在这个ibd
文件中不仅存放表结构
、数据
,还会存放该表对应的索引信息
。 - 而该文件是基于二进制存储的,不能直接基于记事本打开,我们可以在
cmd
命令行使用mysql
提供的一个指令ibd2sdi
,通过该指令就可以从ibd
文件中提取sdi
信息,而sdi
数据字典信息中就包含该表的表结构。
- 我们有一张表
4). 逻辑存储结构
- 表空间 :
InnoDB
存储引擎逻辑结构的最高层,ibd
文件其实就是表空间文件,在表空间中可以包含多个Segment
段。
- 段 : 表空间是由各个段组成的, 常见的段有
数据段
、索引段
、回滚段
等。InnoDB
中对于段的管理,都是引擎自身完成,不需要人为对其控制,一个段中包含多个区。
- 区 : 区是表空间的单元结构,每个区的大小为
1M
。 默认情况下,InnoDB
存储引擎页大小为16K
, 即一个区中一共有64
个连续的页。
- 页 : 页是组成区的最小单元,页也是 InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为
16KB
。为了保证页的连续性,InnoDB
存储引擎每次从磁盘申请4-5
个区。
- 行 :
InnoDB
存储引擎是 面向行 的,也就是说数据是按行进行存放的,在每一行中除了定义表时所指定的字段以外,还包含两个隐藏字段(后面会详细介绍)。
⭐️ 1.3.2、MyISAM
1). 介绍
MyISAM
是MySQL早期的默认存储引擎。
2). 特
- 不支持 事务,不支持 外键
- 支持表锁,不支持 行锁
- 访问速度快
3). 文件
xxx.sdi
:存储 表结构 信息 (json
格式的)xxx.MYD
: 存储 数据xxx.MYI
: 存储 索引
- 可以用记事本打开
xxx.sdi
文件,可以格式化查看:
⭐️ 1.3.3、Memory
1). 介绍
Memory
引擎的表数据时存储在 内存 中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为 临时表 或 缓存 使用。
2). 特点
- 内存存放 (访问速度是非常快的)
hash
索引(默认)
3).文件
xxx.sdi
:存储 表结构 信息
⭐️ 1.3.4、区别及特点
面试题:InnoDB
引擎与MyISAM
引擎的区别 ?
- ①.
InnoDB引
擎, 支持事务, 而MyISAM
不支持。 - ②.
InnoDB
引擎, 支持行锁和表锁, 而MyISAM
仅支持表锁, 不支持行锁。 - ③.
InnoDB
引擎, 支持外键, 而MyISAM
是不支持的。
1.4 存储引擎选择
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。
InnoDB
: 是Mysql的默认存储引擎,支持事务
、外键
。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,那么InnoDB
存储引擎是比较合适的选择。(大部分使用该存储引擎!!!)
MyISAM
: 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。(被MongoDB
取代了!)
MEMORY
:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY
的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性。(被redis
取代了!)
# 1. 体系结构 连接层、服务层、引擎层、存储层# 2、存储引擎简介 show engines ; # 查询当前数据库支持的存储引擎 create table XXXX (……) engine = InnoDB ;# 3、存储引擎特点 InnoDB 与 MyISAM 区别: 事务、外键、行级锁# 存储引擎应用场景 InnoDB: 存储业务系统中对事务、数据完整性要求较高的核心数据。 MyISAM: 存储业务系统的非核心事务。
八、索引
2.1 索引概述
⭐️ 介绍
索引( index
)是帮助MySQL 高效获取数据 的 数据结构(有序)
。
- 在数据之外,数据库系统还维护着满足 特定查找算法 的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
例子:表结构及其数据如下:
假如我们要执行的SQL语句为 : select * from user where age = 45;
1). 无索引情况:全表扫描
- 在无索引情况下,就需要从第一行开始扫描,一直扫描到最后一行,我们称之为 全表扫描,性能很低。
2). 有索引情况
- 如果我们针对于这张表建立了索引,假设索引结构就是二叉树,那么也就意味着,会对age这个字段建立一个二叉树的索引结构。
- 此时我们在进行查询时,只需要扫描三次就可以找到数据了,极大的提高的查询的效率。
备注: 这里我们只是假设索引的结构是二叉树,介绍一下索引的大概原理,只是一个示意图,并不是索引的真实结构,索引的真实结构,后面会详细介绍。
⭐️ 特点
一般情况下是可以忽略以上劣势的:1、现在磁盘比较便宜;2、对一个正常的业务系统增删改的比例很小,主要是查询。
2.2 索引结构
⭐️ 概述
- MySQL的索引是在 存储引擎层 实现的,不同的存储引擎有不同的索引结构,主要包含以下几种:
- 上述是MySQL中所支持的所有的索引结构,接下来,我们再来看看不同的存储引擎对于索引结构的支持情况。
注意: 我们平常所说的索引,如果没有特别指明,都是指B+树结构组织的索引。
⭐️ 二叉树
- 假如说MySQL的索引结构采用二叉树的数据结构,比较理想的结构如下:
- 如果主键是顺序插入的,则会形成一个单向链表,结构如下:
- 所以,如果选择二叉树作为索引结构,会存在以下缺点:
- 顺序插入时,会形成一个链表,查询性能大大降低。
- 大数据量情况下,层级较深,检索速度慢。
此时大家可能会想到,我们可以选择红黑树,红黑树是一颗自平衡二叉树,那这样即使是顺序插入数据,最终形成的数据结构也是一颗平衡的二叉树,结构如下:
- 但是,即使如此,由于红黑树也是一颗二叉树,所以也会存在一个缺点:
- 大数据量情况下,层级较深,检索速度慢。
所以,在MySQL的索引结构中,并没有选择二叉树或者红黑树,而选择的是 B+Tree
,那么什么是 B+Tree
呢?在详解 B+Tree
之前,先来介绍一个B-Tree
。
B-Tree
,B树是一种 多叉路衡查找树,相对于二叉树,B树每个节点可以有多个分支,即多叉。
知识小贴士: 树的度数指的是一个节点的子节点个数。
- 以一颗最大度数(max-degree)为5( 5阶 )的
b-tree
为例,那这个B树每个节点最多存储 4个key
,5个指针:
例子
- 特点:
- 5阶的B树,每一个节点最多存储 4个
key
,对应5个指针。 - 一旦节点存储的
key
数量到达5,就会裂变,中间元素向上分裂。 - 在B树中,非叶子节点和叶子节点都会存放数据。
- 5阶的B树,每一个节点最多存储 4个
⭐️ B+Tree
B+Tree
是 B-Tree
的变种,
- 我们以一颗最大度数(max-degree)为4(4阶)的
b+tree
为例,来看一下其结构示意图:
- 我们可以看到,两部分:
- 绿色框 框起来的部分,是
索引
部分,仅仅起到索引数据的作用,不存储数据。 - 红色框 框起来的部分,是
数据存储
部分,在其 叶子节点中要存储具体的数据。
- 绿色框 框起来的部分,是
- 最终我们看到,
B+Tree
与B-Tree
相比,主要有以下三点区别:- 所有的数据都会出现在叶子节点。
- 叶子节点形成一个单向链表。
- 非叶子节点仅仅起到索引数据作用,具体的数据都是在叶子节点存放的。
上述我们所看到的结构是标准的 B+Tree
的数据结构,接下来,我们再来看看MySQL中优化之后的 B+Tree
。
- MySQL索引数据结构对经典的
B+Tree
进行了 优化。 - 在原
B+Tree
的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有 顺序指针 的B+Tree
,提高区间访问的性能,利于排序。
⭐️ Hash
MySQL中除了支持 B+Tree
索引,还支持一种索引类型— Hash
索引
1). 结构
- 哈希索引就是采用一定的
hash
算法,将 键值 换算成新的hash值
,映射到对应的槽位上,然后存储在hash表
中。
- 如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了
hash冲突
(也称为hash碰撞
),可以通过链表来解决。
2). 特点
- A.
Hash索引
只能用于 对等比较 (=
,in
),不支持范围查询(between
,>
,<
,...
) - B. 无法利用索引完成排序操作
- C. 查询效率高,通常(不存在hash冲突的情况)只需要一次检索就可以了,效率通常要高于
B+tree
索引
3). 存储引擎支持
- 在MySQL中,支持
hash索引
的是Memory存储引擎
。 - 而
InnoDB
中具有 自适应hash 功能,hash索引
是InnoDB存储引擎
根据B+Tree
索引在指定条件下自动构建的
思考题: 为什么 InnoDB存储引擎
选择使用 B+tree索引
结构?
A. 相对于二叉树,层级更少,搜索效率高; B. 对于 B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低; C. 相对 Hash索引 ,B+tree 支持 范围匹配 及 排序 操作;
2.3 索引分类
在MySQL数据库,将索引的具体类型主要分为以下几类:主键索引
、唯一索引
、常规索引
、全文索引
。
⭐️ 聚集索引&二级索引
- 而在
InnoDB存储引擎
中,根据索引的存储形式,又可以分为以下两种:
聚集索引下挂行数据,二级索引不挂行数据。
聚集索引选取规则:
- 如果
存在主键
,主键索引
就是聚集索引
。 - 如果
不存在主键
,将使用 第一个唯一(UNIQUE
)索引作为聚集索引
。 - 如果表没有主键,或没有合适的唯一索引,则
InnoDB
会自动生成一个rowid
作为隐藏的聚集索引
。
聚集索引和二级索引的具体结构如下:
- 聚集索引的叶子节点下挂的是 这一行的数据 。
- 二级索引的叶子节点下挂的是该字段值对应的 主键值。
接下来,我们来分析一下,当我们执行如下的SQL语句时,具体的查找过程是什么样子的。
具体过程如下:
- ①. 由于是根据 name字段 进行查询,所以先根据 name='Arm' 到 name字段 的 二级索引 中进行匹配查找。但是在二级索引中只能查找到 Arm 对应的主键值 10。
- ②. 由于查询返回的数据是 * ,所以此时,还需要根据主键值10,到 聚集索引 中查找10对应的记录,最终找到10对应的 行row。
- ③. 最终拿到这一行的数据,直接返回即可。
回表查询: 这种先到 二级索引 中查找数据,找到 主键值
,然后再到 聚集索引 中根据 主键值
,获取数据
的方式,就称之为回表查询。
思考题:
以下两条SQL语句,那个执行效率高? 为什么?
- A.
select * from user where id = 10 ;
- B.
select * from user where name = 'Arm' ;
备注: id为主键,name字段创建的有索引;
解答:A 语句的执行性能要 高于 B 语句。
因为A语句直接走聚集索引,直接返回数据。 而B语句需要先查询 name 字段的二级索引,然后再查询聚集索引,也就是需要进行回表查询。
思考题:InnoDB
主键索引 的B+tree
高度为多高呢?
假设: 一行数据大小为 1k,一页(16k)中可以存储 16行 这样的数据。InnoDB 的指针占用 6个字节的空间,主键即使为 bigint ,占用字节数为8。 高度为2:n * 8 + (n + 1) * 6 = 16*1024 , 算出 n 约为 1170 1171* 16 = 18736 也就是说,如果树的高度为2,则可以存储 18000 多条记录。 高度为3:1171 * 1171 * 16 = 21939856 也就是说,如果树的高度为3,则可以存储 2200w 左右的记录。
2.4 索引语法
⭐️ 1)、创建索引
create [ unique | fulltext ] index index_name on table_name (index_col_name,... /*一个索引可以关联多个字段*/) ;
⭐️ 2)、查看索引
show index from table_name;
⭐️ 3)、删除索引
drop index index_name on table_name;
例
create table tb_user( id int primary key comment '主键',name varchar(50) not null comment '用户名',phone varchar(11) not null comment '手机号',email varchar(100) comment '邮箱',profession varchar(11) comment '专业',age tinyint unsigned comment '年龄',gender char(1) comment '性别 , 1: 男, 2: 女',status char(1) comment '状态',createtime datetime comment '创建时间' ) comment '系统用户表'; INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('吕布', '17799990000', 'lvbu666@163.com', '软件工程', 23, '1', '6', '2001-02-02 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('曹操', '17799990001', 'caocao666@qq.com', '通信工程', 33, '1', '0', '2001-03-05 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('赵云', '17799990002', '17799990@139.com', '英语', 34, '1', '2', '2002-03-02 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('孙悟空', '17799990003', '17799990@sina.com', '计算机科学与技术', 54, '1', '0', '2001-07-02 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('花木兰', '17799990004', '19980729@sina.com', '软件工程', 23, '2', '1', '2001-04-22 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('大乔', '17799990005', 'daqiao666@sina.com', '舞蹈', 22, '2', '0', '2001-02-07 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('露娜', '17799990006', 'luna_love@sina.com', '应用数学', 24, '2', '0', '2001-02-08 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('程咬金', '17799990007', 'chengyaojin@163.com', '化工', 38, '1', '5', '2001-05-23 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('项羽', '17799990008', 'xiaoyu666@qq.com', '金属材料', 43, '1', '0', '2001-09-18 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('白起', '17799990009', 'baiqi666@sina.com', '计算机科学与技术', 27, '1', '2', '2001-08-16 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('韩信', '17799990010', 'hanxin520@163.com', '计算机科学与技术', 27, '1', '0', '2001-06-12 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('荆轲', '17799990011', 'jingke123@163.com', '会计', 29, '1', '0', '2001-05-11 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('兰陵王', '17799990012', 'lanlinwang666@126.com', '生物医学工程', 44, '1', '1', '2001-04-09 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('狂铁', '17799990013', 'kuangtie@sina.com', '应用数学', 43, '1', '2', '2001-04-10 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('貂蝉', '17799990014', '84958948374@qq.com', '软件工程', 40, '2', '3', '2001-02-12 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('妲己', '17799990015', '2783238293@qq.com', '软件工程', 31, '2', '0', '2001-01-30 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('芈月', '17799990016', 'xiaomin2001@sina.com', '工业经济', 35, '2', '0', '2000-05-03 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('嬴政', '17799990017', '8839434342@qq.com', '化工', 38, '1', '1', '2001-08-08 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('狄仁杰', '17799990018', 'jujiamlm8166@163.com', '国际贸易', 30, '1', '0', '2007-03-12 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('安琪拉', '17799990019', 'jdodm1h@126.com', '城市规划', 51, '2', '0', '2001-08-15 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('典韦', '17799990020', 'ycaunanjian@163.com', '城市规划', 52, '1', '2', '2000-04-12 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('廉颇', '17799990021', 'lianpo321@126.com', '土木工程', 19, '1', '3', '2002-07-18 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('后羿', '17799990022', 'altycj2000@139.com', '城市园林', 20, '1', '0', '2002-03-10 00:00:00'); INSERT INTO tb_user (name, phone, email, profession, age, gender, status, createtime) VALUES ('姜子牙', '17799990023', '37483844@qq.com', '工程造价', 29, '1', '4', '2003-05-26 00:00:00');
- A.
name
字段为姓名字段,该字段的值可能会重复,为该字段创建索引。
create index idx_user_name on tb_user(name); # 索引名命名规范为:idx+表明+字段名
- B.
phone
手机号字段的值,是非空,且唯一的,为该字段创建 唯一索引。
create unique index idx_user_phone on tb_user(phone);
- C. 为
profession
、age
、status
创建 联合索引。
create index idx_user_pro_age_sta on tb_user(profession, age, status);
- D. 为
email
建立 合适的索引 来提升查询效率。
create index idx_email on tb_user(email);
完成上述的需求之后,我们再查看 tb_user
表的所有的索引数据。
show index from tb_user;
2.5 SQL 性能分析
⭐️ 1)、SQL执行频率
- MySQL 客户端连接成功后,通过
show [session|global] status
命令可以提供服务器状态信息。 - 通过如下指令,可以查看当前数据库的
INSERT
、UPDATE
、DELETE
、SELECT
的访问频次:
-- session 是查看当前会话 ; -- global 是查询全局数据 ; show global status like 'Com_______';
Com_delete
: 删除次数Com_insert
: 插入次数Com_select
: 查询次数Com_update
: 更新次数
我们可以查看到当前数据库到底是以查询为主,还是以增删改为主,从而为数据库优化提供参考依据。
- 如果是以增删改为主,我们可以考虑不对其进行索引的优化。
- 如果是以查询为主,那么就要考虑对数据库的索引进行优化了。
那么通过查询SQL的执行频次,我们就能够知道当前数据库到底是增删改为主,还是查询为主。
- 那假如说是以查询为主,我们又该如何定位针对于那些查询语句进行优化呢?
- 次数我们可以借助于慢查询日志。
⭐️ 2)、慢查询日志
- 慢查询日志记录了所有执行时间 超过指定时间参数(
long_query_time
,单位:秒
,默认10秒
)的所有SQL语句的日志。 - MySQL的慢查询日志 默认没有开启,我们可以查看一下系统变量
slow_query_log
。
show variables like 'slow_query_log' ;
如果要开启慢查询日志,需要在MySQL的配置文件( /etc/my.cnf
)中配置如下信息:
# 开启MySQL慢日志查询开关 slow_query_log=1 # 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志 long_query_time=2
配置完毕之后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息 /var/lib/mysql/localhost-slow.log
systemctl restart mysqld
测试:
- A. 执行如下SQL语句 :
select * from tb_user; -- 这条SQL执行效率比较高, 执行耗时 0.00sec select count(*) from tb_sku; -- 由于tb_sku表中, 预先存入了1000w的记录, count一次,耗时13.35sec
- B. 检查慢查询日志 :
- 最终我们发现,在慢查询日志中,只会记录执行时间超多我们预设时间(2s)的SQL,执行较快的SQL是不会记录的。
那这样,通过慢查询日志,就可以定位出执行效率比较低的SQL,从而有针对性的进行优化。
⭐️ 3)、profile详情
show profiles
能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling
参数,能够看到当前MySQL是否支持profile
操作:
SELECT @@have_profiling ;
- 可以看到,当前MySQL是支持 profile操作的,但是开关是关闭的。可以通过set语句在
session/global
级别开启profiling
:
SET profiling = 1;
开关已经打开了,接下来,我们所执行的SQL语句,都会被MySQL记录,并记录执行时间消耗到哪儿去了。 我们直接执行如下的SQL语句:
select * from tb_user; select * from tb_user where id = 1; select * from tb_user where name = '白起'; select count(*) from tb_sku;
执行一系列的业务SQL的操作,然后通过如下指令查看指令的执行耗时:
-- 查看每一条SQL的耗时基本情况 show profiles; -- 查看指定query_id的SQL语句各个阶段的耗时情况 show profile for query query_id; -- 查看指定query_id的SQL语句CPU的使用情况 show profile cpu for query query_id;
- 查看每一条SQL的耗时情况:
- 查看指定SQL各个阶段的耗时情况 :
⭐️ 4)、explain
EXPLAIN
或者DESC
命令获取 MySQL 如何执行SELECT
语句的信息,包括在SELECT
语句执行过程中表如何连接和连接的顺序。
-- 直接在select语句之前加上关键字 explain / desc explain select 字段列表 from 表名 where 条件 ;
Explain
执行计划中各个字段的含义:
重点关注字段 type
、possible_key
、key
、key_len
以及 Extra
。
2.6 索引使用规则
⭐️ 1)、验证索引效率
在讲解索引的使用原则之前,先通过一个简单的案例,来验证一下索引,看看是否能够通过索引来提升数据查询性能。在演示的时候,我们还是使用之前准备的一张表 tb_sku
, 在这张表中准备了百万的记录。
- 这张表中
id
为主键,有主键索引,而其他字段是没有建立索引的。 我们先来查询其中的一条记录,看看里面的字段情况,执行如下SQL:(\g:下一行)
select * from tb_sku where id = 1\G;
- 可以看到即使有百万的数据,根据
id
进行数据查询,性能依然很快,因为主键id
是有索引的。 那么接下来,我们再来根据sn
字段进行查询,执行如下SQL:
select * from tb_sku where sn = '100000003145001';
- 我们可以看到根据
sn
字段进行查询,查询返回了一条数据,结果耗时6.69sec
,就是因为sn
没有索引,而造成查询效率很低。那么我们可以针对于sn
字段,建立一个索引,建立了索引之后,我们再次根据sn
进行查询,再来看一下查询耗时情况。
- 创建索引: create index idx_sku_sn on tb_sku(sn) ;
- 然后再次执行相同的SQL语句,再次查看SQL的耗时。
- 我们明显会看到,
sn
字段建立了索引之后,查询性能大大提升。建立索引前后,查询耗时都不是一个数量级的。
⭐️ 2)、最左前缀法则
如果索引了多列(联合索引),要遵守 最左前缀法则。
- 最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。
- 如果跳跃某一列,索引将会部分失效(后面的字段索引失效)。
以 tb_user
表为例,我们先来查看一下之前 tb_user
表所创建的索引。
- 在
tb_user
表中,有一个联合索引,这个联合索引涉及到三个字段,顺序分别为:profession
,age
,status
。
对于最左前缀法则指的是,查询时,最左变的列,也就是 profession
必须存在,否则索引全部失效。
而且中间不能跳过某一列,否则该列后面的字段索引将失效。
接下来,我们来演示几组案例,看一下具体的执行计划:
explain select * from tb_user where profession = '软件工程' and age = 31 and status = '0';
explain select * from tb_user where profession = '软件工程' and age = 31;
explain select * from tb_user where profession = '软件工程';
- 以上的这三组测试中,我们发现只要联合索引最左边的字段
profession
存在,索引就会生效,只不过索引的长度不同。 - 而且由以上三组测试,我们也可以推测出
profession
字段索引长度为47
、age
字段索引长度为2
、status
字段索引长度为5
。
explain select * from tb_user where age = 31 and status = '0';
explain select * from tb_user where status = '0';
- 而通过上面的这两组测试,我们也可以看到索引并未生效,原因是因为不满足最左前缀法则,联合索引最左边的列
profession
不存在。
explain select * from tb_user where profession = '软件工程' and status = '0';
- 上述的SQL查询时,存在
profession
字段,最左边的列是存在的,索引满足最左前缀法则的基本条件。 - 但是查询时,跳过了
age
这个列,所以后面的列索引是不会使用的,也就是索引部分生效,所以索引的长度就是47。
思考题:
- 当执行SQL语句:
explain select * from tb_user where age = 31 and status = '0' and profession = '软件工程';
时,是否满足最左前缀法则,走不走上述的联合索引,索引长度?
可以看到,是完全满足最左前缀法则的,索引长度54,联合索引是生效的。注意 : 最左前缀法则中指的最左边的列,是指在查询时,联合索引的最左边的字段(即是第一个字段)必须存在,与我们编写SQL时,条件编写的先后顺序无关。
⭐️ 3)、范围查询
联合索引中,出现范围查询(>
, <
),范围查询==右侧的列索引失效==。
explain select * from tb_user where profession = '软件工程' and age > 30 and status = '0';
- 当范围查询使用
>
或<
时,走联合索引了,但是索引的长度为49
,就说明范围查询右边的status
字段是没有走索引的。
explain select * from tb_user where profession = '软件工程' and age >= 30 and status = '0';
- 当范围查询使用
>=
或<=
时,走联合索引了,但是索引的长度为54
,就说明所有的字段都是走索引的。
所以,在业务允许的情况下,尽可能的使用类似于 >=
或 <=
这类的范围查询,而 避免使用 >
或 <
。
⭐️ 4)、索引失效情况
a、索引列运算
- 不要在索引列上进行运算操作, 索引将失效。
在 tb_user
表中,除了前面介绍的联合索引之外,还有一个索引,是 phone
字段的单列索引。
- 当根据
phone
字段进行 等值匹配 查询时, 索引生效。
explain select * from tb_user where phone = '17799990015';
- 当根据
phone
字段进行 函数运算 操作之后,索引失效。
explain select * from tb_user where substring(phone, 10, 2) = '15';
b、字符串不加引号
- 字符串类型字段使用时,不加引号,索引将失效。
接下来,我们通过两组示例,来看看对于字符串类型的字段,加单引号
与不加单引号
的区别:
explain select * from tb_user where profession = '软件工程' and age = 31 and status = '0'; explain select * from tb_user where profession = '软件工程' and age = 31 and status = 0;
explain select * from tb_user where phone = '17799990015'; explain select * from tb_user where phone = 17799990015;
- 经过上面两组示例,我们会明显的发现,如果字符串
不加单引号
,对于查询结果,没什么影响,但是数据库存在隐式类型转换,索引将失效。
c、模糊查询
- 如果仅仅是尾部模糊匹配,索引不会失效。如果是 头部模糊匹配,索引失效。
接下来,我们来看一下这三条SQL语句的执行效果,查看一下其执行计划:
- 由于下面查询语句中,都是根据
profession
字段查询,符合最左前缀法则,联合索引是可以生效的,我们主要看一下,模糊查询时,%
加在关键字之前,和加在关键字之后的影响。
explain select * from tb_user where profession like '软件%'; explain select * from tb_user where profession like '%工程'; explain select * from tb_user where profession like '%工%';
- 经过上述的测试,我们发现,在
like
模糊查询中,在关键字后面加%
,索引可以生效。而如果在关键字前面加了%
,索引将会失效。
d、or
连接条件
- 用
or
分割开的条件, 如果or
前的条件中的列有索引,而 后面的列中没有索引,那么涉及的索引都不会被用到。
所有索引
explain select * from tb_user where id = 10 or age = 23; explain select * from tb_user where phone = '17799990017' or age = 23;
- 由于
age
没有索引,所以即使id
、phone
有索引,索引也会失效。所以需要针对于age
也要建立索引。然后,我们可以对age
字段建立索引。
create index idx_user_age on tb_user(age);
- 最终,我们发现,当
or
连接的条件,左右两侧字段都有索引时,索引才会生效。
e、数据分布影响
- 如果MySQL评估使用索引比全表更慢,则不使用索引。
explain select * from tb_user where phone >= '17799990015'; explain select * from tb_user where phone >= '17799990000';
经过测试我们发现,相同的SQL语句,只是传入的字段值不同,最终的执行计划也完全不一样,这是为什么呢?
- 就是因为MySQL在查询时,会评估使用索引的效率与走全表扫描的效率,
- 如果走全表扫描更快,则放弃索引,走全表扫描。
- 因为索引是用来索引少量数据的,如果通过索引查询返回大批量的数据,则还不如走全表扫描来的快,此时索引就会失效。
接下来,我们再来看看 is null
与 is not null
操作是否走索引。
explain select * from tb_user where profession is null; explain select * from tb_user where profession is not null;
- 接下来,我们做一个操作将
profession
字段值全部更新为null
。
update tb_user set profession = null;
- 然后,再次执行上述的两条SQL,查看SQL语句的执行计划。
最终我们看到,一模一样的SQL语句,先后执行了两次,结果查询计划是不一样的,为什么会出现这种现象,这是和数据库的数据分布有关系。
- 查询时MySQL会评估,走索引快,还是全表扫描快,如果全表扫描更快,则放弃索引走全表扫描。
- 因此,
is null
、is not null
是否走索引,得具体情况具体分析,并不是固定的。
⭐️ 5)、SQL提示
- 目前
tb_user
表的数据情况如下:
- 索引情况如下:
把上述的 idx_user_age
这个之前测试使用过的索引直接删除。 drop index idx_user_age on tb_user;
- A. 执行SQL : 查询走了联合索引。
explain select * from tb_user where profession = '软件工程' ;
- B. 执行SQL,创建
profession
的单列索引:
create index idx_user_pro on tb_user(profession);
- C. 创建单列索引后,再次执行A中的SQL语句,查看执行计划,看看到底走哪个索引。
测试结果,我们可以看到:
possible_keys
中idx_user_pro_age_sta
,idx_user_pro
这两个索引都可能用到,最终MySQL选择了idx_user_pro_age_sta
索引。- 这是MySQL自动选择的结果。
那么,我们能不能在查询的时候,自己来指定使用哪个索引呢?
- 答案是肯定的,此时就可以借助于MySQL的SQL提示来完成。 接下来,介绍一下SQL提示。
SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。
- 1).
use index
: 建议MySQL使用哪一个索引完成此次查询(仅仅是建议,mysql内部还会再次进行评估)。
explain select * from tb_user use index(idx_user_pro) where profession = '软件工程';
- 2).
ignore index
: 忽略指定的索引。
explain select * from tb_user ignore index(idx_user_pro) where profession = '软件工程';
- 3).
force index
: 强制使用索引。
explain select * from tb_user force index(idx_user_pro) where profession = '软件工程';
⭐️ 6)、覆盖索引
- 尽量使用覆盖索引,减少
select *
。 - 那么什么是覆盖索引呢?
- 覆盖索引是指 查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到 。
接下来,我们来看一组SQL的执行计划,看看执行计划的差别,然后再来具体做一个解析。
explain select id, profession from tb_user where profession = '软件工程' and age = 31 and status = '0' ; explain select id,profession,age, status from tb_user where profession = '软件工程' and age = 31 and status = '0' ; explain select id,profession,age, status, name from tb_user where profession = '软件工程' and age = 31 and status = '0' ; explain select * from tb_user where profession = '软件工程' and age = 31 and status = '0';
- 从上述的执行计划我们可以看到,这五条SQL语句的执行计划前面所有的指标都是一样的,看不出来差异。
- 但是此时,我们主要关注的是后面的
Extra
,前面三条SQL的结果为Using where
;Using Index
; - 而后面两条SQL的结果为:
Using index condition
。
- 因为,在 tb_user表中有一个联合索引
idx_user_pro_age_sta
,该索引关联了三个字段profession
、age
、status
,而这个索引也是一个二级索引,所以叶子节点下面挂的是这一行的主键id
。 - 所以当我们查询返回的数据在
id
、profession
、age
、status
之中,则直接走二级索引直接返回数据了。 - 而我们如果一直使用
select *
查询返回所有字段值,很容易就会造成回表查询(除非是根据主键查询,此时只会扫描聚集索引)。
为了更清楚的理解,什么是覆盖索引,什么是回表查询,我们一起再来看下面的这组SQL的执行过程。
- A. 表结构及索引示意图:
id
是主键,是一个聚集索引,叶子结点挂的是这一行数据。name
字段建立了普通索引,是一个二级索引(辅助索引),叶子结点挂的是 索引值 和 id值。
- B. 执行SQL :
select * from tb_user where id = 2;
- 根据
id
查询,直接走聚集索引查询,一次索引扫描,直接返回数据,性能高。
- C. 执行SQL:
selet id,name from tb_user where name = 'Arm';
- 虽然是根据
name
字段查询,查询二级索引,但是由于查询返回在字段为id
,name
,在name
的二级索引中,这两个值都是可以直接获取到的,因为覆盖索引,所以不需要回表查询,性能高。
- D. 执行SQL:
selet id,name,gender from tb_user where name = 'Arm';
- 由于在
name
的二级索引中,不包含gender
,所以,需要两次索引扫描,也就是需要回表查询,性能相对较差一点
- 由于在
思考题:
一张表, 有四个字段( id, username, password, status), 由于数据量大, 需要对以下SQL语句进行优化, 该如何进行才是最优方案:
select id,username,password from tb_user where username = 'itcast';
答案: 针对于 username, password 建立联合索引, sql为: create index idx_user_name_pass on tb_user(username,password); 这样可以避免上述的SQL语句,在查询的过程中,出现回表查询。
⭐️ 7)、前缀索引
当字段类型为字符串(varchar
,text
,longtext
等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO, 影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。
-
1). 语法
create index idx_xxxx on table_name(column(n)) ;
示例:
- 为 tb_user 表的
email
字段,建立长度为5
的前缀索引。
create index idx_email_5 on tb_user(email(5));
2). 前缀长度
- 可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和 数据表的记录总数 的比值,索引选择性越高则查询效率越高, 唯一索引的选择性是
1
,这是最好的索引选择性,性能也是最好的。
select count(distinct email) / count(*) from tb_user ; select count(distinct substring(email,1,5)) / count(*) from tb_user ;
-
3). 前缀索引的查询流程
- 回表查询,通过
id
拿到这一行数据row
时,不会直接返回,还要对比email
的值是不是自己传入的(因为刚刚只比较了前5个);此条对比完成后,还要在辅助索引内往下查询下一行数据。
⭐️ 8)、单列索引与联合索引
- 单列索引:即一个索引只包含单个列。
- 联合索引:即一个索引包含了多个列。
我们先来看看 tb_user 表中目前的索引情况:
- 在查询出来的索引中,既有单列索引,又有联合索引。
接下来,我们来执行一条SQL语句,看看其执行计划:
explain select id, phone, name from tb_user where phone = '17799990010' and name = '韩信' ;
通过上述执行计划我们可以看出来,在 and
连接的两个字段 phone
、name
上都是有单列索引的,但是最终mysql 只会选择一个索引,也就是说,只能走一个字段的索引,此时是会==回表查询==的。
紧接着,我们再来创建一个 phone
和 name
字段的联合索引来查询一下执行计划
create unique index idx_user_phone_name on tb_user(phone,name);
此时,查询时,就走了联合索引,而在联合索引中包含 phone
、name
的信息,在叶子节点下挂的是对应的主键 id
,所以查询是==无需回表查询==的。
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。
如果查询使用的是联合索引,具体的结构示意图如下:
2.7 索引设计原则
- 1). 针对于 数据量较大(超过100万) ,且 查询比较频繁 的表建立索引。
- 2). 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引(尽量使用联合索引)。
- 3). 尽量选择 区分度高的列作为索引,尽量建立 唯一索引,区分度越高,使用索引的效率越高。
- 4). 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立 前缀索引。
- 5). 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以 覆盖索引,节省存储空间,避免回表,提高查询效率。
- 6). 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。(只建立有必要的索引,没必要的索引尽量不要创建)
- 7). 如果索引列不能存储 NULL 值,请在创建表时使用 NOT NULL 约束它。当优化器知道每列是否包含NULL 值时,它可以更好地确定哪个索引最有效地用于查询。
2.8总结
# 1、索引概述 索引是高效获取数据的(有序)数据结构;# 2、索引结构 B+Tree /*(所有的数据都会出现在叶子节点,并且叶子结点形成了一个双向链表)*/ Hash /*(Memory存储引擎当中支持的索引结构,就是一个哈希表【只支持精确匹配,不支持范围查询及排序】)*/# 3、索引分类 主键索引、唯一索引、常规索引、全文索引 聚集索引、二级索引# 4、索引语法 ## 4.1) 创建索引 create [ unique | fulltext ] index index_name on table_name (index_col_name,... /*一个索引可以关联多个字段*/) ; ## 4.2) 查看索引 show index from table_name; ## 4.3) 删除索引 drop index index_name on table_name;# 5、SQL 性能分析 执行频次、慢查询日志、profile、explain(/*使用最多*/) show global status like 'Com_______'; // 查看SQL执行频率 show variables like 'slow_query_log'; // 查看慢查询日志 SELECT @@have_profiling ; // profile详情 explain select 字段列表 from 表名 where 条件 ; // 直接在select语句之前加上关键字 explain / desc# 6、索引使用 联合索引 -- 最左前缀法则;>=、<= 索引失效 -- 函数运算、字符串不加引号、%模糊查询、or连接条件、数据分布影响 SQL 提示 -- use、ignore、force 覆盖索引 -- 需要返回的列,在该索引中已经全部能够找到,不需要回表查询(二级索引 —> id —> 聚集索引查找) 前缀索引 -- 字符串长度较长、大文本字段 单列/联合索引 -- 推荐使用联合索引,性能高、也能避免回表查询 # 7、索引设计原则 哪些表 -- 数据量大、查询频次高 哪些字段 -- where、order by、group by 建立什么样的索引 -- 唯一索引、联合索引、前缀索引