小黑子—MySQL数据库:第二章 - 进阶篇

MySQL数据库入门2.0

  • MySQL进阶篇
    • 1. MySQL体系结构
    • 2. 存储引擎
      • 2.1 InnoDB 存储引擎
      • 2.2 MyISAM 存储引擎
      • 2.3 Memory 存储引擎
      • 2.4 存储引擎选择
      • 2.5 MySQL安装Linux版本
    • 3. 索引
      • 3.1 索引结构
        • 3.1.1 B tree
        • 3.1.2 B+ tree
        • 3.1.3 Hash
      • 3.2 索引分类
        • 3.2.1 思考题
      • 3.3 索引语法
      • 3.4 SQL性能分析
        • 3.4.1 SQL的执行频率
        • 3.4.2 慢查询日志
        • 3.4.3 show profiles
        • 3.4.4 explain执行计划
      • 3.5 索引使用
        • 3.5.1 验证索引效率
        • 3.5.2 最左前缀法则
        • 3.5.3 索引失效的情况
          • 3.5.3 - I 情况一
          • 3.5.3 - II 情况二
        • 3.5.4 SQL提示
        • 3.5.5 覆盖索引 & 回表查询
        • 3.5.6 前缀索引
        • 3.5.7 单列 & 联合索引
      • 3.6 索引设计原则
      • 3.7 小结
    • 4. SQL优化
      • 4.1 插入数据 insert优化
      • 4.2 主键优化
      • 4.3 order by优化
      • 4.4 group by优化
      • 4.5 limit优化
      • 4.6 count优化
      • 4.7 update优化
      • 4.8 优化小结
    • 5. 视图
      • 5.1 视图基本语法
      • 5.2 视图检查选项
        • 5.2.1 cascaded (级联)
        • 5.2.2 local (本地)
      • 5.3 视图的更新
      • 5.4 视图的作用
      • 5.5 视图案例
    • 6. 存储过程
      • 6.1 存储过程基本语法
      • 6.2 存储过程变量
        • 6.2.1 系统变量
        • 6.2.2 用户定义变量
        • 6.2.3 局部变量
      • 6.3 if条件判断
      • 6.4 参数
      • 6.5 case
      • 6.6 循环
        • 6.6.1 while 循环
        • 6.6.2 repeat 循环
        • 6.6.3 loop 循环
      • 6.7 cursor 游标
      • 6.8 handler 条件处理程序
      • 6.9 存储函数
    • 7. 触发器
      • 7.1 insert 类型
      • 7.2 update 类型
      • 7.3 delete 类型
      • 7.4 视图&存储过程&触发器小结
  • 8. InnoDB 引擎
    • 8.1 逻辑存储结构
    • 8.2 架构
      • 8.2.1 内存结构
      • 8.2.2 磁盘结构
      • 8.2.3 后台线程
    • 8.3 事务原理
      • 8.3.1 redo log
      • 8.3.2 undo log
    • 8.4 MVCC(多版本并发控制)
      • 8.4.1 隐藏字段
      • 8.4.2 undo log版本链
      • 8.4.3 ReadView介绍
      • 8.4.4 原理分析
        • 8.4.4 - I RC级别
        • 8.4.4 - II RR级别
    • 8.5 InnoDB小结
  • 9. MySQL管理
    • 9.1 系统数据库
    • 9.2 常用工具
      • 9.2.1 mysql
      • 9.2.2 mysqladmin
      • 9.2.3 mysqlbinlog
      • 9.2.4 mysqlshow
      • 9.2.5 mysqldump
      • 9.2.6 mysqlimport/source
    • 9.3 mysql管理小结
  • 10. 进阶篇总结

MySQL进阶篇

1. MySQL体系结构

小黑子—MySQL数据库:第二章 - 进阶篇_第1张图片

  • 连接层
    最上层是一些客户端和链接服务,主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
  • 服务层
    第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。
  • 引擎层
    存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。
  • 存储层
    主要是将数据存储在文件系统之上,并完成与存储引擎的交互。

2. 存储引擎

其实很多名词,把他们拆开的意思组合起来,就可以大致明白组合成的名词是什么意思。就拿存储引擎来说,存储在计算机中的意思是将数据保存到某种介质中,并且保证数据的正常访问。而引擎的是指发动机的核心。

组合起来的意思大致是保存数据的核心技术,也就是说,存储引擎是服务于存储服务的,通过存储引擎将数据保存。就跟计算机如何将数据保存到磁盘中一样,在数据库中,存储引擎的意思就是通过何种引擎将数据存储在磁盘中。

存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。

  1. 在创建表的时候,指定存储引擎
CREATE TABLE 表名(
	字段1 字段1类型[COMMENT 字段1注释],
	……
	字段n 字段n类型[COMMENT 字段n注释]ENGINE = INNODB [COMMENT 表注释];
  1. 查看当前数据库支持的存储引擎
SHOW ENGINES;

例子:
1、

show engines;

小黑子—MySQL数据库:第二章 - 进阶篇_第2张图片
2、

create table my_myisname(
    id int ,
    name varchar(10)
)engine = MyISAM;

选择然后Go to DDL,查看
小黑子—MySQL数据库:第二章 - 进阶篇_第3张图片
小黑子—MySQL数据库:第二章 - 进阶篇_第4张图片

2.1 InnoDB 存储引擎

InnoDB是一种兼顾搞可靠性和高性能的通用存储引擎,在MySQL5.5之后,InnoDB是默认的存储引擎

特点:

  • DML操作遵守ACID模型,支持事务
  • 行级锁,提高并发访问性能;
  • 支持外键FOREIGN KEY 约束,保证数据的完整性和正确性

文件:
XXX.ibd:XXX代表的是表名,innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的结构(frm、sdi)、数据和索引

参数:innodb_file_per_table

比如:

show variables like 'innodb_file_per_table';

可见该表是打开着的,每一张表都对应着一个表空间
小黑子—MySQL数据库:第二章 - 进阶篇_第5张图片
注意:查看ProgramData的时候如果一开始没有,打开显示隐藏文件就行
小黑子—MySQL数据库:第二章 - 进阶篇_第6张图片
不能直接点击去看,因为其是二进制文件
打开cmd,输入ibd2sdi account.ibd
小黑子—MySQL数据库:第二章 - 进阶篇_第7张图片

InnoDB 的逻辑存储结构:
小黑子—MySQL数据库:第二章 - 进阶篇_第8张图片
相当于图书馆->书架->书本->书页->一行字->一个字

  • 一个Extent(区)的大小固定为 1M
  • 一个 Page(页)的大小固定为 16K
  • 一个区包含64个页

2.2 MyISAM 存储引擎

MyISAM是MySQL早期的默认存储引擎

特点:

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

文件:

  • xxx.sdi:存储表结构信息
  • xxx.MYD:存储数据
  • xxx.MYI:存储索引
    小黑子—MySQL数据库:第二章 - 进阶篇_第9张图片

例如:
用notepad++打开sdi文件,负责立马的数据,再用浏览器打开json.cn显示
小黑子—MySQL数据库:第二章 - 进阶篇_第10张图片

小黑子—MySQL数据库:第二章 - 进阶篇_第11张图片

2.3 Memory 存储引擎

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

特点:

  1. 内存存放
  2. hash索引(默认)

文件:
xxx.sdi:存储表结构信息

存储引擎的区别:

小黑子—MySQL数据库:第二章 - 进阶篇_第12张图片

2.4 存储引擎选择

在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合

  1. InnoDB:是Mysql的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,那么InnoDB存储引擎是比较合适的选择

  2. MyISAM:如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。比如:业务系统当中的日志相关数据、电商当中的足迹、评论的相关数据等

  3. MEMORY:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORV的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性

小结:
小黑子—MySQL数据库:第二章 - 进阶篇_第13张图片

2.5 MySQL安装Linux版本

学习linux操作
看黑马MySQL p65

注意:

如果没有连接失败了,那么就在虚拟机端口里面输入systemctl stop firewalld,之后出现弹窗输入虚拟机的密码即可关闭防火墙

小黑子—MySQL数据库:第二章 - 进阶篇_第14张图片

小黑子—MySQL数据库:第二章 - 进阶篇_第15张图片

3. 索引

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

  • 演示
    小黑子—MySQL数据库:第二章 - 进阶篇_第16张图片

备注:上述二叉树索引结构的只是一个示意图,并不是真实的索引结构

优势 劣势
提高数据索引的效率,降低数据库的IO成本 索引列也是要占用空间的
通过索引列对数据进行排列,减低数据排列的成本,降低CPU的消耗 索引大大提高了查询效率,同时却也降低更新表的速度,如对表进行INSERT、UPDATE、DELETE时,效率降低

3.1 索引结构

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

索引结构 描述
B+Tree索引 最常见的索引类型,大部分引擎都支持B+树索引
Hash索引 底层数据结构是用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询
R-tree(空间索引) 空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少
Full-text(全文索引) 是一种通过建立倒排索引,快速匹配文档的方式。类似于Lucenne,Solr,ES

小黑子—MySQL数据库:第二章 - 进阶篇_第17张图片
我们平常所说的索引,如果没有特别指明,都是指B+树结构组织的索引

3.1.1 B tree

二叉数 与 红黑树:
小黑子—MySQL数据库:第二章 - 进阶篇_第18张图片

  • B-Tree(多路平衡查找树)
    以一颗最大度数〈max-degree)为5(5阶)的b-tree为例(每个节点最多存储4个key,5个指针):
    在这里插入图片描述
    例如:这里树的根节点有4个key,分别是20,30,62,89,那么,小于20的数据就会指向一个子节点,同理,20-30,30-62,62-89,大于89也都会分别指向不同的子节点

数据结构可视化的网址 Data Structure Visualizations

  1. b-tree的生成:
    先往树里插入数据,例如上面的最大度数为5的b-tree树,当往一个节点插入4个数据(key)时,此时这个节点就已经满了,再插入数据就会进行裂变,会进行一个中间元素向上裂变的过程
    例如:四个key
    在这里插入图片描述
  2. 再往里插入1200,违背5阶了,中间就发生裂变
    小黑子—MySQL数据库:第二章 - 进阶篇_第19张图片

