二 MySQL进阶

系列文章目录


文章目录

  • 系列文章目录
    • 1、存储引擎
      • 1.1 MySQL体系结构
        • (1)连接层
        • (2)服务层
        • (3)引擎层
        • (4) 存储层
      • 1.2 存储引擎简介
      • 1.3 存储引擎特点
        • (1)InnoDB
        • (2)MyISAM
        • (3)Memory
      • 区别及特点
      • 1.4 存储引擎选择
    • 2、索引
      • 2.1索引概述
      • 2.2 索引结构
        • 概述
        • 二叉树
        • B-Tree
        • B+Tree
        • Hash
      • 2.3索引分类
      • 2.4 索引语法
      • 2.5 SQL性能分析
        • SQL 的执行频率
        • 慢查询日志
        • profile详细
        • explain
      • 2.6索引使用
        • 最左前缀索法则(联合索引)
        • SQL提示
        • 覆盖索引
        • 前缀索引
        • 单列索引与联合索引
      • 2.7 索引设计原则
    • 3、SQL优化
      • 插入数据
      • 主键优化
      • order by优化
      • group by优化
      • limit优化
      • count优化
      • update优化
    • 4、视图/存储过程/触发器
      • 4.1 视图
      • 4.2 存储过程
      • 4.3 存储函数
      • 4.4 触发器
    • 5、锁
      • 全局锁
      • 表级锁
        • 表锁
        • 元数据锁
        • 意向锁
      • InnoDB行级锁
    • 6、InnoDB引擎
      • 事务


1、存储引擎

Innodb存储引擎,每次读取16k

1.1 MySQL体系结构

二 MySQL进阶_第1张图片

(1)连接层

​ 最上层是一些客户端和连接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。主要完成连接处理,授权认证以及相关的安全方案。该层引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它具有的操作权限。

(2)服务层

​ 大部分核心服务功能在该层实现。如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有的跨存储引擎功能也在这一层实现,如过程,函数等。在该层,服务器会解析查询并创建相应的内部解析树,并完成相应的优化,如确定表的查询顺序,是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存,如果缓存空间足够大,可以在大量的读操作中提高系统性能。

(3)引擎层

​ 存储引擎层, 存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。数据库的索引(Index)是在存储引擎层实现的。在MySQL5.5版本之后,默认的存储为Innodb。

(4) 存储层

​ 数据存储层, 主要是将数据(如: redolog、undolog、数据、索引、二进制日志、错误日志、查询、日志、慢查询日志等)存储在文件系统之上,并完成与存储引擎的交互。

​ 和其他数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎上,插件式的存储引擎架构,将查询处理和其他的系统任务以及数据的存储提取分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。

1.2 存储引擎简介

​ MySQL数据库的核心,我们需要在合适的场景选择合适的存储引擎。

​ 存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。我们可以在创建表的时候,来指定选择的存储引擎,如果没有指定将自动选择默认的存储引擎。在MySQL5.5版本之后,默认的存储为Innodb。

(1)建表指定引擎

create table 表名()engine=innodb

(2)查询当前数据库支持引擎

show engines;

(3)查询建表语句

show create table 表名

1.3 存储引擎特点

(1)InnoDB

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

特点

  1. DML(增删改)操作遵循ACID(原子性,一致性,隔离性,持久性),支持事务
  2. 行级锁,提高并发访问性能。
  3. 支持外键约束foreign key约束,保持数据的完整性和正确性;

文件

xxx.ibd:innoDB引擎的每张表都会对应这样一个表空间文件,存储该表结构(frm-早期的格式、新版的)、数据和索引。该文件是基于二进制存储的,不能直接打开,可以用MySQL提供的指令 ibd2sdi 名.idb 通过该指令就可以从ibd文件中提取sdi信息,而sdi数据字典信息中就包含该表结构。

逻辑存储结构