0345就会变成中间元素,它就会向上分裂
插入元素超过最大度数的限制时会再次向上分裂,会把中间元素放到最上面的节点中

  1. 输入1234,1500,1000后,当前节点的元素变成了五个(0899 1000 1200 1234 1500),有六个指针违背了五阶
    小黑子—MySQL数据库:第二章 - 进阶篇_第20张图片
    中间元素会向上分裂,1200就到了上面的节点
    小黑子—MySQL数据库:第二章 - 进阶篇_第21张图片
  2. 经过一系列插入
    小黑子—MySQL数据库:第二章 - 进阶篇_第22张图片
    再插入2456跟1567相比大,走右侧指针,然后又比2000还大,有了5个元素(1800 1888 1980 2000 2456),在该节点6个指针违背五阶,中间元素1980向上分裂,又违背5阶便再次分裂
    小黑子—MySQL数据库:第二章 - 进阶篇_第23张图片

3.1.2 B+ tree

B+tree是B树的变种
以一个最大度数为4的B+tree为例
小黑子—MySQL数据库:第二章 - 进阶篇_第24张图片

B+tree的特点:

  1. 所有的元素都会出现在叶子节点
  2. 上面的非叶子节点主要起到索引的作用
  3. 在B+tree中,所有的叶子节点形成了一张单向链表
  4. 数据都存放在叶子节点

B+tree的生成:

和btree类似,也是超出范围(最大度数)后中间元素向上分裂,但是,不同的地方在于,B+tree向上分裂的同时,会把中间元素留在下面(留在叶子节点),同时叶子节点形成链表,向上分裂时,叶子节点以向上的元素为分裂点进行分裂

以一颗最大度数为5的B+tree为例:

  • 当key=4时
    小黑子—MySQL数据库:第二章 - 进阶篇_第25张图片
  • 当key>4时
    小黑子—MySQL数据库:第二章 - 进阶篇_第26张图片小黑子—MySQL数据库:第二章 - 进阶篇_第27张图片

在上面的图中插入1234,就会变成下面的样子
小黑子—MySQL数据库:第二章 - 进阶篇_第28张图片
MySQL索引数据结构对经典的B+tree进行了优化,在原B+tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+tree,提高区间访问
的性能
小黑子—MySQL数据库:第二章 - 进阶篇_第29张图片
说到页就要指针inodb的逻辑存储结构:表空间、段、区、页、行,而且一个页大小在innodb当中默认是16k
小黑子—MySQL数据库:第二章 - 进阶篇_第30张图片

3.1.3 Hash

哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后储存在hash表中

如果两个(或多个)键值,映射到一个相同的槽位上,他们就形成了hash冲突(也成为hash碰撞),可以通过链表来解决
小黑子—MySQL数据库:第二章 - 进阶篇_第31张图片

hash索引的特点:

  1. hash索引只能用于对等比较(=,in),不支持范围查询(between,>,<,…)
  2. 无法利用索引完成排序操作
  3. 查询效率搞,通常只需要以此检索就可以了,效率通常要高于b+tree索引(要在不出现hash碰撞的情况下)
    在这里插入图片描述

思考题:
为什么InnoDB储存引擎选择使用B+tree索引结构?
小黑子—MySQL数据库:第二章 - 进阶篇_第32张图片

  • 相对于二叉树,层级更少,查询效率搞;
  • 对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加数的高度,导致性能降低
  • 先对于hash索引,B+tree支持范围匹配度及排序操作

3.2 索引分类

分类 含义 特点 关键字
主键索引 针对于表中主键创建的索引 默认自动创建,只能有一个 PRIMARY
唯一索引 避免同一个表中某数据列中的值重复 可以有多个 UNIQUE
常规索引 快速定位特点数据 可以有多个
全文索引 全文索引查找的是文本中的关键字,而不是比较索引中的值 可以有多个 FULLTEXT
  • 在InnoDB中,根据索引的存储形式,又可以分为以下两种
特点 含义 特点
聚集索引(Clustered Index) 将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据 必须有,而且只有一个
二级索引(Secondary Index) 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 可以存在多个

聚集索引选取规则:

  1. 如果存在主键,主键索引就是聚集索引
  2. 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引
  3. 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引

例子:
小黑子—MySQL数据库:第二章 - 进阶篇_第33张图片

这里id为主键,所以id就是聚集索引,id形成的聚集索引就是上图的第一个B+tree结构,叶子节点存储的就是id所对应的那一行的数据,例如,id=5的叶子节点对应的就是第一行的数据,

第二个B+tree结构对应的就是二级索引,二级索引的叶子节点所对应的数据就是主键值

小黑子—MySQL数据库:第二章 - 进阶篇_第34张图片

如果查询时不是通过主键查询,那么就会先走二级索引查到对应的主键值,再通过对应的主键值在聚集索引中查找到对应的行元素这个过程就叫作回表查询

3.2.1 思考题

小黑子—MySQL数据库:第二章 - 进阶篇_第35张图片

第一个执行效率高,因为他直接通过主键查找元素,
直接使用聚集索引查找数据,而第二条要先在二级索引找到对应的id值,再通过id值在聚集索引中查找对应的数据(回表查询)

小黑子—MySQL数据库:第二章 - 进阶篇_第36张图片
同理,三层就会有1171*16kb=21939856kb的存储空间

3.3 索引语法

  • 创建索引
CREATE [UNIQUE | FULLTEXT] INDEX index_name ON table_name(index_col_name,...);
  • 查看索引
SHOW INDEX FROM table_name;
  • 删除索引
DROP INDEX index_name ON table_name;

案例:
小黑子—MySQL数据库:第二章 - 进阶篇_第37张图片
表详情:

mysql> select *from tb_user_s1;
+----+-----------+-------------+---------------+-----------------+------+--------+--------+---------------------+
| id | name      | phone       | email         | profession      | age  | gender | status | createtime          |
+----+-----------+-------------+---------------+-----------------+------+--------+--------+---------------------+
|  1 | 鲁班      | 12345678910 | 123@qq.com    | 软件工程        |   22 | 1      | 3      | 2012-02-01 00:00:00 |
|  2 | 华佗      | 12645678910 | 12@qq.com     | 互联网工程      |   22 | 1      | 3      | 2012-02-02 00:00:00 |
|  3 | 张飞      | 12343678910 | 1@qq.com      | 土木工程        |   22 | 1      | 3      | 2012-02-03 00:00:00 |
|  4 | 关羽      | 12345678910 | 13@qq.com     | 计算机          |   22 | 1      | 3      | 2012-02-04 00:00:00 |
|  5 | 刘备      | 12315678910 | 23@qq.com     | 软件工程        |   22 | 1      | 3      | 2012-02-11 00:00:00 |
|  6 | 猴子      | 12342678910 | 1234@qq.com   | 化学工程        |   22 | 1      | 3      | 2012-02-21 00:00:00 |
|  7 | 压缩      | 12345678910 | 1223@qq.com   | 通讯工程        |   22 | 1      | 3      | 2012-02-06 00:00:00 |
|  8 | 剑圣      | 12345678610 | 1123@qq.com   | 软件工程        |   22 | 1      | 3      | 2012-02-12 00:00:00 |
|  9 | 刀妹      | 12345698910 | 12344@qq.com  | 国际贸易        |   22 | 1      | 3      | 2012-02-15 00:00:00 |
| 10 | 小法师    | 12245678910 | 13235@qq.com  | 软件工程        |   22 | 1      | 3      | 2012-02-09 00:00:00 |
| 11 | 佐伊      | 12345678910 | 12213@qq.com  | 园林工程        |   22 | 1      | 3      | 2012-02-11 00:00:00 |
| 12 | 狼人      | 12345658910 | 124653@qq.com | 软件工程        |   22 | 1      | 3      | 2012-02-21 00:00:00 |
| 13 | 男刀      | 12385678910 | 12203@qq.com  | 软件工程        |   22 | 1      | 3      | 2012-02-11 00:00:00 |
| 14 | vn        | 32345678910 | 12398@qq.com  | 人工智能        |   22 | 1      | 3      | 2012-02-21 00:00:00 |
| 15 | 卡沙      | 12325678910 | 12013@qq.com  | 应用数学        |   22 | 1      | 3      | 2012-02-21 00:00:00 |
| 16 || 12345378910 | 12023@qq.com  | 物理            |   22 | 1      | 3      | 2012-02-19 00:00:00 |
+----+-----------+-------------+---------------+-----------------+------+--------+--------+---------------------+
16 rows in set (0.00 sec)

索引的名称:一般为idx_表名_字段名

-- 1.
create index idx_user_name on tb_user_s1(name);

-- 2.
create unique index idx_user_phone on tb_user_s1(phone);

-- 3. 创建联合索引的顺序是有讲究的
create idnex idx_user_pro_age_sta on tb_user_s1(profession,age,status);

-- 4.删除索引
create index idx_user_emaill on tb_user_s1(email);
drop index idx_user_email on tb_user_s1;

3.4 SQL性能分析

3.4.1 SQL的执行频率

MySQL客户端连接成功后,通过show[session | global] status 命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次

SHOW GLOBAL STATUS LIKE 'Com_______';

例子:
在这里插入图片描述

3.4.2 慢查询日志

慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志

MySQL的慢查询日志默认没有开启,需要MySQL的配置文件(/etc/my.cnf)中配置:

# 开启MySQL慢日志查询开关
slow_query_log = 1

# 设置日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time = 2

配置完毕之后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息/var/lib/mysql/localhost-slow.log

小黑子—MySQL数据库:第二章 - 进阶篇_第38张图片
vi /etc/my.cnf切换,然后按G切换到最后操作慢查询日志

小黑子—MySQL数据库:第二章 - 进阶篇_第39张图片
退出后重启systemctl restart mysqld,可见开关打开了
小黑子—MySQL数据库:第二章 - 进阶篇_第40张图片

-- 先用cd切换到这个目录下
cd /var/lib/mysql
-- 然后执行下面这条语句就可以实时查看慢查询日志,
-- 只要有执行时间超过指定时间的,都会被慢查询日志记录
tail -f  localhost-slow.log

3.4.3 show profiles

show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling参数,能够看到当前MySQL是否支持profile操作:

SELECT @@have_profiling;

可见支持
小黑子—MySQL数据库:第二章 - 进阶篇_第41张图片

默认profiling是关闭的,可以通过set语句在session/global级别开启profiling;

SET profiling = 1;

查看一下,0可见没有开启
小黑子—MySQL数据库:第二章 - 进阶篇_第42张图片
开启
小黑子—MySQL数据库:第二章 - 进阶篇_第43张图片

  • profile详情:
    执行一系列的业务SQL的操作,然后通过如下指令查看指令的执行耗时;
#1.查看每一条SQL的耗时基本情况
show profiles;

#2.查看指定query_id的SQL语句各个阶段的耗时情况
show profile for query query_id;

#3.查看指定query_id的语句CPU的使用情况
show profile cpu for query query_id;