二 MySQL进阶_第2张图片

  • 表空间(Tablespace):InnoDB存储引擎逻辑结构的最高层,ibd文件其实就是表空间,在表空间中可以包含多个Segment段。
  • 段(Segment):表空间是由各个段组成的,常见的段有数据段,索引段,回滚段等。InnoDB中对于段的管理,都是引擎自身完成,不不需要人为对其控制,一个段中包含多个区。
  • 区(Extent):区是表空间的单元结构,每个区的大小为1M。默认情况下,InnoDB存储引擎页大小为16K,即一个区中一共有64个连续的页。
  • 页(Page):页是组成区的最小单元,页也是InnoDB存储引擎磁盘管理的最小单元,每个页的大小默认 为16kb。为保证页的连续性,InnoDB存储引擎每次从磁盘申请4-5个区。
  • 行(Row):InnoDB存储引擎是面向行的,也就是说数据是按行进行存放的,在每一行中除了定义表示所指定的字段意外,还包含两个隐藏字段。
(2)MyISAM

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

特点

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

文件

xxx.sdi:存储表结构信息

xxx.MYD:存储数据

xxx.MYI:存储索引

(3)Memory

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

特点

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

文件

xxx.sdi:存储表结构信息

区别及特点

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

1.4 存储引擎选择

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

  • InnoDB;是MySQL的默认存储引擎,支持事务,外键。如果对事务的完整性有比较高的要求,在并发条件下要求数据一致性,数据操作除了插入和查询之外,还包含很多更新、删除操作,那么InnoDB存储引擎是比较合适的选择。
  • MyISM:如果是以读操作和插入操作为主,只有少量的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择MyISM
  • MEMORY:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性。

2、索引

2.1索引概述

​ 索引(index)是存储表中的一个特定列值数据结构(最常见的B-Tree) 。索引是在表的列上创建。索引是一种数据结构

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

例如:我们要执行查询语句

(1)无索引情况,就需要从第一行扫描到最后一行。称之为全表扫描,性能很低。

(2)有索引情况,假如对这张表建立了索引结构为二叉树,对age建立一个二叉树索引结构。如下图:

二 MySQL进阶_第3张图片
此时只需要扫描几次,就可以找到需要的数据了,极大的提高了查询的效率。

特点

优势 劣势
提高数据检索的效率,降低数据库的IO成本 索引列也是要占用空间的
通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗 提高查询效率的同时降低更新表(insert,update,delete)的速度

2.2 索引结构

概述

MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的结构

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

引擎支持

索引 InnoDB MyISAM Memory
B+Tree索引 支持 支持 支持
Hash索引 - - 支持
R-tree索引 - 支持 -
Full-text 5.6版本后支持 支持 -
二叉树

理想结构如下:

二 MySQL进阶_第4张图片
但是如果是顺序插入就会生成一个单向列表,结构如下:
二 MySQL进阶_第5张图片
二叉树作为索引结构缺点

  • 顺序插入时,会形成一个链表,查询性能大大降低
  • 大量数据情况下,层级深,检索速度慢。

红黑树

选择红黑树即使在顺序插入数据,最终形成的数据结构也是一颗平衡的二叉树。结构如下:

红黑树:图解插入示例
二 MySQL进阶_第6张图片
即使如此,红黑树也是二叉树,也会存在同样的缺点:

  • 大量数据情况下,层级较深,检索速度慢
B-Tree

B-Tree,B树是一种多叉路平衡查找树,B树每个节点可以有多个分支,及多叉。一棵树的最大度数(max-degree)为5(5阶)的b-tree为例,这个B树每个节点最多存储4个key,5个指针。

B-Tree可视化网站

B+Tree

MySQL索引数据结构对B+Tree进行了优化。在B+Tree基础上,增加了一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问性能。

B+Tree可视化网站 B+Tree介绍

结构如下:

指针比键值多一个
二 MySQL进阶_第7张图片

  • 上部分绿色框起来的部分是索引部分,仅仅起到索引数据的作用,不存储数据。
  • 下部分红色框起来的部分是数据存储部分,在其叶子节点存储具体的数据。

B+Tree与B-Tree的区别

  • 所有的数据都在叶子节点
  • 叶子节点形成了一个单项链表
  • 非叶子节点仅仅起到索引数据作用,具体数据都是在叶子节点存放的。
Hash

MySQL中除了支持B+Tree索引,还支持一种索引类型–Hash索引,如Mermory存储引擎就支持Hash索引。

哈希表(hash)表

​ 哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

数组内存是连续的,可以直接通过下标访问,随机访问,但是插入修改困难。而链式链表是用一组任意的存储单元来存储数据,在插入和删除时,可以通过修改指针完成。综合起来就是一个链表的数组。如哈希表的一种实现方法–拉链法。

(1)结构

哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。
二 MySQL进阶_第8张图片
(2)特点

  1. Hash索引只能用于等值比对(=,in)不支持范围查询(between,>,<…)
  2. 无法通过索引完成排序操作
  3. 查询效率高(在不存在Hash冲突的情况)只需要检索一次,效率通常要高于B+Tree索引

(3)存储引擎支持

​ 在MySQL中,只有memory存储引擎支持Hash索引。而InnoDB中有自适应hash功能,而hash索引是InnoDB存储引擎根据B+Tree索引在指定条件下构建的。

为什么InnoDB使用B+Tree索引结构?

  1. 相比于二叉树,层级更少,搜索效率高;(如循序插入时,二叉树会形成一个单项链表)
  2. 相比于B-tree,无论是叶子节点还是非叶子结点都会保存数据,导致一页中存储的键值减少,指针跟着减少,要保存大量数据时,只能增加数的高度,导致性能降低。
  3. 对于Hash索引只支持等值匹配,而B+Tree支持范围匹配及排序操作

2.3索引分类

分类 含义 特点 关键字
主键索引 主键创建的索引 默认创建,只能有一个 PRIMARY
唯一索引 避免同一个表中数据列的值重复 可以有多个 UNIQUE
常规索引 快速定位特定数据 可以有多个
全文索引 全文索引查找的是文本中的关键词,而不是比较索引中的值 可以有多个 FULLTEXT

存储形式分类

分类 含义 特点
聚集索引(ClusteredIndex) 将数据存储于索引放到一块,索引结构的叶子节点保存了行数据 必须有而且只有一个
二级索引(SeconddaryIndex) 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 可以存在多个

聚集索引选取规则:

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

聚集索引和二级索引结构如下:
二 MySQL进阶_第9张图片

  • 聚集索引的叶子节点下挂的是这一行的数据
  • 二级索引的叶子节点下挂的是该字段对应主键值
  • 二 MySQL进阶_第10张图片
    具体过程如下:

①. 由于是根据name字段进行查询,所以先根据name='Arm’到name字段的二级索引中进行匹配查找。但是在二级索引中只能查找到 Arm 对应的主键值 10。②. 由于查询返回的数据是*,所以此时,还需要根据主键值10,到聚集索引中查找10对应的记录,最终找到10对应的行row。 ③. 最终拿到这一行的数据,直接返回即可。

回表查询:先到二级索引中查找数据,找到主键值,然后再到索引中根据主键值,获取数据的方法。

2.4 索引语法

创建索引

CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (index_col_name,... ) ; 

查看索引

SHOW INDEX FROM table_name

删除索引

DROP INDEX index_name on table_name

建表