例子:
1.
小黑子—MySQL数据库:第二章 - 进阶篇_第44张图片
2.
小黑子—MySQL数据库:第二章 - 进阶篇_第45张图片
3.
小黑子—MySQL数据库:第二章 - 进阶篇_第46张图片

3.4.4 explain执行计划

通过explain或者desc命令可以获取mysql如何执行select语句的信息,包括select语句执行过程中表如何连接和连接的顺序
语法:

#直接在select语句之前加上关键字explain/desc
EXPLAN SELECT 字段列表 FROM 表名 WHERE 条件;

在这里插入图片描述

  • explain执行计划
    EXPLAIN 执行计划哥字段含义:
  1. id :
    select 查询的序列号,表示查询中执行select子句或者是操作表的顺序(id相同,执行顺序从上到下;id不同,值越大,越先执行)
    小黑子—MySQL数据库:第二章 - 进阶篇_第47张图片
    课程表
    小黑子—MySQL数据库:第二章 - 进阶篇_第48张图片
    学生表
    小黑子—MySQL数据库:第二章 - 进阶篇_第49张图片
    课程与学生之间的关系
    小黑子—MySQL数据库:第二章 - 进阶篇_第50张图片

查看每一个学生的选课情况

select s.*,c.* from student s,coures c,student_course sc where s.id = sc.studentid and o.id = sc.course; 

小黑子—MySQL数据库:第二章 - 进阶篇_第51张图片
查看执行计划,只需在这个执行语句前加explain:

explain select s.*,c.* from student s,coures c,student_course sc where s.id = sc.studentid and o.id = sc.course;

下图中sql的执行顺序就是c-》sc-》< subquery2 >-》s
小黑子—MySQL数据库:第二章 - 进阶篇_第52张图片

  1. select_type
    表示SELECT的类型,常见的取值有SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(SELECT/WHERE之后包含了子查询)等

  2. type(比较重要)
    表示连接类型,性能好到差的连接类型为NULL、system、const、eq_ref、ref、range、index、all(全表扫描性能会比较低)

  3. possible_key
    显示可能应用于这张表上的索引,一个或多个

  4. Key(主要关注的字段)
    实际用到的索引,如果为NULL,则就没有使用索引

  5. Key_len(主要关注的字段)
    表示索引使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好

  6. rows
    MySQL认为必须要执行查询的行数,在InnoDB中,是一个估计值,可能并不总是准确的

  7. filtered
    表示返回结果的行数占需读取行数的百分比,filtered的值越大越好

  8. Extra(主要关注的字段)
    额外信息

3.5 索引使用

3.5.1 验证索引效率

  • 验证索引效率
    在未建立索引之前,执行如下SQL语句,查看SQL的耗时
    例如:
SELECT*FROM tb_sku WHERE sn = '100000003145001';

小黑子—MySQL数据库:第二章 - 进阶篇_第53张图片
一条数据的查询耗时了21秒的时间,这个性能极低
小黑子—MySQL数据库:第二章 - 进阶篇_第54张图片
为什么性能极低呢?
由于id是主键,主键默认的是由主键索引的
小黑子—MySQL数据库:第二章 - 进阶篇_第55张图片

  • 针对字段创建索引
    例如:
create index indx_sku_sn on tb_sky(sn);

可见构建这1000万的数据b+tree花费了1分多钟的时间
小黑子—MySQL数据库:第二章 - 进阶篇_第56张图片

  • 然后再执行相同的SQL语句,再次查看SQL的耗时
SELECT*FROM tb_sky WHERE sn = '100000003145001';

这次查询0.01秒,比上次效率提高
小黑子—MySQL数据库:第二章 - 进阶篇_第57张图片
用到了索引
小黑子—MySQL数据库:第二章 - 进阶篇_第58张图片

3.5.2 最左前缀法则

如果一个索引关联了多个字段(联合索引),在使用时就要遵循最左前缀法则,

最左前缀法则:查询从索引的最左列开始,如果没有最左边的列,那么就不能使用索引查询,如果存在最左边的列,但是查询时查询条件跳过了索引中的某一列,那么索引将部分失效(最左边列之后的索引都会失效)

例如:

-- 可以使用索引查询,因为最左边的索引存在且没有跳过任何一个列
-- 这里这三个条件的位置可以改变,不会影响,但是最左的字段必须存在
explain  select *from tb_user_s1 
where profession='软件工程' and age=22 and status='3'; 
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-----------------------+
| id | select_type | table      | partitions | type | possible_keys           | key                     | key_len | ref               | rows | filtered | Extra                 |
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-----------------------+
|  1 | SIMPLE      | tb_user_s1 | NULL       | ref  | idx_user_pro_age_status | idx_user_pro_age_status | 73      | const,const,const |    6 |   100.00 | Using index condition |
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------------------+------+----------+-----------------------+
1 row in set, 1 warning (0.01 sec)
-- possible_keys显示可能使用的索引
-- key显示实际使用的索引
-- key_len 显示索引使用的字节数                 




-- 不能使用索引查询,因为在联合索引中间age没有查询,所以查询时不能使用索引
mysql>  explain  select *from tb_user_s1 where age=22 and status='3'
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table      | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_user_s1 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   16 |     6.25 | Using where |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
-- 这里最左边列一旦没有出现,之后的索引就全部失效了

-- 使用的查询方式是全部遍历
mysql> explain  select *from tb_user_s1 where profession='软件工程' and status='3'; 
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table      | partitions | type | possible_keys           | key                     | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | tb_user_s1 | NULL       | ref  | idx_user_pro_age_status | idx_user_pro_age_status | 63      | const |    6 |    10.00 | Using index condition |
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
-- 索引当中最左边的字段存在就可以了,跟所放的位置无关
  • 范围查询
    联合索引中,出现范围查询(>,<),范围查询右侧的列索引失败
-- 这里age后的status索引会失效
-- 使用>=不会失效,只有使用>或<时才会失效
mysql> explain  select *from tb_user_s1 where profession='软件工程' and age>20 and status='3';
+----+-------------+------------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+
| id | select_type | table      | partitions | type  | possible_keys           | key                     | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+------------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | tb_user_s1 | NULL       | range | idx_user_pro_age_status | idx_user_pro_age_status | 68      | NULL |    6 |    10.00 | Using index condition |
+----+-------------+------------+------------+-------+-------------------------+-------------------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

3.5.3 索引失效的情况

3.5.3 - I 情况一
  • 索引列运算
    表数据查询在使用索引时,每个字段的索引是单独生效的

不要在索引列上进行运算操作,否则索引将会失效

select *from tb_user_s1 where substring(phone,10,2);
-- 这里就使用了substring进行函数运算,
-- 在查询时就没有使用索引,而是全表扫描,性能降低

小黑子—MySQL数据库:第二章 - 进阶篇_第59张图片

  • 字符串不加引号
    在查询字符串类型的数据时,sql语句where后的字符串在查询时没有加单引号,那么索引就会失效,在查询时就没有使用索引,而是全表扫描,性能降低
;-- 字符串加了单引号
mysql> explain select *from tb_user_s1  where phone='12385678910'
-- type是ref,代表使用索引查询
+----+-------------+------------+------------+------+----------------+----------------+---------+-------+------+----------+-----------------------+
| id | select_type | table      | partitions | type | possible_keys  | key            | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+------------+------------+------+----------------+----------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | tb_user_s1 | NULL       | ref  | idx_user_phone | idx_user_phone | 45      | const |    1 |   100.00 | Using index condition |
+----+-------------+------------+------------+------+----------------+----------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

-- 字符串没加单引号
mysql> explain select *from tb_user_s1  where phone=12385678910;
-- type是all,代表没有使用索引查询
+----+-------------+------------+------------+------+----------------+------+---------+------+------+----------+-------------+
| id | select_type | table      | partitions | type | possible_keys  | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+------------+------------+------+----------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_user_s1 | NULL       | ALL  | idx_user_phone | NULL | NULL    | NULL |   16 |    10.00 | Using where |
+----+-------------+------------+------------+------+----------------+------+---------+------+------+----------+-------------+
1 row in set, 3 warnings (0.00 sec)

  • 模糊查询
    如果仅仅是尾部模糊匹配,索引不会失效。如果头部模糊匹配,索引失效
    小黑子—MySQL数据库:第二章 - 进阶篇_第60张图片
    下面就失效了
    在这里插入图片描述
3.5.3 - II 情况二
  • or连接的条件
    用or分割开,如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到

注意:

复合索引使用时如果没有使用最左侧的索引,那么索引就不会生效

-- 这里phone是普通索引,而age是联合索引,由于没有联合索引的最左列字段,所以age这个字段没有建立索引
-- 可以看作没有索引,此时在进行查询时,索引都没有生效
mysql> explain select *from tb_user_s1  where phone='12345698910' or age=22;
+----+-------------+------------+------------+------+----------------+------+---------+------+------+----------+-------------+
| id | select_type | table      | partitions | type | possible_keys  | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+------------+------------+------+----------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_user_s1 | NULL       | ALL  | idx_user_phone | NULL | NULL    | NULL |   16 |    16.92 | Using where |
+----+-------------+------------+------------+------+----------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

-- phone和name都是普通索引,or的两边都有索引,所以索引查询生效
mysql> explain select *from tb_user_s1  where phone='12345698910' or name='刀妹';
+----+-------------+------------+------------+-------------+------------------------------+------------------------------+---------+------+------+----------+--------------------------------------------------------+
| id | select_type | table      | partitions | type        | possible_keys                | key                          | key_len | ref  | rows | filtered | Extra                                                  |
+----+-------------+------------+------------+-------------+------------------------------+------------------------------+---------+------+------+----------+--------------------------------------------------------+
|  1 | SIMPLE      | tb_user_s1 | NULL       | index_merge | idx_user_name,idx_user_phone | idx_user_phone,idx_user_name | 45,43   | NULL |    2 |   100.00 | Using union(idx_user_phone,idx_user_name); Using where |
+----+-------------+------------+------------+-------------+------------------------------+------------------------------+---------+------+------+----------+--------------------------------------------------------+
1 row in set, 1 warning (0.01 sec)
  • 数据分布影响
    如果MySQL评估使用索引查询数据比使用全表扫描更慢,则就不会使用索引

例如:在查询某些数据时,这个表中的大多数数据都符合要求,那么就不会使用索引,而是用全表扫描,因为MySQL认为全表扫描比索引更快