CREATE TABLE tb_user( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', uname 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 '系统用户表';

需求如下

(1) name字段为姓名字段,该字段的值可能会重复,为该字段创建索引。

create index index_user_uname on tb_user(uanme);

(2) phone手机号字段的值,是非空,且唯一的,为该字段创建唯一索引。

create unique index index_user_phone on tb_user(phone);

(3)为profession、age、status创建联合索引(它有顺序之分)。

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

(4)为email建立合适的索引来提升查询效率。

create index index_email on tb_user(email);

2.5 SQL性能分析

SQL 的执行频率

​ MySQL客户端连接成功后,通过show [session/global] status 命令可以提供服务器状态信息。通过指令可以查看当前数据库insert,update,delete的访问频次;

-- session 是查看当前会话 ; -- global 是查询全局数据 ;
show global status like 'Com_______';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Com_binlog    | 0     |
| Com_commit    | 20    |
| Com_delete    | 1     |
| Com_insert    | 6399  |
| Com_repair    | 0     |
| Com_revoke    | 0     |
| Com_select    | 179   |
| Com_signal    | 0     |
| Com_update    | 6     |
| Com_xa_end    | 0     |
+---------------+-------+
慢查询日志

​ 慢查询日志记录了所有执行时间超过参数(long_query_time,默认10秒)的所有sql语句的日志。

mysql> show variables like 'slow_query_log';
+----------------+-------+
| Variable_name  | Value |
+----------------+-------+
| slow_query_log | ON    |
+----------------+-------+

如果要开启需要在MySQL的配置文件中配置以下信息

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

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

MySQL5.7–配置慢查询(windows10)

通过慢查询日志,就可以定位出执行效率比较低的SQL,从而有针对性的进行优化。

profile详细

查看profile能够帮助我们了解时间都耗费到哪里去了。通过 select @@profiling参数,能够看到当前MySQL是否支持profile操作

select @@having_profile;-- 查看当前是否支持查看profile

执行一系列的业务SQL操作执行以下指令的执行耗时;

-- 查看每一条SQL的耗时基本情况
show profiles;
-- 查看指定query_id的SQL语句各个阶段的耗时情况
show profile query id;
-- 查看指定query_id的申SQL语句的CPU使用情况
show profile cpu for query id;
explain

explain 或者 desc命名获取如何执行select语句的信息,包括在select语句执行过程中如何连接表的顺序。

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

二 MySQL进阶_第11张图片

2.6索引使用

验证索引效率:直接查看语句执行时间

二 MySQL进阶_第12张图片

最左前缀索法则(联合索引)

​ 如果索引了多列(联合索引),要遵守最左前缀法则。就是从索引的最左列开始并不跳过索引中的列。如果跳跃某一列,索引可能会部分失效(后边的字段索引失效)

(貌似与where后的语句顺序无关,语法优化器会帮我们优化为按照索引的顺序)
二 MySQL进阶_第13张图片看key_len的大小;来判断走的索引
二 MySQL进阶_第14张图片
二 MySQL进阶_第15张图片
如果MySQL评估使用索引比全表扫描更慢,就不使用索引。不是聚集索引的话就会,先走索引找到主键值回表扫描。

​ 就是因为MySQL在查询时,会评估使用索引的效率与走全表扫描的效率,如果走全表扫描更快,则放弃索引,走全表扫描。 因为索引是用来索引少量数据的,如果通过索引查询返回大批量的数据,则还不如走全表扫描来的快,此时索引就会失效。

is null is not null
二 MySQL进阶_第16张图片
一模一样的SQL语句,先后执行了两次,结果查询计划是不一样的,为什么会出现这种现象,这是和数据库的数据分布有关系。查询时MySQL会评估,走索引快,还是全表扫描快,如果全表扫描更快,则放弃索引走全表扫描。 因此,is null 、is not null是否走索引,得具体情况具体分析,并不是固定的。

SQL提示

(1)use index(index_name):建议MYSQL走哪一个索引完成此次查询(仅仅是建议,mysql内部会再次进行评估)

explain select * from tb_user use index(idx_user_pro) where profession = '软件工程';

(2)igore index(index_name):忽略指定的索引。

explain select * from tb_user ignore index(idx_user_pro) where profession = '软件工程';

(3)force index(index_name):强制使用索引

explain select * from tb_user force index(idx_user_pro) where profession = '软件工程';
覆盖索引

​ 尽量使用户覆盖索引,减少select * .**覆盖索引就是指查询使用了索引并且返回需要的列,该列在该索引中已经能够全部能够找到。**就不需要回表扫描了。
二 MySQL进阶_第17张图片

Extra 含义
Using where;Using Index 查找使用了索引,但是需要的数据在索引列中能够找到,所以不需要回表查询数据
Using index condition 查找使用了索引,但是需要回表查询数据
前缀索引

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

create index index_name on table(字段名(长度))

执行流程普通索引一样,就是索引字段减少了。

可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高, 唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

单列索引与联合索引

单列索引:即只包含单个列。

联合索引:一个索引包含了多个列。

2.7 索引设计原则

(1)针对数据量大,且查询比较频繁的表建立索引。

(2)针对于常作为查询条件(where),排序(order by),分组(group by)操作的字段建立索引。

(3)尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。

(4)如果是字符串类型的字段,字段的长度越长,可以针对于字段的特点,建立前缀索引。

(5)尽量使用联合索引,减少单列索引,查询时,联合索引可以覆盖索引(查询使用了索引并且返回需要的列,该列在该索引中已经能够全部能够找到)节省存储空间,避免回表,提高查询效率。

(6)需要控制索引的数量,索引越多,维护索引的代价也就越大,会影响增删改的效率(每次增删改都需要更新索引)

(7)如果索引列不能存储null值,就在创建表时使用Not null约束它。优化时知道每列是否包含null值时,可以更好的确定哪个索引最有效地用于查询。

3、SQL优化

插入数据

​ 插入数据时,可以批量插入数据。或者提交一定数量的值之后,手动提交事务。按照主键顺序插入,性能要高于乱序插入

-- 批量插入数据
Insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry'); 1
-- 手动控制事务
start transaction; 
insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry'); 
insert into tb_test values(4,'Tom'),(5,'Cat'),(6,'Jerry'); 
insert into tb_test values(7,'Tom'),(8,'Cat'),(9,'Jerry'); 
commit;
-- 主键顺序插入

批量插入大批数据

​ 如果一次性需要插入大批量数据(如几百万条记录),使用insert语句插入性能较低,可以使用MYSQL数据库提供的load指令进行插入。

use database_name;
-- 设置全局local_infile为1开启从 本地加载文件导入 数据的开关
set global local_infile = 1;
-- 执行load指令将准备好的数据加载到表结构中
load data local infile '文件路径' into table tableName fields terminated by '分隔符' lines terminated by '结束符' ;
-- 这里的文件路径为反斜杠/

主键优化

​ 在InnoDB存储引擎中,表数据都是根据主键顺序存放的,这种存储方式的表行数据都是存储在聚集索引的叶子节点上的。

索引设计原则

  • 满足业务需求的情况下,尽量降低主键的长度。
  • 插入数据时,尽量选择顺序插入,选择使用auto_increment主键自增
  • 尽量不要使用uuid做主键或者是其他自然主键,如身份证号。

order by优化

MySQL的排序方式 介绍
Using filesort 通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sortbuffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。
Using index 通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要

Using index的性能高,而Using filesort的性能低,我们在优化排序操作时,尽量要优化为 Using index。

explain select id,age,phone from tb_user order by phone , age;

建立索引之后,再次进行排序查询,就由Extra原来的Using filesort, 变为了 Using index,性能就是比较高的了。

降序排列某列,此时Extra中出现了 Using index 但是也出现了 Backward index scan,这个代表反向扫描索引,因为在MySQL中我们创建的索引,默认索引的叶子节点是从小到大排序的,而此时我们查询排序时,是从大到小,所以,在扫描时,就是反向扫描,就会出现 Backward index scan。 在MySQL8版本中,支持降序索引,我们也可以创建降序索引。

优化原则

  • 根据排序字段建立合适的索引,所字段排序时,也遵循最前缀法则。
  • 尽量使用覆盖索引(查询使用了索引并且返回需要的列,该列在该索引中已经能够全部能够找到)。
  • 多字段排序,一个升序一个降序,此时需要联合索引在创建时的规则(ASC/DESC)
  • 如果不可避免的出现filesort,大量数据排序时,可以适当增大排序缓冲区大小sort_buffer_size(默认256k)

group by优化

分组操作主要看看索引对于分组操作的影响。

优化

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

limit优化

在数据量比较大时,

一般分页查询时,通过创建覆盖索引能够比较好的提高性能,可以通过覆盖索引加子查询形式进行优化。

explain select * from tb_sku t , (select id from tb_sku order by id limit 2000000,10) a where t.id = a.id;

count优化

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

如果说要大幅度提升InnoDB表的count效率,主要的优化思路:自己计数(可以借助于redis这样的数据库进行,但是如果是带条件的count又比较麻烦了)。

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

按照效率排序的话,count(字段) < count(主键 id) < count(*),所以尽量使用 count(*)。

update优化

我们主要需要注意一下update语句执行时的注意事项。

update course set name = 'javaEE' where id = 1 ;

当我们在执行删除的SQL语句时,会锁定id为1这一行的数据,然后事务提交之后,行锁释放.

update course set name = 'SpringBoot' where name = 'PHP' ;

当我们开启多个事务,在执行上述的SQL时,我们发现行锁升级为了表锁。 导致该update语句的性能大大降低。

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

因为name不加索引需要先去全表扫描找到对应的name值对应的记录,所以会表锁也就是锁整张表,而id是主键约束所以只是行锁.

总结

插入数据 insert批量插入,手动控制事务,主键顺序插入
主键优化 主键长度尽量短,顺序插入 主键自增(auto_increment)
order by 优化 using index:直接通过索引(覆盖索引)返回数据,性能高 Using filesort 需要将返回的结果在排序缓冲区中排序
group by group by的字段为索引,多字段分组满足最左前缀法则
limit 覆盖索引找id然后通过id子查询查找所有数据【】
count优化 count(字段) < count(主键 id) < count(※),所以尽量使用 count(※)
update 尽量判断条件的时候根据主键/有索引的字段进行数据更新

4、视图/存储过程/触发器

4.1 视图

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

-- 创建视图
create or replace view as select 语句

-- 查看创建视图语句
show create view 视图名称;
-- 查看视图数
select * from 视图名称

-- 修改视图
alter view 视图名称 as select 语句

-- 删除视图
drop view if exists 视图名称

视图的更新

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

(1)聚合函数或窗口函数(sum(),max(),min(),count()等)

(2)DISTINCT

(3)GROUP BY

(4)HAVING

(5)UNION 或者UNION ALL

视图作用

(1)简单

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

(2)安全

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

(3)数据独立

视图可帮助用户屏蔽真实表结构变化带来的影响。

4.2 存储过程

存储过程是事先通过编译并存储在数据库中的一段SQL语句的集合,调用存储过程可以简化应用开发人员的工作,将减少数据在应用服务器之间的传输,对于提高数据处理是有好处的。其实就是数据库层面代码的封装与重用。

-- 存储过程基本语法
-- 删除
drop procedure if exists 存储过程名;
-- 创建
create procedure 存储过程名() --不加分号
begin 
	SQL语句; -- 这里的分号不要忘记了
end;

-- 调用
call 存储过程名();

-- 查看
show create procedure 存储过程名;

(1)IF

IF 条件1 THEN ... ; -- 分号要写
ELSEIF 条件2 THEN ...; -- ELSEIF必须连写
ELSE 条件3 THEN ...;
END IF;

(2)参数

参数的类型主要有三种 IN、OUT、INOUT。

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

(3)case

create procedure p6(in month int) 
begin
case 
	when month >= 1 and(也可用or) 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;

(4)while

满足条件进入循环

create procedure p7(in n int)
begin 
	declare total int default 0; -- 赋值
	while n>0 do 
		set total:=total+n;
		set n := n - 1; -- while这里要加循环结束条件可以自定义数
	end while;
	select total;
end;

(5)repeat

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

create procedure p8(in n int)
begin
	repeat
		set total:=total +n ;
		set n:=n-1;
		until n<=0
	end repeat;

(6)loop

loot实现简单的循环,如果不在SQL语句逻辑中增加退出循环的条件,可以

用来实现死循环。

-- A. 定义局部变量, 记录累加之后的值; 
-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环 ----> 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 sum;
	select total;
end;
call p10(100)

(7)游标

-- 查询游标
DECLARE 游标 CURSOR FOR 查询语句;

-- 打开游标
open 游标名称;

-- 获取游标记录
FETCH 游标名称 INTO 变量;

-- 关闭游标
CLOSE 游标名称;
-- 逻辑: -- A. 声明游标, 存储查询结果集 -- B. 准备: 创建表结构 -- C. 开启游标 -- D. 获取游标中的记录 -- E. 插入数据到新表中
-- F. 关闭游标

4.3 存储函数

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

characteristic说明:

  • DETERMINISTIC:相同的输入参数总是产生相同的结果
  • NO SQL :不包含 SQL 语句
  • READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句
create function 存储函数名称(参数列表)
returns type [characteristic]
begin 
		-- mysql语句
		return ...;
end;

例:

create function fun1(n int) 
returns int deterministic
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);