-- 这里is null就会使用索引,因为mysql认为表中的数据大多数都不是null,所以使用索引
mysql> explain select *from tb_user_s1  where profession is null;
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table      | partitions | type | possible_keys           | key                     | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | tb_user_s1 | NULL       | ref  | idx_user_pro_age_status | idx_user_pro_age_status | 63      | const |    1 |   100.00 | Using index condition |
+----+-------------+------------+------------+------+-------------------------+-------------------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.01 sec)

-- 这里就不会使用索引,还是因为mysql认为表中的数据大多数都不是null,所以不走索引
mysql> explain select *from tb_user_s1  where profession is not null;
+----+-------------+------------+------------+------+-------------------------+------+---------+------+------+----------+-------------+
| id | select_type | table      | partitions | type | possible_keys           | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+------------+------------+------+-------------------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_user_s1 | NULL       | ALL  | idx_user_pro_age_status | NULL | NULL    | NULL |   16 |   100.00 | Using where |
+----+-------------+------------+------------+------+-------------------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

3.5.4 SQL提示

不用SQL提示时,当一个字段有多个索引,那么,MySQL会自动的选择一个索引使用,例如,profession有一个普通索引和联合索引,那么,在查询时MySQL会自动选择使用联合索引去查询。

而使用SQL提示就可以使SQL按照指定的索引去查询

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

例子:

  1. use index:(告诉SQL使用指定索引,给MySQL一个建议,MySQL也有可能不使用指定索引)
explain select * from tb_user use index(idx_user_pro) where profession = '软件工程';

在这里插入图片描述

  1. ignore index:(告诉SQL不使用指定索引)
explain select*from tb_user ignore index(idx_user_pro) where profession = '软件工程';

在这里插入图片描述

  1. force index:(告诉SQL必须使用指定索引)
explain select * from tb_user force index(idx_user_pro) where profession = '软件工程';

在这里插入图片描述

3.5.5 覆盖索引 & 回表查询

尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少select *

简单理解就是,在select之后的字段尽量都有索引,查询时就不会回表查询了

例如:
小黑子—MySQL数据库:第二章 - 进阶篇_第61张图片
覆盖查询在explain中只看Extra

mysql> explain select *from tb_user_s1  where profession='软件工程' and age=22 and status='3' ;
+-----------------------+
 | Extra                 |
+-----------------------+
 | Using index condition |
+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select id,profession,age,status from tb_user_s1  where profession='软件工程' and age=22 and status='3' ;
+--------------------------+
 | Extra                    |
+--------------------------+
 | Using where; Using index |
+--------------------------+
1 row in set, 1 warning (0.00 sec)

可以看到,使用select*查询时出现的是Using index condition,证明出现的回表查询,效率不是最高的,而查询时,select后是联合索引对应的字段时,出现的是Using where; Using index证明是直接通过索引查询,效率较高

覆盖索引,不需要回表,查询两个字段,查询后直接就把数据返回,没有再进去表中查询,一次索引扫描就完成了
小黑子—MySQL数据库:第二章 - 进阶篇_第62张图片
没有覆盖索引,要先扫描辅助索引(二级索引),获取id值后还要再去扫描聚集索引才能获取想要的数据,索引扫描了两次,效率自然就低了,进行回表查询:
小黑子—MySQL数据库:第二章 - 进阶篇_第63张图片

使用select*就会很容易回表查询,所以要避免使用select*

思考题:
小黑子—MySQL数据库:第二章 - 进阶篇_第64张图片

3.5.6 前缀索引

当字段类型为字符串(varchar,text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率

  • 语法:
-- idx_xxxx:索引名
-- table_name:表名
-- column(n)字段名,n代表提取前几个字符作为前缀索引,例如,n=5,
-- 那么就会使用字符串的前5个字符构建索引
create index idx_xxxx on table_name(colunm(n));
  • 前缀长度
    可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据库的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的

求选择性的方法:
例如

select count(distinc email) / count(*) from tb_user;
-- 这里就是用substring来判断选择度,下面的代码就是截取从1索引开始,前5个字符,来判断一个
-- 字段前5个字符的选择度
-- 用这段代码就可以获取选择性,进而判断选择前几个字符作为前缀来创建索引
select count(distinct substring(email,1,5)/count(*) from tb_user;
  • 前缀索引的结构
    小黑子—MySQL数据库:第二章 - 进阶篇_第65张图片

前缀索引是用来解决一些长字符串或者是大文本字段,在整个字段进行索引的时候、索引体积过于庞大而造成浪费大量磁盘IO的情况,可以使用前缀索引

3.5.7 单列 & 联合索引

  • 单列索引:一个索引只包含单个列
    小黑子—MySQL数据库:第二章 - 进阶篇_第66张图片
    key只用到了phone的索引,也就是说这两个条件根据phone去查,根据name查没走,所以这时候会涉及到覆盖索引的问题和回表查询

单列索引情况:

explain select id,phone,name from tb_user where phone = '17799990010' and name = '韩信'
  • 联合索引:一个索引包含多个列
    小黑子—MySQL数据库:第二章 - 进阶篇_第67张图片
    用到了覆盖索引,此时是不需要回表查询的

一次查询使用多个单列索引时,只会使用一个单列索引,另外的单列索引不会使用

在业务场景中,如果存在多个查询条件,针对多个字段建立索引时,建议使用联合索引,而不是单列索引

多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询

联合索引情况:
小黑子—MySQL数据库:第二章 - 进阶篇_第68张图片
phone和name有联合索引,每个字段放前面还是放后面对查询是有影响的
小黑子—MySQL数据库:第二章 - 进阶篇_第69张图片

尽量使用联合索引,避免使用单列索引,创建联合索引时注意创建的字段顺序

3.6 索引设计原则

  1. 针对于数据量较大,且查询比较频繁的表建立索引
  2. 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
  3. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高
  4. 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引
  5. 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率
  6. 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率
  7. 如果索引列不能存储NULL值,请在创建表时使用NOT NULL 约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询

3.7 小结

小黑子—MySQL数据库:第二章 - 进阶篇_第70张图片
小黑子—MySQL数据库:第二章 - 进阶篇_第71张图片

4. SQL优化

4.1 插入数据 insert优化

例子:

insert into tb_test values(1,'tom');
insert into tb_test values(2,'cat');
insert into tb_test values(3,'jerry');
....
  • 批量插入
    在插入多条数据时,不要一句一句的SQL语句插入,而是一次性插入多条数据
INSERT INTO 表名  (字段名1,字段名2...) VALUES (值一,值二,...),(值一,值二,...),(值一,值二,...);.....
INSERT INTO 表名 VALUES (值一,值二,...),(值一,值二,...),(值一,值二,...);``

但是批量插入一次最多不要超过1000条,大概就是500到1000条,如果一次性要插入几万条数据,那么可以将其分为多条insert语句插入

  • 手动提交事务
    可以优化插入速度,在插入前手动开启事务,插入完成后手动结束事务
start transaction;
insert into 表名 values 具体数据1;
insert into 表名 values 具体数据2;
insert into 表名 values 具体数据3;
commit;
  • 主键顺序插入
    在插入主键时按照主键的顺序插入
主键乱序插入:8 1 9 21 88 2 4 15 89 5 7 3
主键顺序插入:1 2 3 4 5 7 8 9 15 24 88 89
  • 大批量插入数据
    如果一次性插入大批量数据(万级的),使用insert语句插入性能较低,此时可以使用MySQL提供的load指令插入,通过load指令可以将本地磁盘中的数据全部加载到数据库当中

小黑子—MySQL数据库:第二章 - 进阶篇_第72张图片

使用load指令的步骤:
1.在客户端连接服务端的时候,加上参数 – local-infile,以此查看全局参数

mysql --local-infile -u root -p

小黑子—MySQL数据库:第二章 - 进阶篇_第73张图片

2.设置全局参数local_infile为1,开启从本地加载文件导入数据的开关

set gloabl local_infile = 1

小黑子—MySQL数据库:第二章 - 进阶篇_第74张图片
创建表结构:
在这里插入图片描述
小黑子—MySQL数据库:第二章 - 进阶篇_第75张图片

3.执行load指令将准备好的数据,加载到表结构中

load data local infile '/root/sql1.log' into table 'tb_user' fields terminated by ','lines terminated by '\n';

在finalshell导入的文件
小黑子—MySQL数据库:第二章 - 进阶篇_第76张图片
查看该脚本所处位置是在root目录下的
在这里插入图片描述
注意这里插入数据时,由于是虚拟机,所以要现在finalshall中上传数据,把数据上传到虚拟机中
小黑子—MySQL数据库:第二章 - 进阶篇_第77张图片
这里插入100万条数据只需要耗时16秒,很强

使用load插入时也需要主键顺序插入,顺序插入数据高于乱序插入

主键顺序插入性能高于乱序插入

4.2 主键优化

  • 数据组织方式
    在InnoDB存储引擎,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(index organized table IOT)
    小黑子—MySQL数据库:第二章 - 进阶篇_第78张图片

回顾一下InnoDB的存储结构:
小黑子—MySQL数据库:第二章 - 进阶篇_第79张图片

  • 页分裂
    页可以为空,也可以填充一半,也可以填充100%。每个页包含了2 - N 行数据(如果一行数据多大,会行溢出),根据主键排列
    小黑子—MySQL数据库:第二章 - 进阶篇_第80张图片

主键的时候乱序插入:
小黑子—MySQL数据库:第二章 - 进阶篇_第81张图片
不会这样子插入,因为叶子节点是有序的,50插入进来就应该存放在47后面这个位置

小黑子—MySQL数据库:第二章 - 进阶篇_第82张图片
那么再来看1号数据页虽然没写满了,但是50这一行数据写不下了,它会开启一个新的数据页,但是50不会直接写到这个数据页上,会做一个操作:
开启一个新的数据页,然后会找到第一个数据页50%的位置——23和47这两行数据移动到新开辟的数据页当中
小黑子—MySQL数据库:第二章 - 进阶篇_第83张图片

小黑子—MySQL数据库:第二章 - 进阶篇_第84张图片

然后再将50插入到这个new数据页当中

小黑子—MySQL数据库:第二章 - 进阶篇_第85张图片

此时需要再对链表指针再进行一个重新的设置,它会设置1号数据页它的下一个数据页是3号,而3号数据页的下一个是2号
小黑子—MySQL数据库:第二章 - 进阶篇_第86张图片

小黑子—MySQL数据库:第二章 - 进阶篇_第87张图片
那么这种主键乱序插入的情况现象称之为——页分裂 , 这个是比较消耗性能的
小黑子—MySQL数据库:第二章 - 进阶篇_第88张图片

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

当页中删除的记录达到MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用
小黑子—MySQL数据库:第二章 - 进阶篇_第89张图片

当下个数据页还有50%以上是空闲的,所以这2号和3号数据页InnoDB引擎就会自动进行合并
小黑子—MySQL数据库:第二章 - 进阶篇_第90张图片
小黑子—MySQL数据库:第二章 - 进阶篇_第91张图片

  • 主键的设计原则
  1. 在满足业务需求的情况下,尽量降低主键的长度
  2. 插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键
  3. 尽量不要使用UUID做主键或者是其他自然主键,如身份证号
  4. 业务操作时,避免对主键的修改
    小黑子—MySQL数据库:第二章 - 进阶篇_第92张图片

4.3 order by优化

  1. Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后再排序缓冲区sort buffer中完成排序操作,因此所有不是通过索引直接返回排序结果的排序都叫FileSort排序
  2. Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为using index,不需要额外排序,操作效率搞
#没有创建索引时,根据age,phone进行排序
explain select id,age,phone from tb_user order by age,phone;

#创建索引
create index idx_user_age_phone_aa on tb_user(age,phone);

#创建索引后,根据age,phone进行升序排列
explain select id,age,phone from tb_user order by age,phone;

#创建索引后,根据age,phone进行降序排序
explain select id,age,phone from tb_user order by age desc, phone desc;

例如:

-- 此时age和phone都没有索引,使用order by排序时都是Using filesort,效率较低
mysql> explain select id,age ,phone from tb_user_s1 order by age;
+----------------+
 | Extra          |
+----------------+
| Using filesort |
+----------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select id,age ,phone from tb_user_s1 order by age,phone;
+----------------+
 | Extra          |
+----------------+
| Using filesort |
-+----------------+
1 row in set, 1 warning (0.00 sec)

-- 为age和phone建立联合索引后,就会变为Using index

小黑子—MySQL数据库:第二章 - 进阶篇_第93张图片

-- 若在查询时,order by之后的联合索引的排序不同,例如,一个顺序,一个倒序,
-- 也会出现Using filesort的情况
-- 这里age升序排列,phone倒序排列
select id,age ,phone from tb_user_s1 order by age asc,phone desc;

-- 这种情况的解决方式就是在创建联合索引时就把顺序定好
-- 下面就是在创建联合索引时就确定age和phone的排列方式
create index idx_user_age_phone_ad on tb_user_s1(age asc ,phone desc)
-- 此时age就按升序排列,phone就按倒序排列

小黑子—MySQL数据库:第二章 - 进阶篇_第94张图片
如果此时在执行如下语句,就不会有age就按升序排列,phone就按倒序排列
在这里插入图片描述


#根据age,phone进行降序一个升序,一个降序
explain select id,age,phone from tb_user order by age asc,phone desc;

#创建索引
create index idx_user_age_phone_ad on tb_user(age asc,phone desc);

#根据age,phone进行降序一个升序,一个降序
explain select id,age,phone from tb_user order by age asc,phone desc;

小黑子—MySQL数据库:第二章 - 进阶篇_第95张图片

注意:上述的所有排序优化都有一个条件,就是覆盖索引,如果不是覆盖索引就不行

即order by优化主要就以下几点:

  1. 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则
  2. 尽量使用覆盖索引
  3. 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)
  4. 如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小sort_buffer_size(默认为256k)

4.4 group by优化

#删除掉目前的联合索引idx_user_pro_age_sta
drop index idx_user_pro_age_sta on tb_user;

#执行分组操作,根据profession字段分组
explain select profession,count(*) from tb_user group by profession;

#创建索引
Create index idx_user_pro_age_sta on tb_user(profession,age,status);

#执行分组操作,根据profession字段分组
explain select profession,count(*) from tb_user group by profession;

#执行分组操作,根据profession,age字段分组
explain select profession,count(*) from tb_user group by profession,age;

例如:
当前表的索引:
小黑子—MySQL数据库:第二章 - 进阶篇_第96张图片


小黑子—MySQL数据库:第二章 - 进阶篇_第97张图片
查看当前sql语句的执行:
可见使用为Extra:Using temporary
小黑子—MySQL数据库:第二章 - 进阶篇_第98张图片
再次创建索引,查看执行计划

create index idx_user_pro_age_sta on tb_user(profession,age,status);

可见用到了索引优化为了index
小黑子—MySQL数据库:第二章 - 进阶篇_第99张图片


  • 在分组操作时,可以通过索引来提高效率
  • 在分组操作时,索引的使用也是满足最左前缀法则的

4.5 limit优化

limit一个常见的问题就是,在大数据量的情况下,越往后查询数据,limit的效率月底,例如limit 2000000,10,此时需要mysql排序前2000010记录,但是仅仅返回2000000和2000010之间的记录,其他的记录丢弃,查询排序的代价非常大

官方给出的优化方式是,通过覆盖索引和子查询的方式优化

-- 直接使用limit查询,效率很低,大概要19秒多
select *from tb_user_s2 limit 9000000,10;

-- 使用覆盖索引和子查询的方式优化
-- 先在子查询中找到对应的主键,然后再使用主键进行查询数据
-- 但是这种方式好像在mysql8.0.26语法不支持
select * from tb_user_s2 where id in(select id from tb_user_s2 order by in limit 9000000,10);

-- 那么可以使用另外的语法实现这个效果
-- 把select id from tb_user_s2 order by in limit 9000000,1
-- 返回的结果看成一张表,然后使用多表查询
select s.* from tb_user_s2 s, (select id from tb_user_s2 order by in limit 9000000,10) a where a.id=s.id;
-- 这样写大概查询是10秒,提高9秒的效率

4.6 count优化

当数据量很大时,count一次也是很耗时的

explain select count(*) from tb_user;

这个实际上是取决于InnoDB的处理方式

  • MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行count(*)的时候会直接返回这个数,效率很高
    这里MyISAM的前提条件是没有where条件,如果有where条件,那么MyISAM也会比较慢

  • InnoDB引擎就麻烦,它执行count(*)的时候,需要把数据一行一行地从引擎里面读出来,然后累加计数

对于count来说,优化策略:就是自己计数,在插入或删除数据时自己记录下来

count是一个聚合函数,对于返回的结果集,一行行的判断,如果count函数的参数不是NULL,累计值就加1,否则不加,最后返回累计值,即count参数会统计数据的数量

count的几种用法:

  1. count(*)

  2. count(主键)

  3. count(字段)

  4. count(1)

-- count(*)就是求取某个表中数据的总体数量
mysql> select count(*)from tb_user_s1;
+----------+
| count(*) |
+----------+
|       16 |
+----------+
1 row in set (0.00 sec)

-- count(字段),会统计字段的数量,对字段里的值统计前会先判断是不是null,
-- 不是null才会统计进去,是null就不会统计
-- 更新某个id的profession为null
mysql> select count(profession)from tb_user_s1;
+----------+
| count(*) |
+----------+
|       15 |
+----------+
1 row in set (0.00 sec)

-- count(1)的使用和count(*)是一样的,也是会统计数据的总体数量

count(主键):

  • InnoDB会遍历整张表,将所有主键都拿出来,然后累加返回具体数量

count(字段):

  • 没有not null约束的话,InnoDB会遍历整张表把字段值拿出来,然后判断每个值是否为null,不为null,数量增加,为null,数量不增加,有not null约束的话,就跟count(主键)类似,InnoDB会遍历整张表把字段值拿出来,然后累加获得字段值的数量

count(1):

  • InnoDB遍历整张表,不取值,对每一行放一个数字1进去,直接按行进行累加

count(*):

  • InnoDB并不会把全部字段值取出来,已经优化过了,不取值,直接按行累加

按照效率的话:
在这里插入图片描述

4.7 update优化

需要规避的一个问题是:在更新字段时需要根据带有索引的字段进行更新,即where后的条件字段尽量是带有索引的,否则,就会出现行锁升级为表锁,锁住整张表,从而使并发性能降低

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

小黑子—MySQL数据库:第二章 - 进阶篇_第100张图片

4.8 优化小结

对SQL优化绝大时候都是对索引优化
小黑子—MySQL数据库:第二章 - 进阶篇_第101张图片

5. 视图

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

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

5.1 视图基本语法

  • 创建
CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [WITH[CASCADED | LOCAL] CHECK OPTION]

例子:

 create table student(
     id int comment'编号',
     name varchar(10) comment '姓名',
     no varchar(11) comment '电话'
 )comment '学生表';

insert into student values(1,'麻瓜',2000100100),(2,'愣头青',2000100102),(3,'铁头娃',2000100103),(4,'小老板',2000100100)

-- 创建视图
create or replace view stu_v_l as select id,name from student where id<=10;

小黑子—MySQL数据库:第二章 - 进阶篇_第102张图片

  • 查询
查看创建视图语句:SHOW CREATE VIEW 视图名称;
查看视图数据:SELECT * FROM 视图名称……;

小黑子—MySQL数据库:第二章 - 进阶篇_第103张图片

  • 修改
方式一:CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句[WITH[CASSCADED|LOCAL]CHECK OPTION]

方式二:ALTER VIEW 视图名称[(列名列表)] AS SELECT语句 [WITH [ CASCADED|LOCAL] CHECK OPTION]

在这里插入图片描述

  • 删除
DROP VIEW [IF EXISTS] 视图名称 [视图名称] ...

在这里插入图片描述

5.2 视图检查选项

注意:视图在数据库中并不存在,虽然可以往视图里添加数据,但是实际上数据是被添加到视图所对应的基表中去了,并且视图只会显示符合创建条件时的数据,超出条件范围的数据不会视图中,即在视图中找不到超出创建视图条件的数据

举例:

-- 创建视图
create or replace view stu_v_l as select id,name from student where id<=20 with local check option;
-- 往视图中插入数据实际上就是往视图所对应的基表中插入数据
-- 这里所对应的基表就是tb_user_s1 

insert into name_v_1 values (17,'Tom');
insert into name_v_1 values (20,'Jack');
-- 上面的两条数据只有(17,'Tom');能在视图中显示出来,因为(20,'Jack');超出了
-- 创建视图的条件

上面的with cascaded check option就是视图中检查选项的应用

  • 视图的检查选项
    当使用WITH CHECK OPTION子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如插入、更新、删除,以使其符合视图的定义。MySQL允许基于另一个视图创建视图,它还会检查视图中的规则以保持一致性。为了确定检查的范围,mysql提供了两个选项:CASCADEDLOCAL,默认值为CASCADED

在创建视图时使用with … check option的话,mysql就会检查视图中正在改变的每一行的数据,如果有数据不满足创建视图时的条件,那么就会报错,不允许视图改变,这里默认值就是 cascaded 不加也可以
小黑子—MySQL数据库:第二章 - 进阶篇_第104张图片

cascaded与local的区别:

5.2.1 cascaded (级联)

  • cascaded作用:
    创建视图时(这里新创建的视图记作s1),视图的基表可以是另一个视图(另一个视图记作s2),如果创建时使用cascaded,那么这个新创建的视图(s1)里的数据既要满足s1的条件,又要满足s2的条件,并且,s2在创建时并不一定有with … check option语句

    如果以s2为基表又创建一个s3的话,s3在创建时没加with … check option语句的话,s3里的数据就需要满足s1和s2的条件,不一定需要满足s3的条件(因为s3没有加with … check option语句),如果s3中也添加了with … check option语句,那么就需要同时满足s1,s2,s3的条件
    例如:
-- cascaded
create or replace view stu_v_l as select id,name from student where id<=20;
insert into stu_v_l values(5,'Tom');
insert into stu_v_l values(25,'Tom');#没有加上with local check option,mysql就不会在检查增删改操作是否违背了这个工作【

create or replace view stu_v_2 as select id,name from stu_v_l where id>=10 with local check option;
insert into stu_v_2 values(7,'Tom'); -- 报错
insert into stu_v_2 values(26,'Tom'); -- 也报错
insert into stu_v_2 values(15,'Tom'); -- 成功

create or replace view stu_v_3 as select id,name from stu_v_2 where id<=15;
insert into stu_v_3 values(11,'Tom'); -- 成功
insert into stu_v_3 values(17,'Tom'); -- 也成功
insert into stu_v_3 values(28,'Tom'); -- 报错

小黑子—MySQL数据库:第二章 - 进阶篇_第105张图片

  1. 当从父视图删除行时,子视图中相应的行也会被删除。如果子视图也有相关的子视图,则进一步进行级联删除。
  2. 当从父视图插入新行时,子视图中也会相应地插入新行。如果子视图也有相关的子视图,则进一步进行级联插入。
  3. 当从父视图更新行时,子视图也会相应地更新对应的行。如果子视图也有相关的子视图,则进一步进行级联更新。

5.2.2 local (本地)

小黑子—MySQL数据库:第二章 - 进阶篇_第106张图片

  • local:作用
    同样的,创建视图时(这里新创建的视图记作s1),视图的基表可以是另一个视图(另一个视图记作s2),如果创建时使用local,那么这个新创建的视图(s1)里的数据要满足s1的条件,然后看s2中有没有with … check option语句,如果有的话,就既要满足s1,又要满足s2,如果没有,那么就不用管基表(s2)的条件,只满足s1的条件即可

    至于这里再以s2作为基表创建新视图s3的规则就跟上面一样了,也满足local的规则
    例如:
-- local
create or replace view stu_v_4 as select id,name from student where id<=15;
insert into stu_v_4 values(5,'Tom'); -- 成功
insert into stu_v_4 values(16,'Tom'); -- 也成功

create or replace view stu_v_5 as select id,name from stu_v_4 where id>=10 with local check option;
insert into stu_v_5 values(13,'Tom'); -- 成功
insert into stu_v_5 values(17,'Tom'); -- 也成功,首先v5中id>=10通过,又有with local check option关联到v4,虽然v4id条件<=15但是没有检查选项故也通过
insert into stu_v_5 values(9,'Tom'); -- 失败

create or replace view stu_v_6 as select id,name from stu_v_5 where id< 20;
insert into stu_v_6 values(14,'Tom'); -- 也成功

小黑子—MySQL数据库:第二章 - 进阶篇_第107张图片

  1. 当从父视图删除行时,子视图中相应的行也会被删除。如果子视图也有相关的子视图,则进一步进行级联删除。
  2. 当从父视图插入新行时,子视图中也会相应地插入新行。如果子视图也有相关的子视图,则进一步进行级联插入。
  3. 当从父视图更新行时,子视图也会相应地更新对应的行。如果子视图也有相关的子视图,则进一步进行级联更新。

5.3 视图的更新

要使视图可更新,视图中的行与基础表(基表)中的行之间必须存在一对一的关系,如果视图包含以下任何一项,则该视图不可更新

比如:
在视图创建时里面包含一些

  1. 聚合函数(sum,min,max,count等),
  2. DISTINCT–distinct关键字
  3. GROUP BY – group by
  4. HAVING – having
  5. UNION或UNION ALL

举例:

-- 创建视图,使用聚合函数
create view stu_v_count as select count(*) from student;

insert into stu_v_count values(10); -- 报错

5.4 视图的作用

  1. 操作简单
    视图可以简化用户对数据的理解和操作,那些被经常使用查询的表就可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件

  2. 数据安全
    数据库可以授权,但是最低也只 能授权到表上,不能授权到数据库中特定行和特定列上,但是通过视图用户只能查询和修改他们所能见到的数据
    比如,我只想让某一个用户看到并修改学生表的id和name字段,那么就可以使用视图来使这些用户只看到id和name

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

5.5 视图案例

小黑子—MySQL数据库:第二章 - 进阶篇_第108张图片
表数据:
小黑子—MySQL数据库:第二章 - 进阶篇_第109张图片

-- 1.
create view tb_user as select as select id,name,profession,age,gender,status,createtime from tb_user;
select * from tb_user_view;

-- 2.
create view tb_stu_course_view as select s.name student_name ,s.no student_no,c.name course_name from students, student_course sc,course c where s.id = student.id

小黑子—MySQL数据库:第二章 - 进阶篇_第110张图片

6. 存储过程

存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。

存储过程思想上很简单,就是数据库SQL语言层面的代码封装与重用。

在这里插入图片描述
其特点:

  1. 封装,复用
  2. 可以接收参数,也可以返回数据
  3. 减少网络交互,效率提升

6.1 存储过程基本语法

  • 创建
CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
		--SQL语句
END;
  • 调用
CALL 名称([参数]);

例子:
小黑子—MySQL数据库:第二章 - 进阶篇_第111张图片


  • 查看
SELECT*FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx'; -- 查询指库的存储过程及状态信息

SHOWEATE PROCEDURE 储存过程名称; -- 查询某个存储过程的定义

  • 删除
DROP PROCEDURE [IF EXISTS] 存储过程

小黑子—MySQL数据库:第二章 - 进阶篇_第112张图片

但是在finalshell当中创建存储过程时,语句执行的时候却报错了
小黑子—MySQL数据库:第二章 - 进阶篇_第113张图片
该问题就出现在了分号当中,在命令行里,如果说要去执行存储过程创建的这个circle,就需要delimiter来自定义指定sql语句的执行

注意:在命令行中,执行创建存储过程的SQL时,需要通过关键字delimiter指定SQL语句的结束符。

例如:
delilmiter $$ 设置必须以$$结尾才能结束
小黑子—MySQL数据库:第二章 - 进阶篇_第114张图片

6.2 存储过程变量

6.2.1 系统变量

系统变量是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)会话变量(SESSION)