4.4 触发器

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

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

触发器类型 NEW OLD
INSERT 型触发器 NEW 表示将要或者已经新增的数据
UPDATE 型触发器 OLD 表示修改之前的数据 , NEW 表示将要或已经修改后的数据
DELETE 型触发器 OLD 表示将要或者已经删除的数据
CREATE TRIGGER trigger_name 
BEFORE/AFTER INSERT/UPDATE/DELETE 
ON tbl_name FOR EACH ROW -- 行级触发器 
BEGIN	
	trigger_stmt; 
END;
-- 查看触发器
show TRIGGERS;

-- 删除触发器
drop trigger [数据库名.]trigger_name; -- 没有指定数据库名 默认为当前数据库
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, ', profession=', NEW.profession)); 
end;
DROP TRIGGER IF EXISTS status_change
#设置新的结束标记
DELIMITER $$
CREATE TRIGGER status_change AFTER UPDATE 
ON sales_orders FOR EACH ROW 
BEGIN
DECLARE total INT DEFAULT 0;
set total=total+1;

  IF new.status = 3 AND old.status!=3
  THEN 
  INSERT INTO stockout_lists 
  SELECT 
    * 
  FROM
    sales_order_items
  WHERE order_id = new.id ;
  ELSEIF new.status = - 2 
  THEN 
  DELETE 
  FROM
    stockout_lists 
  WHERE order_id = new.id;
  END IF ;