会话是什么?
指的是开启多个同类的窗口会话,一个会话里的变量不会影响另一个会话窗口
在这里插入图片描述

  • 查看系统变量
show [session|global] variables; --查看所有系统变量
show [session|global] variables like'...'; --可以通过like模糊匹配方式查找变量
select @@[session|global] 系统变量名; --查看指定变量的值


小黑子—MySQL数据库:第二章 - 进阶篇_第115张图片
小黑子—MySQL数据库:第二章 - 进阶篇_第116张图片

  • 设置系统变量
set [session|global] 系统变量名=;
set @@[session|global] 系统变量名=;

注意:

如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量。
mysql服务重新启动之后,所设置的全局参数会失效,要想不失效,可以在/etc/my.cnf 中配置。

6.2.2 用户定义变量

用户定义变量是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用"@变量名" 使用就可以。其作用域为当前连接

  • 赋值
set @var_name = expr [,@var_name = expr]...;
set @var_name = expr [,@var_name := expr]...;
select @var_naem := expr [,@var_naem:=expr]...;
select 字段名 INTO @var_name FROM 表名

比较推荐在赋值的时候使用:=
例子:
小黑子—MySQL数据库:第二章 - 进阶篇_第117张图片

  • 使用
select @var_naem;

例子:
小黑子—MySQL数据库:第二章 - 进阶篇_第118张图片

注意:用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL

6.2.3 局部变量

局部变量是根据定义的在局部生效的变量,访问之前,需要DECLARE声明。可用作存储过程变量和输入参数,局部变量的范围是在其内声明的BEGIN...END

  • 声明
declare 变量名 变量类型[default...];

变量类型是数据库字段类型:int,bigint,char,varchar,date,time等

  • 赋值
set 变量名=;
set 变量名:=;
select 字段名 into 变量名 from 表名...;

例子:
小黑子—MySQL数据库:第二章 - 进阶篇_第119张图片

6.3 if条件判断

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

案例:在这里插入图片描述

create procedure p3()
begin
    declare score int default 58;
    declare result varchar(10);

    if score >=85 then
        set result := '优秀';
    elseif score>=60 then
        set result := '及格';
    else
        set result := '不及格';
    end if;
    select result;
end;

call p3();

小黑子—MySQL数据库:第二章 - 进阶篇_第120张图片

6.4 参数

类型 含义 备注
IN 该类参数作为输入,也就是需要调用时传入值 默认
OUT 该类参数作为输出,也就是该参数可以作为返回值
INOUT 即可以作为输入参数,也可以作为输出参数

用法:

create procedure 储存过程名称([IN/OUT/INOUT 参数名 参数类型])
begin
		-- SQL语句
END;

练习2:
小黑子—MySQL数据库:第二章 - 进阶篇_第121张图片

create procedure p4(in score int,out result varchar(10))
begin
    if score >=85 then
        set result := '优秀';
    elseif score>=60 then
        set result := '及格';
    else
        set result := '不及格';
    end if;
    select result;
end;

call p3(68,@result);
create procedure p5(inout score double)
begin
    set score := score * 0.5;
end;

set @score = 198;
call p5(@score);
select @score;

6.5 case

  • 语法一
CASE case_value
		WHEN when_value1 then statement_list1
		[WHEN when_value2 then statement_list2]...
END CASE;
  • 语法二
CASE case_value
		WHEN search_condition1 then statement_list1
		[WHEN search_condition2 then statement_list2]...
END CASE;

练习:
小黑子—MySQL数据库:第二章 - 进阶篇_第122张图片