-- SELECT total
UPDATE users set total -- 测试
END $$
DELIMITER ;
-- --------------------------------------
SELECT id,status FROM sales_orders ;
UPDATE 
  sales_orders 
SET
  STATUS = 3 
WHERE id = 107 ;

UPDATE 
  sales_orders 
SET
  STATUS = 3 
WHERE id = 149 ;
UPDATE 
  sales_orders 
SET
  STATUS = - 2 
WHERE id = 9 ;

5、锁

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

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

全局锁

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

典型的使用场景是做全库的逻辑备份,对所有表进行锁定,从而索取一致性视图,保证数据的完整性。

如图

二 MySQL进阶_第18张图片
此时备份出来的数据,是存在问题的。库存表和订单表中的数据不一致(有新的订单,但是库存数没减少)。

​ 对数据库进行逻辑备份之前,先对数据库加上全局锁,之后的删除修改操作,就处于阻塞状态但是可以执行查询语句,也就是处于只读状态,而数据库备份就是查询语句。那么数据在进行逻辑备份的过程中,数据库的数据是不会发生变化的,就保证的了数据的一致性和完整性

1  DDL:Data Definition Language,数据定义语言,用来维护存储数据的结构(数据库、表),代表指令为create、drop和alter等。

2  DML:Data Manipulation Language,数据操作语言,用来对数据进行操作(表中的内容)代表指令为insert、delete和update等,不过在 DML 内部又单独进行了一个分类,即 DQL(Data Query Language)数据查询语言,代表指令为select.