create procedure p6(IN month int)
begin
    declare result varchar(10);

    case
        when month >= 1 and month <=3 then
            set result := '第一季度';
        when month >= 4 and month <=6 then
            set result := '第二季度';
        when month >= 7 and month <=9 then
            set result := '第三季度';
        else
            set result := '非法参数';
    end case;

    select concat('你输入的月份为:',month,',所属的季度为:',result);
end;

call p6(4);

在这里插入图片描述

6.6 循环

6.6.1 while 循环

while循环是有条件的循环控制语句。满足条件时候,再执行循环体中的SQL语句。具体语法为:

#先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
while 条件 DO
		SQL逻辑
end while;

练习:
小黑子—MySQL数据库:第二章 - 进阶篇_第123张图片

-- A. 定义局部变量,记录累加之后的值;
-- B. 每次循环一次,就会对n进行减1,如果n减到0,则退出循环
create procedure p7(in n int)
begin
    declare total int default 0;
    while n>0 do
        set total := total +n;
        set n := n-1;
        end while;
    select total;
end;

call p7(10);

小黑子—MySQL数据库:第二章 - 进阶篇_第124张图片

6.6.2 repeat 循环

repeat是有条件的循环控制语句,当满足条件的时候退出循环。具体语法为:

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

练习:
小黑子—MySQL数据库:第二章 - 进阶篇_第125张图片

-- A. 定义局部变量,记录累加之后的值;
-- B. 每次循环一次,就会对n进行减1,如果n减到0,则退出循环
create procedure p8(in n int)
begin
    declare total int default 0;
    repeat
        set total := total+n;
        set n := n-1;
    until  n<=0
    end repeat;
    select total;
end;

call p8(10);

6.6.3 loop 循环

loop实现简单的循环,如果不在SQL逻辑中怎加退出循环的条件,可以用其来实现简单的死循环。loop可以配合一下两个语句使用:

  • leave:配合循环使用,退出循环
  • iterate :必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环
[begin_label:]loop
		SQL逻辑...
end loop [end_label];
leave label; -- 退出指定标记的循环体
iterate label; -- 直接进入下一次循环

练习:
小黑子—MySQL数据库:第二章 - 进阶篇_第126张图片
1、

create procedure p9(in n int)
begin
    declare total int default 0;
    sum:loop
        if n<=0 then
            leave sum;
        end if;
        set total := total + n;
        set n := n -1;
    end loop;
    select total;
end;

call p9(100);

2、

-- loop计算从1到n之问的偶数累加的值,n为传入的参数值。
-- A.定文局部变量,记录累加之后的值;
-- B.每循环一次,就会对n进行-1,如果n减到8,则退出循环―---> leave xx
-- C.如果当次累加的数据是奇数,则直接进入下一次循环. --------> iterate xx
create procedure p10(in n int)
begin
    declare total int default 0;
    sum:loop
        if n<=0 then
            leave sum;
        end if;

        if n%2 = 1 then
            set n:= n-1;
            iterate sum;
        end if;

        set total := total + n;
        set n := n -1;
    end loop;
    select total;
end;

call p10(10);

6.7 cursor 游标

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

  • 声明游标
declare 游标名称 cursor for 查询语句;
  • 打开游标
open 游标名称;
  • 获取游标记录
fetch 游标名称 into 变量[,变量];
  • 关闭游标
close 游标名称;

练习:
小黑子—MySQL数据库:第二章 - 进阶篇_第127张图片


create procedure p11(in uage int)
begin
    -- 要先声明变量再声明游标
    declare uname varchar(100);
    declare upro varchar(100);
    declare u_cursor cursor for select name,profession from tb_user where age <= uage;


    drop table if exists tb_user_pro;
    create table if not exists tb_user_pro(
      id int primary key auto_increment,
    name varchar(100),
    profession varchar(100)
    );

    open u_cursor;
    while true do
        fetch u_cursor into uname,upro;
        insert into tb_user_pro values (null,uname,upro);
    end while;
    # 循环报错是因为while true会一直循环下去,在循环的过程当中,每循环一次就需要从游标当中去获取下一条的记录
    -- 在吧这个记录赋值uname和upro,当循环到最后一条之后再进行下一次循环时,游标里没有数据了,所以此时再去获取游标当中的数据就会报错
    close u_cursor;
end;

call p11(40);

6.8 handler 条件处理程序

条件处理程序(handler)可以用来定义再流程控制结构执行过程中遇到时相应的处理步骤。具体语法为:

declare handler_action handler for condition_value [.condition_value] ... statement;

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

对于上例while循环报错问题进行解决:


create procedure p11(in uage int)
begin
    -- 要先声明变量再声明游标
    declare uname varchar(100);
    declare upro varchar(100);
    declare u_cursor cursor for select name,profession from tb_user where age <= uage;
    declare exit handler for SQLSTATE '02000' close u_cursor; # not found等替换

    drop table if exists tb_user_pro;
    create table if not exists tb_user_pro(
      id int primary key auto_increment,
    name varchar(100),
    profession varchar(100)
    );

    open u_cursor;
    while true do
        fetch u_cursor into uname,upro;
        insert into tb_user_pro values (null,uname,upro);
    end while;
    # 循环报错是因为while true会一直循环下去,在循环的过程当中,每循环一次就需要从游标当中去获取下一条的记录
    -- 在吧这个记录赋值uname和upro,当循环到最后一条之后再进行下一次循环时,游标里没有数据了,所以此时再去获取游标当中的数据就会报错
    close u_cursor;
end;

call p11(40);

M有SQL官方文档状态栏含义

6.9 存储函数

存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的。具体语法如下:

create function 存储函数名称({参数列表})
returns type [characteristic...]
begin
	--SQL语句
	return...;
end;

characteristic说明:
-- deterministic:相同的输入参数总是产生相同的结果
-- no sql:不含sql语句
-- reads sql data:包含读取数据的语句,但不包含写入数据的语句

练习:
小黑子—MySQL数据库:第二章 - 进阶篇_第128张图片

create function fun1(n int)
returns int deterministic -- deterministic用于解决指定DETERMINISTIC, NO SQL, or READS SQL DATA的特性报错
begin
    declare total int default 0;
    while n>0 do
        set total := total+n;
        set n := n-1;
     end while;
    return total;
end;

select fun1(50);

小黑子—MySQL数据库:第二章 - 进阶篇_第129张图片

7. 触发器

触发器是与表有关的数据库对象,指再insert/update/delete之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用再数据库端确保数据的完整性,日志记录,数据校验等操作
使用别名OLD和NEW来引用触发器中发生的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句触发

小黑子—MySQL数据库:第二章 - 进阶篇_第130张图片

7.1 insert 类型

  • 创建
create trigger trigger_name
before/after insert/update/delete
on tbl_name for each row  -- 行级触发器
begin
		trigger_stmt;
end;
  • 查看
show triggers;
  • 删除
drop trigger [schema_name.]trigger_name; -- 如果没有指定schema_name,默认为当前数据库

练习:
小黑子—MySQL数据库:第二章 - 进阶篇_第131张图片

create table user_logs(
    id int(11) not null auto_increment primary key ,
    operation varchar(20) not null comment '操作类型,insert/update/delete',
    operate_time datetime not null comment '操作时间',
    operate_id int not null comment '操作ID',
    operate_params varchar(500) comment '操作参数'
)engine=innodb default charset = utf8;

create trigger tb_user_insert_trigger
    after insert on tb_user for each row
begin
    insert into user_logs(id,operation,operate_time,operate_id,operate_params)values
    (null,'insert',now(),new.id,concat('插入的数据内容为:id=',new.id,',name=',new.name,'phone=',new.phone,'email=',new.email));
end;
-- 查看
show triggers ;
-- 删除
drop trigger tb_user_insert_trigger;

-- 插入数据到tb_user
insert into tb_user(id,name,phone,email)values (25,'二皇子','123456789','[email protected]')

7.2 update 类型

create trigger user_logs_trigger2
    after update on student for each row
    begin
        insert into user_logs(id,operation,operate_time,operate_id,operate_params) values
        (null,'insert',now(),NEW.id,
         concat('更新之前数据为:id=',old.id,'name=',old.name,'num=',old.num
                ,'更新之后的数据为:id=',new.id,'name=',new.name,'num=',new.num));
    end;

show triggers;

7.3 delete 类型

create trigger user_logs_delete_trigger
    after update on student for each row
    begin
        insert into user_logs(id,operation,operate_time,operate_id,operate_params) values
        (null,'delete',now(),OLD.id,
         concat('删除之前的数据为:id=',old.id,'name=',old.name,'num=',old.num));
    end;

7.4 视图&存储过程&触发器小结

小黑子—MySQL数据库:第二章 - 进阶篇_第132张图片

8. InnoDB 引擎

8.1 逻辑存储结构

小黑子—MySQL数据库:第二章 - 进阶篇_第133张图片

8.2 架构

M)SQL5.5版本开始,默认使用InoDB存储引擎,它擅长事务处理,具有崩溃恢复特性,在日常开发中使用非常广泛。下面是InnoDB架构图,左侧为内存结构,右侧为磁盘结构。
小黑子—MySQL数据库:第二章 - 进阶篇_第134张图片

8.2.1 内存结构

假如说没有缓冲区,意味着我们执行增删改查的时候,每一次数据库都要区操作磁盘空间,那么就会存在大量的磁盘io,而且在业务比较复杂的系统当中会产生大量的随机磁盘io,所以此时是非常耗性能的

因此有了缓冲区之后,就不用每一次都去操作磁盘文件了。可以直接操作缓冲区,然后每隔断时间或者说触发了某一种机制之后,再将缓冲区当中的数据再刷到磁盘当中就可以了

Buffer Pool:缓冲池
小黑子—MySQL数据库:第二章 - 进阶篇_第135张图片
Change Buffer:更矮缓冲区
小黑子—MySQL数据库:第二章 - 进阶篇_第136张图片

adaptive_hash_index:自适应哈希
小黑子—MySQL数据库:第二章 - 进阶篇_第137张图片
例如:
小黑子—MySQL数据库:第二章 - 进阶篇_第138张图片

Log Buffer:日志缓冲区
小黑子—MySQL数据库:第二章 - 进阶篇_第139张图片

默认大小:
在这里插入图片描述
刷新时机:
小黑子—MySQL数据库:第二章 - 进阶篇_第140张图片

8.2.2 磁盘结构

小黑子—MySQL数据库:第二章 - 进阶篇_第141张图片

  • System Tablespace:系统表空间
    小黑子—MySQL数据库:第二章 - 进阶篇_第142张图片
  • File-Per-Table:表空间文件
    小黑子—MySQL数据库:第二章 - 进阶篇_第143张图片