3  DCL:Data Control Language,数据控制语言,主要是负责(用户)权限管理,代表指令为grant和revoke等。

语法

-- 加全局锁
flush tables with read lock
-- 释放锁
unlock tables

特点

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

在InnoDB引擎中,可以在备份时加上参数来完成不加锁的一致性问题。

表级锁

​ 表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM,InnoDB,BDB等存储引擎中。

表级索主要分为以下三类:

  • 表锁
  • 元数据锁(meta,data,lock,MDL)
  • 意向锁
表锁
  • 表享读锁(red,lock)
  • 表独占写锁(write lock)

语法

-- 加表锁
lock tables 表名 read/write
-- 释放锁
unlock tables

特点

1、读锁

​ 对指定表加了读锁,不会影响其他表的读,但是会阻塞右侧客户端的写

2、写锁

​ 对指定表加了写锁,会阻塞其他表的读和写

结论:读锁不会影响其他的读,但是会阻塞写。写锁既会阻塞其他的读和写

元数据锁

​ meta data lock 元数据锁,简写MDL。这里的元数据,可以简单理解成就是一张表的结构。也就是说,某一张表涉及到未提交的事务时,是不能够修改这张表结构的。

MDL加锁过程是系统自动控制,无需显式使用,在访问表的时候会自动加上。MDL锁主要作用是维护表元数据的一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML与DDL冲突,保证读写的正确性。

MYSQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)。

对应SQL 锁类型 说明
lock tables xxx read/write SHARED_READ_ONLY/ 加读锁或写锁
select、slect … lock in share mode SHARED_READ 与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE互斥
insert,update,select … for update SHARED_WRITE 与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE
alter table … SHARED_WRITE 与其他的MDL
意向锁

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

分类

  • 意向共享锁(IS ): 于表锁共享锁(read)兼容,与表锁排他锁(write)互斥。
  • 意向排他锁(IX ):与表锁共享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥。

一旦事务提交了,意向排他锁,都会自动释放。

InnoDB行级锁

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

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

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

行锁

InnoDB实现了以下两种类型的行锁。

  • 共享锁(s):允许一个事务去读一行,组织其他事务获得相同数据集的排他锁。
  • 排他锁(x):允许后去排他锁的事务更新数据,组织其他事务获得相同数据集的共享锁和排他锁。

6、InnoDB引擎

(1)表空间

​ 表空间是InnoDB存储引擎逻辑结构的最高层,如果用了参数,每个表都会有一个空间(xxx.ibd),一个MYSQL实例可以对应多个表空间,用于存储多个表空间,用于存储记录、索引等数据。

(2)段

​ 分为数据段,索引段,回滚段,Inno是索引组织表,数据段就是B+树的叶子节点,索引段为B+树的非叶子节点。段用来管理多个Extent(区)。

(3)区

​ 区,表空间的单元结构,每个区的大小为1M。默认情况下,InnoDB存储引擎页大小为16K,即一个区中一共有64个连续的页。

(4)页

​ 页,是InnDB存储管理的最小单元,每个页的大小为16Kb。为了保证页的连续性InnoDB存储引擎每次从磁盘申请4-5区。

(5)行

​ InnoDB的存储引擎数据是按行进行存放的。

​ 在行中,默认有两个隐藏字段:

  • Trx_id:每次对某条记录进行改动时,都会把对应的事务id赋值给trx_id隐藏列。
  • Roll_pointer:每次对某条记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针列就相当于与一个指针,可以通过它来找到该记录修改前的信息。

事务

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

特性

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

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