小黑子—MySQL数据库:第二章 - 进阶篇_第144张图片

  • General Tablespaces:通用表空间
    通用表空间需要自己创建,并且在创建的时候需要自己区指定关联的表空间是谁
    小黑子—MySQL数据库:第二章 - 进阶篇_第145张图片
    小黑子—MySQL数据库:第二章 - 进阶篇_第146张图片

  • Undo Tablespaces:撤销表空间
    撤销表空间对应的磁盘文件
    在这里插入图片描述

  • Temporary Tablespaces:临时表空间

小黑子—MySQL数据库:第二章 - 进阶篇_第147张图片

  • Doublewrite Buffer Files:双写缓冲区
    在这里插入图片描述
  • Redo Log:重做日志

8.2.3 后台线程

内存结构中的数据是怎么刷新到磁盘当中的呢?

这个就涉及到了一组后台线程
小黑子—MySQL数据库:第二章 - 进阶篇_第148张图片

后台线程其作用:将InnoDB存储引擎的缓冲池当中的数据,在合适的时机刷新到磁盘文件当中
在这里插入图片描述

  1. Master Thread
    核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中,保持数据的一致性,还包括脏页的刷新、合并插入缓存、undo页的回收

  2. IO Thread
    在InnoDB存储引擎中大量使用了AIO来处理IO请求,这样可以极大地提高数据库的性能,而IOThread主要负责这些lO请求的回调

线程类型 默认个数 职责
Read thread 4 负责读操作
Write thread 4 负责写操作
Log thread 1 负责将日志缓冲区刷新到磁盘
Insert buffer thread 1 负责将写缓冲区内容刷新到磁盘
show engine innodb status;

小黑子—MySQL数据库:第二章 - 进阶篇_第149张图片

  1. Purge Thread
    主要用于回收事务已经提交了的undo log,在事务提交之后,undo log可能不用了,就用它来回收

  2. Page Cleaner Thread
    协助Master Thread刷新脏页到磁盘的线程,它可以减轻Master Thread 的工作压力,减少阻塞

  • 对InnoDB引擎的整个体系结构,当我们业务在操作的时候,那么会直接区操作这一块的缓冲区,如果缓冲区当中没有数据会将磁盘当中的数据加载回来,然后再存储再缓冲区当中。
  • 我们再增删改查的时候,都会去操作这一块的缓冲区,缓冲区当中的数据会以一定的频率或者说一定的时机,要通过这组后台线程刷新到磁盘当中,然后在磁盘当中进行永久化地保留下来

8.3 事务原理

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

●特性

  • 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
  • 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
  • 隔离性(Ilsolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
  • 持久性(Durability)︰事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。

事务原理:
小黑子—MySQL数据库:第二章 - 进阶篇_第150张图片

8.3.1 redo log

小黑子—MySQL数据库:第二章 - 进阶篇_第151张图片

重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。
该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file) ,前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改傣息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。
小黑子—MySQL数据库:第二章 - 进阶篇_第152张图片

8.3.2 undo log

在这里插入图片描述

回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和MVCC(多版本并发控制)。

undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。

  • Undo log销毁: undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日志可能还用于MVCC。
  • Undo log存储: undo log采用段的方式进行管理和记录,存放在前面介绍的 rolback segment回滚段中,内部包含1024个undo logsegment。

8.4 MVCC(多版本并发控制)

当前读:
读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记 录进行加锁。对于我们日常的操作,如:select...lock in share mode(共享锁)select... for updateupdateinsertdelete(排他锁)都是一种当前读。
小黑子—MySQL数据库:第二章 - 进阶篇_第153张图片
当事务提交了也查不到,是因为当前隔离级别是repeatable read,保证了可重复读,所以使用当前读读到最新数据

快照读:
简单的select (不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。

  • Read Committed:每次select,都生成一个快照读。

  • Repeatable Read:开启事务后第一个select语句才是快照读的地方。

  • Serializable:快照读会退化为当前读。

MVCC:
全称 Muti- version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现MCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView.

8.4.1 隐藏字段

小黑子—MySQL数据库:第二章 - 进阶篇_第154张图片

隐藏字段 含义
DB_TRX_ID 最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID
DB_ROLL_PTR 回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本
DB_ROW_ID 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段

8.4.2 undo log版本链

回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。
当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。
而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。

小黑子—MySQL数据库:第二章 - 进阶篇_第155张图片
小黑子—MySQL数据库:第二章 - 进阶篇_第156张图片
小黑子—MySQL数据库:第二章 - 进阶篇_第157张图片

不同事务或相同事务对同一条记录进行修改,会导致该记录的undo log日志生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录

那么当我们进行查询的时候,到底应该返回哪一个版本呢?由 ReadView组件来控制

8.4.3 ReadView介绍

ReadView(读视图)是快照读SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务〈未提交的) id。ReadView中包含了四个核心字段:

字段 含义
m_ids 当前活跃的事务ID集合
min_trx_id 最小活跃事务ID
max_trx_id 预分配事务ID,当前最大事务ID+1(因为事务ID是自增的)
creator_trx_id ReadView创建者的事务ID
  • readview
    小黑子—MySQL数据库:第二章 - 进阶篇_第158张图片

不同的隔离级别,生成ReadView的时机不同:

read committed:在事务中每一次指向快照读时生成readview
repeatable read:仅在事务中第一次指向快照读时生成readview,后续复用该readview

8.4.4 原理分析

8.4.4 - I RC级别

RC隔离级别下,在事务中每一次指向快照时生成readview
小黑子—MySQL数据库:第二章 - 进阶篇_第159张图片

8.4.4 - II RR级别

RR隔离级别下,仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView
小黑子—MySQL数据库:第二章 - 进阶篇_第160张图片
小黑子—MySQL数据库:第二章 - 进阶篇_第161张图片

8.5 InnoDB小结

小黑子—MySQL数据库:第二章 - 进阶篇_第162张图片

9. MySQL管理

9.1 系统数据库

Mysql数据库安装完成后,自带了一下四个数据库,具体作用如下:

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

9.2 常用工具

9.2.1 mysql

该mysql不是指mysql服务,而是指mysql的客户端工具

语法:
	mysql [options] [dataabase]
选项:
	-u, --uesr=name #指定用户名
	-p, --password[=name] #指定密码
	-h, --host=name	#指定服务器IP或域名
	-p. --port=port #指定连接端口
	-e, --execute=name #指向SQL语句并退出

-e选项可以在msql客户端指向SQL语句,而不用连接到mysql数据库再执行,对于一些批处理脚本,这种方式尤其方便

示例:
	mysql -uroot-p123455 db01 -e "select*from stu";

小黑子—MySQL数据库:第二章 - 进阶篇_第163张图片

9.2.2 mysqladmin

mysqladmin是一个执行管理操作的客户端程序。可以用它来检查服务器的配置和当前状态、创建并删除数据库等。
小黑子—MySQL数据库:第二章 - 进阶篇_第164张图片
例如:
小黑子—MySQL数据库:第二章 - 进阶篇_第165张图片

9.2.3 mysqlbinlog

由于服务器生成的二进制日志文件以二进制格式保存,所以如果想要检查这些文本的文本格式,就会使用到mysqlbinlog日志管理工具。

语法:
	mysqlbinlog [options] log-files1 log-files2 ...
	
选项:
	-d, --database=name 	指定数据库名称,只列出指定的数据库相关操作。
	-O, --offset=# 	忽略掉日志中的前n行命令。
	-r,--result-file=name 	将输出的文本格式日志输出到指定文件。
	-s, --short-form	显示简单格式,省略掉一些信息。
	--start-datatime=date1 --stop-datetime=date2	 指定日期间隔内的所有日志。
	--start-position=pos1 --stop-position=pos2	 指定位置间隔内的所有日志。

9.2.4 mysqlshow

mysqlshow客户端对象查找工具,用来很快地查找存在哪些数据库、数据库中的表、表中的列或者索引

语法:
	mysqlshow [options] [db_name [table_name[col_name]]]
选项:
	--count	显示数据库及表的统计信息(数据库,表均可以不指定)
	-i	显示指定数据库或者指定表的状态信息
示例:
	#查询每个数据库的表的数量及表中记录的数量
	mysqlshow-uroot -p2143 --count
	
	#查询test库中每个表中的字段书,及行数
	mysqlshow -uroot-p2143 test --count
	
	#查询test库中book表的详细情况
	mysqlshow -uroot -p2143 test book --count

例如:
小黑子—MySQL数据库:第二章 - 进阶篇_第166张图片

9.2.5 mysqldump

mysqldump客户端工具用求备份数据库或在不同数据库之间进行数据迁移。备份内容包含创建表,及插入表的SQL语句。

语法:
	mysqldump [options] db_name [tables]
	mysqldump [options] --databasel-B db1[db2 db3...]
	mysqldump [options] --all-databases/-A
连接选项:
	-U, --USer=name		指定用户名
	-p,--password[=name]	指定密码	
	-h, --host=name		指定服务器ip或域名
	-P, --port一#		指定连接端口
输出选项:
	--add-drop-database	在每个数据库创建语句前加上drop database语句
	--add-drop-table	在每个表创建语句前加上 drop table语句,默认开启;不开启(--skip-add-drop-table)
	-n, --no-create-db	不包含数据库的创建语句
	-t, --no-create-info	不包含数据表的创建语句
	-d --no-data	不包含数据
	-T, --tab=name	自动生成两个文件:一个.sql文件,创建表结构的语句;一个.txt文件,数据文件

案例:
小黑子—MySQL数据库:第二章 - 进阶篇_第167张图片
mysql信任的文件存放目录: /var/lib/mysql-files/
小黑子—MySQL数据库:第二章 - 进阶篇_第168张图片
所以在备份时,应该备份在这个目录下

9.2.6 mysqlimport/source

mysqlimport是客户端数据导入工具,用来导入mysqldump加-T参数后导出的文本文件

语法:
	mysqlimport [options] db_name textfile [textfile2...]
示例:
	mysqlimport -uroot -p1234 test /tmp/city.txt

如果需要导入sql文件,可以使用mysql中的source指令:

语法:
	source/root/xxxxx.sql

9.3 mysql管理小结

小黑子—MySQL数据库:第二章 - 进阶篇_第169张图片

10. 进阶篇总结

小黑子—MySQL数据库:第二章 - 进阶篇_第170张图片

你可能感兴趣的:(MySQL,数据库,数据库,mysql)