MySQL高级篇性能优化

MySQL高级

  • 生产过程中优化的过程
    • 观察,至少跑一天,看看生产的慢SQL情况
    • 开启慢查询日志,设置阈值,比如超过5秒钟的就是慢SQL,并将它抓取出来
    • Explain+慢SQL分析
    • show profile
    • 运维经理 or DBA,进行SQL数据库服务器的参数调优
  • 目标
    • 慢查询的开启并捕获
    • explain+慢SQL分析
    • show profile查询SQL在MySQL服务器里面的执行细节和生命周期情况
    • SQL数据库服务器的参数调优

MySQL的架构介绍

MySQL简介

概述

  • MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司。MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性
  • Mysql是开源的,所以你不需要支付额外的费用
  • Mysql是可以定制的,采用了GPL协议,你可以修改源码来开发自己的Mysql系统
  • Mysql支持大型的数据库。可以处理拥有上千万条记录的大型数据库
  • MySQL使用标准的SQL数据语言形式
  • Mysql可以允许于多个系统上,并且支持多种语言。这些编程语言包括C、C++、Python、Java、Perl、PHP、Eiffel、Ruby和Tcl等
  • MySQL支持大型数据库,支持5000万条记录的数据仓库,32位系统表文件最大可支持4GB,64位系统支持最大的表文件为8TB

MySQL高级

  • 提示:完整的mysql优化需要很深的功底,大公司设置有专门的DBA写上述

  • MySQL内核

  • SQL优化工程师

  • MySQL服务器的优化

  • 各种参数常量设定

  • 查询语句优化

  • 主从复制

  • 软硬件升级

  • 容灾备份

  • SQL编程

Linux版MySQL安装

官网

拷贝解压缩

  • xftp,将下载的压缩包拷贝至 /opt 目录下并解压
  • image-20200903110222691

检查工作

  • 检查当前系统是否安装过mysql

     rpm -qa|grep -i mysql
    
  • 删除命令

    rpm -e --nodeps  mysql-libs
    
  • CentOS的默认数据库已经不再是MySQL,而是MariaDB,先删除自带的MariaDB

    MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可。开发这个分支的原因之一是:甲骨文公司收购了MySQL后,有将MySQL闭源的潜在风险,因此社区采用分支的方式来避开这个风险。MariaDB的目的是完全兼容MySQL,包括API和命令行,使之能轻松成为MySQL的代替品

    //查看mariadb
    rpm -qa | grep mariadb
    //删除mariadb
    rpm -e --nodeps mariadb-libs-5.5.65-1.el7.x86_64
    

    image-20200903133529134

安装

  • 依次安装

    rpm -ivh mysql-community-common-5.7.31-1.el7.x86_64.rpm 
    rpm -ivh mysql-community-libs-5.7.31-1.el7.x86_64.rpm
    rpm -ivh mysql-community-client-5.7.31-1.el7.x86_64.rpm 
    rpm -ivh mysql-community-server-5.7.31-1.el7.x86_64.rpm
    rpm -ivh mysql-community-devel-5.7.31-1.el7.x86_64.rpm
    

    image-20200903133922158

查看MySQL安装版本

  • 查看安装的mysql版本

    mysqladmin --version
    

    image-20200903134214590

  • 查看mysql的用户和用户组

    • //mysql用戶
      cat /etc/passwd|grep mysql
      //mysql用戶組
      cat /etc/group|grep mysql
      

      image-20200903134718565

MySQL服务启动、停止

  • 启动

    service mysqld start
    
  • 停止

    service mysqld stop
    
  • image-20200903141400630

首次登陆

  • Mysql5.7默认安装之后root是有密码的

    • 获取MySQL的临时密码

    • 为了加强安全性,MySQL5.7为root用户随机生成了一个密码,在error log中,关于error log的位置,如果安装的是RPM包,则默认是/var/log/mysqld.log,只有启动过一次mysql才可以查看临时密码

       grep 'temporary password' /var/log/mysqld.log
      

      image-20200903150537738

  • 使用临时密码登录mysql -u root -pimage-20200903150633590

  • 使用临时密码登录后,并不能操作数据库,必须修改临时密码

  • //如果修改的密码过于简单 必须先修改两个全局策略
    set global validate_password_policy=0;
    set global validate_password_length=1;
    //策略修改成功后再设置密码
    alter user 'root'@'localhost' identified by '123456';
    

    image-20200903153336143

自启动MySQL服务

  • 设置mysql自启动

    systemctl enable mysqld
    
  • 查看

    systemctl list-unit-files | grep mysqld
    

MySQL的安装位置

  • 参数 路径 解释 备注
    --basedir /usr/bin 相关命令目录 mysqladmin、mysqldump等命令
    --datadir /var/lib/mysql mysql数据库文件的存放路径
    --plugin-dir /usr/lib64/mysql/plugin mysql插件存放路径
    --log-error /var/lib/mysql/xx.err mysql错误日志路径
    --pid-file /var/lib/mysql/xx.pid 进程pid文件
    --socket /var/lib/mysql/mysql.sock 本地连接时用的unix套接字文件
    /usr/share/mysql 配置文件目录 mysql脚本及配置文件
    /etc/init.d/mysql 服务启停相关脚本

修改配置文件位置

  • 查看默认配置

    vim /etc/my.cnf
    

修改字符集

  • 连接mysql,建库建表插入数据,一切正常

    //建库db01
    create database db01;
    //使用库db01
    use db01;
    //创建user表
    create table user(id int not null,name varchar(20));
    //向user表插入数据
    insert into user values(1,'z3');
    //查看user表
    select * from user;
     
    

    image-20200903164843754

  • 向user表中插入中文

    image-20200903165235850

  • 查看字符集

    show variables like 'character%';
    show variables like '%char%';
    

    image-20200903165925038

  • insert into user values(3,'张三');
    

    插入失败,插入中文字符集需修改配置image-20200904091810703

  • 编辑 /ect/my.cnf 文件

    vim /etc/my.cnf
    

    增加一行character-set-server=utf8,保存退出并重启mysql服务 service mysqld restartimage-20200904091934966

  • 注意重新连接后,之前建立表仍然不允许,插入中文。需要重新create database并使用新建库,再重新建表

    //建库db02
    create database db02;
    //使用db02
    use db02;
    //创建新表users
    create table users(id int not null,name varchar(20));
    //插入数据
    insert into users(1,'张三03');
    //查看
    select * form users;
    

    image-20200904092705153

MySQL配置文件

如何配置

  • windows环境 my.ini文件
  • Linux环境 /etc/my.cnf 文件

二进制日志log-bin

  • 主从复制(先了解,后面会详细讲解):log-bin 中存放了所有的操作记录(写?),可以用于恢复。相当于 Redis 中的 AOF

错误日志log-error

  • 默认是关闭的,记录严重的警告和错误信息,每次启动和关闭的详细信息等

查询日志log

  • 默认关闭,记录查询的sql语句,如果开启会减低mysql的整体性能,因为记录日志也是需要消耗系统资源的
  • 可自定义“慢”的概念:0-10秒之间的一个数。慢查询日志会将超过这个查询事件的查询记录下来,方便找到需要优化的 sql 。用于优化sql语句是使用。

数据文件

  • 两种环境
    • Windows安装路径
    • Linux:/var/lib/mysql
  • frm文件(framework):存放表结构
  • myd文件(data):存放表数据
  • myi文件(index):存放表索引

MySQL逻辑架构介绍

总体概览

  • 设计思路

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

    • 1.连接层
      • 最上层是一些客户端和连接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于tcp/ip的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限
    • 2.服务层
      • 2.1 Management Serveices & Utilities: 系统管理和控制工具
      • 2.2 SQL Interface: SQL接口接受用户的SQL命令,并且返回用户需要查询的结果。比如select from就是调用SQL Interface
      • 2.3 Parser: 解析器,SQL命令传递到解析器的时候会被解析器验证和解析
      • 2.4 Optimizer: 查询优化器,SQL语句在查询之前会使用查询优化器对查询进行优化。 用一个例子就可以理解: select uid,name from user where gender= 1;优化器来决定先投影还是先过滤
      • 2.5 Cache和Buffer: 查询缓存,如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据,这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等
        缓存是负责读,缓冲负责写
    • 3.引擎层
      • 存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。后面介MyISAM和InnoDB
    • 4.存储层
      • 数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互

查询说明

  • 查询流程图

    image-20200904110058340

  • 解释说明

    • mysql客户端通过协议与mysql服务器建连接,发送查询语句,先检查查询缓存,如果命中(一模一样的sql才能命中),直接返回结果,否则进行语句解析,也就是说,在解析查询之前,服务器会先访问查询缓存(query cache)——它存储SELECT语句以及相应的查询结果集。如果某个查询结果已经位于缓存中,服务器就不会再对查询进行解析、优化、以及执行。它仅仅将缓存中的结果返回给用户即可,这将大大提高系统的性能
    • 语法解析器和预处理:首先mysql通过关键字将SQL语句进行解析,并生成一颗对应的“解析树”。mysql解析器将使用mysql语法规则验证和解析查询;预处理器则根据一些mysql规则进一步检查解析数是否合法
    • 查询优化器当解析树被认为是合法的了,并且由优化器将其转化成执行计划。一条查询可以有很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划
    • 然后,mysql默认使用的BTREE索引,并且一个大致方向是:无论怎么折腾sql,至少在目前来说,mysql最多只用到表中的一个索引

MySQL存储引擎

查看命令

  • 查看当前mysql使用的存储引擎

    show engines;
    

    image-20200904114048624

  • 查看你的mysql当前默认的存储引擎:

    show variables like '%storage_engine%';
    

    image-20200904114229286

各个引擎简介

  • InnoDB存储引擎
    InnoDB是MySQL的默认事务型引擎,它被设计用来处理大量的短期(short-lived)事务。除非有非常特别的原因需要使用其他的存储引擎,否则应该优先考虑InnoDB引擎。行级锁,适合高并发情况
  • MyISAM存储引擎
    MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM不支持事务和行级锁(myisam改表时会将整个表全锁住),有一个毫无疑问的缺陷就是崩溃后无法安全恢复
  • Archive引擎
    Archive存储引擎只支持INSERT和SELECT操作,在MySQL5.1之前不支持索引
    Archive表适合日志和数据采集类应用,适合低访问量大数据等情况
    根据英文的测试结论来看,Archive表比MyISAM表要小大约75%,比支持事务处理的InnoDB表小大约83%
  • Blackhole引擎
    Blackhole引擎没有实现任何存储机制,它会丢弃所有插入的数据,不做任何保存。但服务器会记录Blackhole表的日志,所以可以用于复制数据到备库,或者简单地记录到日志。但这种应用方式会碰到很多问题,因此并不推荐
  • CSV引擎
    CSV引擎可以将普通的CSV文件作为MySQL的表来处理,但不支持索引
    CSV引擎可以作为一种数据交换的机制,非常有用
    CSV存储的数据直接可以在操作系统里,用文本编辑器,或者excel读取
  • Memory引擎
    如果需要快速地访问数据,并且这些数据不会被修改,重启以后丢失也没有关系,那么使用Memory表是非常有用。Memory表至少比MyISAM表要快一个数量级。(使用专业的内存数据库更快,如redis)
  • Federated引擎
    Federated引擎是访问其他MySQL服务器的一个代理,尽管该引擎看起来提供了一种很好的跨服务器的灵活性,但也经常带来问题,因此默认是禁用的

MyISAM和InnoDB(*)

对比项 MyISAM InnoDB
主外键 不支持 支持
事务 不支持 支持
行表锁 表锁,即使操作一条记录也会锁住整张表,不适合高并发的操作 行锁,操作时只锁某一行,不对其他行有影响,适合高并发的操作
缓存 只缓存索引,不缓存真实数据 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响
表空间
关注点 性能 事务
默认安装 Y Y
用户表默认使用 N Y
自带系统表使用 Y N
  • innoDB 索引,使用B+树,MyISAM 索引使用 B-树
  • innoDB 主键为聚簇索引,基于聚簇索引的增删改查效率非常高

阿里巴巴、淘宝用哪个

  • image-20200904132321344
  • Percona 为 MySQL 数据库服务器进行了改进,在功能和性能上较 MySQL 有着很显著的提升。该版本提升了在高负载情况下的 InnoDB 的性能、为 DBA 提供一些非常有用的性能诊断工具;另外有更多的参数和命令来控制服务器行为
  • 该公司新建了一款存储引擎叫xtradb完全可以替代innodb,并且在性能和并发上做得更好
  • 阿里巴巴大部分mysql数据库其实使用的是percona的原型加以修改
  • AliSql+AliRedis

索引优化分析

性能下降SQL慢

查询语句写的差

  • 能不能拆,条件过滤尽量少

索引失效

  • 单值
  • 复合
    • 条件多时,可以建共同索引(混合索引)。混合索引一般会偶先使用。有些情况下,就算有索引具体执行时也不会被使用

关联了太多join

  • 设计缺陷或不得已的需求

服务器调优及各个参数设置(缓冲、线程数等)(不重要DBA的工作)

常见通用的Join查询

SQL执行顺序

  • 手写

    • image-20200904135253784
  • 机读(先从From开口)

    • 随着Mysql版本的更新换代,其优化器也在不断的升级,优化器会分析不同执行顺序产生的性能消耗不同而动态调整执行顺序

    • 下面是经常出现的查询顺序:

      image-20200904135439784

  • 总结

    • image-20200904135640745

JOIN关系图

  • 所有的join关系

    image-20200904135829735

  • 共有独有的理解

    共有:满足 a.deptid = b.id 的叫共有
    A独有: A 表中所有不满足 a.deptid = b.id 连接关系的数据
    同时参考 join 图

建表SQL

  • CREATE TABLE `t_dept` (
     `id` INT(11) NOT NULL AUTO_INCREMENT,
     `deptName` VARCHAR(30) DEFAULT NULL,
     `address` VARCHAR(40) DEFAULT NULL,
     PRIMARY KEY (`id`)
    ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
     
    CREATE TABLE `t_emp` (
     `id` INT(11) NOT NULL AUTO_INCREMENT,
     `name` VARCHAR(20) DEFAULT NULL,
     `deptId` INT(11) DEFAULT NULL,
     PRIMARY KEY (`id`),
     KEY `fk_dept_id` (`deptId`)
     #CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
    ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
    
    INSERT INTO t_dept(deptName,address) VALUES('RD',11);
    INSERT INTO t_dept(deptName,address) VALUES('HR',12);
    INSERT INTO t_dept(deptName,address) VALUES('MK',13);
    INSERT INTO t_dept(deptName,address) VALUES('MIS',14);
    INSERT INTO t_dept(deptName,address) VALUES('FD',15);
     
    INSERT INTO t_emp(NAME,deptId) VALUES('z3',1);
    INSERT INTO t_emp(NAME,deptId) VALUES('z4',1);
    INSERT INTO t_emp(NAME,deptId) VALUES('z5',1);
    INSERT INTO t_emp(NAME,deptId) VALUES('w5',2);
    INSERT INTO t_emp(NAME,deptId) VALUES('w6',2);
    INSERT INTO t_emp(NAME,deptId) VALUES('s7',3);
    INSERT INTO t_emp(NAME,deptId) VALUES('s8',4);
    INSERT INTO t_emp(NAME,deptId) VALUES('s9',51);
     
    
  • image-20200904170144015

7中JOIN

  • 笛卡尔积

    select * from t_dept,t_emp
    

    image-20200904170112925

  • 两表共有的 (INNER JOIN)

    select * from t_dept d inner join t_emp e on d.id=e.deptId;
    

    image-20200904170406256

  • 左外连接(LEFT JOIN)两表共有的加左表独有

    select * from t_dept d left join t_emp e on d.id=e.deptId;
    

    image-20200904170644873

  • 右外连接(RIGHT JOIN)两表共有的加右表独有

    select * from t_dept d  right  join t_emp e on d.id=e.deptId;
    

    image-20200904170906716

  • 左表独有的

    select * from t_dept d left join t_emp e on d.id=e.deptId where e.id is null;
    

    image-20200904171847494

  • 右边独有的

    select * from t_dept d right join t_emp e on d.id=e.deptId where d.id is null;
    

    image-20200904171832590

  • 两表全有

    #MySQL Full Join的实现 因为MySQL不支持FULL JOIN,下面是替代方法
    #left join + union(可去除重复数据)+ right join
    select * from t_dept d left  join t_emp e on d.id=e.deptId union select * from t_dept d right join t_emp e on d.id=e.deptId;
    

    image-20200904173127918

  • 左表独有+右表独有

    select * from t_dept d left join t_emp e on d.id=e.deptId where e.id is null union select * from t_dept d right join t_emp e on d.id=e.deptId where d.id is null;
    

    image-20200904173108850

索引简介

是什么(*)

  • MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。可以得到索引的本质:索引是数据结构

    • 索引的目的在于提高查询效率,可以类比字典
    • 如果要查“mysql”这个单词,我们肯定需要定位到m字母,然后从下往下找到y字母,再找到剩下的sql
    • 如果没有索引,那么你可能需要a----z,如果我想找到Java开头的单词呢?或者Oracle开头的单词呢?是不是觉得如果没有索引,这个事情根本无法完成?
  • 你可以简单理解为“排好序的快速查找数据结构

    • 详解(*)

      • 在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据

      • 这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图就是一种可能的索引方式示例

        image-20200907103123086

      • 左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录

      • 二叉树弊端之一:二叉树很可能会发生两边不*衡的情况
        B-TREE: (B:balance) 会自动根据两边的情况自动调节,使两端无限趋*于*衡状态。可以使性能最稳定。(myisam使用的方式)
        B-TREE弊端:(插入/修改操作多时,B-TREE会不断调整*衡,消耗性能)从侧面说明了索引不是越多越好
        B+TREE:Innodb 所使用的索引

    • 结论

      • 数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引

  • 一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘

  • 我们*常所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈稀索引(hash index)等。

优势

  • 类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本
  • 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗

劣势

  • 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间
  • 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE,因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息
  • 索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句

mysql索引结构

BTree索引(*)
  • BTree又叫多路*衡查找树,一颗m叉的BTree特性如下:

    • 树中每个节点最多包含m个孩子
    • 除根节点与叶子节点外,每个节点至少有[ceil(m/2)]个孩子(ceil()为向上取整)
    • 若根节点不是叶子节点,则至少有两个孩子
    • 所有的叶子节点都在同一层
    • 每个非叶子节点由n个key与n+1个指针组成,其中[ceil(m/2)-1] <= n <= m-1
  • Myisam普通索引

  • 检索原理

  • img

    这是一个3叉(只是举例,真实会有很多叉)的BTree结构图,每一个方框块我们称之为一个磁盘块或者叫做一个block块,这是操作系统一次IO往内存中读的内容,一个块对应四个扇区,紫色代表的是磁盘块中的数据key,黄色代表的是数据data,蓝色代表的是指针p,指向下一个磁盘块的位置

  • 来模拟下查找key为29的data的过程:

    • 根据根结点指针读取文件目录的根磁盘块1。【磁盘IO操作1次
    • 磁盘块1存储17,35和三个指针数据。我们发现17<29<35,因此我们找到指针p2
    • 根据p2指针,我们定位并读取磁盘块3。【磁盘IO操作2次
    • 磁盘块3存储26,30和三个指针数据。我们发现26<29<30,因此我们找到指针p2
    • 根据p2指针,我们定位并读取磁盘块8。【磁盘IO操作3次
    • 磁盘块8中存储28,29。我们找到29,获取29所对应的数据data
  • 由此可见,BTree索引使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率

B+Tree索引(*)
  • B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度

  • innodb的普通索引

  • 原理图

    img

    B+TREE 第二级的 数据并不能直接取出来,只作索引使用。在内存有限的情况下,查询效率高于 B-TREE
    B-TREE 第二级可以直接取出来,树形结构比较重,在内存无限大的时候有优势

  • B+Tree与B-Tree 的区别:结论在内存有限的情况下,B+TREE 永远比 B-TREE好。无限内存则后者方便

    • 非叶子节点只存储键值信息, 数据记录都存放在叶子节点中, 将上一节中的B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,所以B+Tree的高度可以被压缩到特别的低
    • 在B+Tree上通常有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。所以我们除了可以对B+Tree进行主键的范围查找和分页查找,还可以从根节点开始,进行随机查找
  • 思考:为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引?

    • B+树的磁盘读写代价更低
      B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了
    • B+树的查询效率更加稳定
      由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当
  • B+Tree索引可以分为聚集索引(clustered index)和辅助索引(secondary index)

聚簇索引与非聚簇索引
  • 聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。术语‘聚簇’表示数据行和相邻的键值紧凑的存储在一起

  • 如下图,左侧的索引就是聚簇索引,因为数据行在磁盘的排列和索引排序保持一致

image-20200907142432179

  • 聚簇索引的好处:
    按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多个数据块中提取数据,所以节省了大量的io操作
    聚簇索引的限制:
    对于mysql数据库目前只有innodb数据引擎支持聚簇索引,而Myisam并不支持聚簇索引。
    由于数据物理存储排序方式只能有一种,所以每个Mysql的表只能有一个聚簇索引。一般情况下就是该表的主键,为了充分利用聚簇索引的聚簇的特性,所以innodb表的主键列尽量选用有序的顺序id,而不建议用无序的id,比如uuid这种(参考聚簇索引的好处)
  • 这里说明了主键索引为何采用自增的方式:1、业务需求,有序 2、能使用到聚簇索引
Hash索引
full-index全文索引
R-Tree索引

mysql索引分类

单值索引
  • 即一个索引只包含单个例,一个表可以有多个单列索引
唯一索引
  • 索引列的值必须唯一,但允许有空值
复合索引
  • 即一个索引包含多个列
  • 在数据库操作期间,复合索引比单值索引所需要的开销更小(对于相同的多个列建索引)当表的行数远大于索引列的数目时可以使用复合索引
基本语法
  • 创建:ALTER mytable ADD [UNIQUE ] INDEX [indexName] ON (columnname(length)) ;
  • 删除:DROP INDEX [indexName] ON mytable;
  • 查看:SHOW INDEX FROM table_name\G;
  • 使用ALTER命令
    • ALTER TABLE tbl_name ADD PRIMARY KEY (column_list): 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL
    • ALTER TABLE tbl_name ADD UNIQUE index_name (column_list): 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)
    • ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出现多次
    • ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT 用于全文索引

哪些情况需要创建索引

  1. 主键自动建立唯一索引
  2. 频繁作为查询条件的字段应该创建索引
  3. 查询中与其它表关联的字段,外键关系建立索引
  4. 频繁更新的字段不适合创建索引,因为每次更新不单单是更新记录还需要更新索引
  5. where条件里用不到的字段不创建索引
  6. 单键/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
  7. 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
  8. 查询中统计或者分组字段

哪些情况不需要创建索引

  1. 表记录太少
  2. 经常增删改的表--提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE
    因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件
  3. 数据重复且分布*均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果image-20200907143538958

性能分析

MySQL Query Optimizer
  • MySQL优化器

    image-20200907153422003

MySQL常见瓶颈
  • CPU:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据的时候
    • SQL中对大量数据进行比较、关联、排序、分组
  • IO:磁盘IO瓶颈发生在装入数据远大于内存容量的时候
    • 实例内存满足不了缓存数据或排序等需要,导致产生大量 物理 IO
    • 查询执行效率低,扫描过多数据行
    • 不适宜的锁的设置,导致线程阻塞,性能下降
    • 死锁,线程之间交叉调用资源,导致死锁,程序卡住
  • 服务器硬件的性能瓶颈:top,free,iostat和vmstat来查看系统的性能状态
Explain(*)
  • 是什么(查看执行计划)

    • 使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈

    • 官网介绍

      image-20200907154426453

  • 能干嘛

    • 表的读取顺序---id字段
    • 数据读取操作的操作类型---select_type
    • 哪些索引可以使用---possible_key
    • 哪些索引被实际使用---key
    • 表之间的引用
    • 每张表有多少行被优化器查询---rows
  • 怎么玩

    • Explain + SQL语句

    • 执行计划包含的信息

      image-20200907154751077

  • 各字段解释

    • id

      • select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序

      • 三种情况

        1. id相同,执行顺序由上至下(t1--t3--t2)

          image-20200907161131289

          id相同,执行顺序由上至下。此例中 先执行where 后的第一条语句 t1.id = t2.id 通过 t1.id 关联 t2.id ,而 t2.id 的结果建立在 t2.id=t3.id 的基础之上

        2. id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行(t3--t2--t1)

          image-20200907161213954

          id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行

        3. id相同不同,同时存在(t3--derived2--t2)

          image-20200907161529852

          id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行

          衍生表 = derived2 --> derived + 2 (2 表示由 id =2 的查询衍生出来的表。type 肯定是 all ,因为衍生的表没有建立索引)

    • select_type

      • 有哪些类型

        类型 解释说明
        SIMPLE 简单的 select 查询,查询中不包含子查询或者UNION
        PRIMARY 查询中若包含任何复杂的子部分,最外层查询则被标记为Primary
        DERIVED 在FROM列表中包含的子查询被标记为DERIVED(衍生),MySQL会递归执行这些子查询, 把结果放在临时表里
        SUBQUERY 在SELECT或WHERE列表中包含了子查询
        DEPENDENT SUBQUERY 在SELECT或WHERE列表中包含了子查询,子查询基于外层
        UNCACHEABLE SUBQUREY 无法被缓存的子查询
        UNION 若第二个SELECT出现在UNION之后,则被标记为UNION,若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED
        UNION RESULT 从UNION表获取结果的SELECT
      • 查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询

    • table

      • 显示这一行的数据是关于哪张表的
    • type

      • image-20200907163559503

      • type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:

        system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range(尽量保证) > index > ALL

        system>const>eq_ref>ref>range>index>ALL

        一般来说,得保证查询至少达到range级别,最好能达到ref

      • 显示查询使用了何种类型,从最好到最差依次是:system>const>eq_ref>ref>range>index>ALL

      • 类型 解释说明
        system 表只有一行记录(等于系统表),这是const类型的特列,*时不会出现,这个也可以忽略不计
        const 表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快如将主键置于where列表中,MySQL就能将该查询转换为一个常量
        eq_ref 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
        ref 非唯一性索引扫描,返回匹配某个单独值的所有行.
        本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,
        它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体
        range 检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引
        一般就是在你的where语句中出现了between、<、>、in等的查询
        这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引
        index Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。
        也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)
        all Full Table Scan,将遍历全表以找到匹配的行

        备注:一般来说,得保证查询至少达到range级别,最好能达到ref

    • possible_key(理论上)

      • 显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
    • key

      • 实际使用的索引。如果为NULL,则没有使用索引

      • 查询中若使用了覆盖索引,则该索引和查询的select字段重叠

        image-20200907171519006

    • key_len

      • 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度,在不损失精确性的情况下,长度越短越好
      • key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的
      • image-20200907172541125
      • 同样的查询结果,key_len 越小越好
    • ref

      • 显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值

        image-20200907173245514

    • rows

      • rows列显示MySQL认为它执行查询时必须检查的行数

      • 越少越好

        image-20200907174149254

    • Extra

      • 包含不适合在其他列中显示但十分重要的额外信息

        字段 解释说明
        Using filesort 说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,MySQL中无法利用索引完成的排序操作称为“文件排序”
        Using temporary 使了用临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。
        USING index 表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!
        如果同时出现using where,表明索引被用来执行索引键值的查找;
        如果没有同时出现using where,表明索引只是用来读取数据而非利用索引执行查找 覆盖索引(Covering Index):查询字段和索引字段重合
        Using where 表明使用了where过滤
        using join buffer 使用了连接缓存
        impossible where where子句的值总是false,不能用来获取任何元组
        select tables optimized away 在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者
        对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,
        查询执行计划生成的阶段即完成优化
        distinct 优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作
      • Using filesort

        image-20200908094428440

      • Using temporary

        image-20200908094928494

      • Using index和Using where

        image-20200908095558558

  • 案例Case(*)

    • 描述执行顺序------id:4-3-2-1-nullimage-20200908100616270
      • 第一行(执行顺序4):id列为1,表示union里的第一个select,select_type列的primary表示该查询为外层查询,table列被标记为,表示查询结果来自一个衍生表,其中derived3中代表该查询衍生自第三个select查询,即id为3的select 【select d1.name......】
      • 第二行(执行顺序2):id为3,是整个查询中第三个select的一部分,因查询包含在from中,所以为derived【select id,name from t1 where other_column=""】
      • 第三行(执行顺序3):select列表中的子查询select_type为subquery,为整个查询中的第二个select【select id from t3】
      • 第四行(执行顺序1):select_type为union,说明第四个select是union里的第二个select,最先执行【select name,id from t2】
      • 第五行(执行顺序5):代表从union的临时表中读取行的阶段,table列的<union1,4>表示用第一个和第四个select的结果进行union操作【两个结果union操作】

索引优化 (*)

索引分析

单表
  • 建表SQL

    • //建表
      CREATE TABLE IF NOT EXISTS `article` (
      `id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
      `author_id` INT(10) UNSIGNED NOT NULL,
      `category_id` INT(10) UNSIGNED NOT NULL,
      `views` INT(10) UNSIGNED NOT NULL,
      `comments` INT(10) UNSIGNED NOT NULL,
      `title` VARBINARY(255) NOT NULL,
      `content` TEXT NOT NULL
      );
      //插入数据
      INSERT INTO `article`(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES
      (1, 1, 1, 1, '1', '1'),
      (2, 2, 2, 2, '2', '2'),
      (1, 1, 3, 3, '3', '3');
      //查看表数据
      SELECT * FROM article;
      
      

      image-20200908105638509

  • 案例分析

    • 查询 category_id 为1 且 comments 大于 1 的情况下,views 最多的 article_id

      • SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
        

        image-20200908105741514

      • Explain分析

        image-20200908105821004

        结论:很显然,type是ALL,即最坏的情况,Rxtra里还出现了Using filesort,也是最坏的情况,优化是必须的

      • 查看文章表已有的索引

        show index from article;
        

        image-20200908105925547

      • 开始优化

        1. 第一次优化:创建复合索引

          create index idx_article_ccv on article(category_id,comments,views);
          

          image-20200908110417751

          第二次执行Explain

          image-20200908110705812

          结论:
          type 变成了 range,这是可以忍受的。但是 extra 里使用 Using filesort 仍是无法接受的。
          但是我们已经建立了索引,为啥没用呢?
          这是因为按照 BTree 索引的工作原理,先排序 category_id,如果遇到相同的 category_id 则再排序 comments,如果遇到相同的 comments 则再排序 views,当 comments 字段在联合索引里处于中间位置时,因comments > 1 条件是一个范围值(所谓 range),MySQL 无法利用索引再对后面的 views 部分进行检索,即 range 类型查询字段后面的索引无效

        2. 第二次优化:删除第一次优化建立的索引,重建索引

          //删除索引
          DROP INDEX idx_article_ccv ON article;
          //重新创建索引
          #ALTER TABLE `article` ADD INDEX idx_article_cv ( `category_id` , `views` ) ;
          create index idx_article_cv on article(category_id,views);
          
          

          image-20200908111610172

          第三次执行Explain

          image-20200908111900342

      • 结论:可以看到,type 变为了 ref,Extra 中的 Using filesort 也消失了,结果非常理想

两表(关联查询)
  • 建表SQL

    •  
      CREATE TABLE IF NOT EXISTS `class` (
      `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
      `card` INT(10) UNSIGNED NOT NULL,
      PRIMARY KEY (`id`)
      );
      CREATE TABLE IF NOT EXISTS `book` (
      `bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
      `card` INT(10) UNSIGNED NOT NULL,
      PRIMARY KEY (`bookid`)
      );
       
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
       
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
      
  • 案例分析

    • Explain select * from class c left join book b on c.card=b.card;
      

      第一次执行Explainimage-20200908113345526

    • 第一次优化 ,book表(右表)card字段 添加索引

      ALTER TABLE `book` ADD INDEX Y (`card`);
      

      image-20200908113748153

      加索引后,第二次执行Explain

      image-20200908113936575

      结论:可以看到第二行的 type 变为了 ref,rows 也变成了优化比较明显

    • 第二次优化,删除book表的索引,在class表(左表)的card字段,创建索引

      drop index Y on book;
      ALTER TABLE class ADD INDEX X (`card`);
      show index from class;
      

      image-20200908114627413

      第三次执行Explain

      image-20200908114836689

      优化效果不明显

    • 结论:由左连接特性决定的,LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要建立索引;同理右连接,左边表一定要建立索引

三表
  • 建表SQL

    • CREATE TABLE IF NOT EXISTS `phone` (
      `phoneid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
      `card` INT(10) UNSIGNED NOT NULL,
      PRIMARY KEY (`phoneid`)
      )ENGINE=INNODB;
      
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
      
  • 案例分析

    • Explain select * from class inner join book on class.card=book.card inner join phone on book.card=phone.card;
      

      三表关联,第一次Explainimage-20200908133931153

    • 跟phone表和book表的 card字段 创建索引

      ALTER TABLE phone ADD INDEX Z (`card`);
      ALTER TABLE book ADD INDEX Y (`card`);
      

      image-20200908134438178

      创建完索引后,第二次Explainimage-20200908134559319

      优化明显,后两行的type都是ref且总的必须检查的记录数rows优化很好,效果不错。因此索引最好设置在需要经常查询的字段中

    • 结论:join语句的优化

      • 尽可能减少Join语句中的NestedLoop的循环总次数:永远用小的结果集驱动大的结果集
      • 优先优化NestedLoop的内层循环
      • 保证Join语句中被驱动表上Join条件字段已经被索引
      • 当无法保证被驱动表的Join条件字段被索引且内存资源充足的情况下,不要太吝惜JoinBuffer的设置

索引失效(应该避免)

  • 建表SQL

    • CREATE TABLE IF NOT EXISTS `staffs` (
      `id` INT(10) Primary key  AUTO_INCREMENT,
      name varchar(24) not null default ' ' comment '姓名',
      age INT not NULL default 0 comment '年龄',
      pos VARCHAR(20) not null default ' ' comment '职位',
       add_time TimeStamp not null default current_timestamp comment '入职时间'
      )charset utf8 comment '员工记录表';
      
      INSERT INTO staffs(NAME,age,pos,add_time)values('z3',22,'manager',now());
      INSERT INTO staffs(NAME,age,pos,add_time)values('july',23,'dev',now());
      INSERT INTO staffs(NAME,age,pos,add_time)values('2000',23,'dev',now());
      
      select * from staffs;
      
      //创建复合索引
      ALTER TABLE staffs ADD INDEX idx_staffs_nameAgePos(name,age,pos);
      

      image-20200908143352651

案例(索引失效)
  1. 全值匹配最喜欢看到

    •  Explain Select * from staffs where name='july' and age=25 and pos='manager';
      

      image-20200908155852496

  2. 最佳左前缀法则:如果索引了多列,要遵守最左前缀法则,指的是查询从索引的最左前列开始并且不跳过索引中的列

    • 当使用覆盖索引的方式时,(select name,age,pos from staffs where age=10 (后面没有其他没有索引的字段条件)),即使不是以 name 开头,也会使用 idx_nameAgePos 索引

    • 如果中间有跳过的列 name、pos,则只会部分使用索引

    • 索引 idx_staffs_nameAgePos 建立索引时 以 name , age ,pos 的顺序建立的。全值匹配表示 按顺序匹配的

      Explain select * from staffs where name='july';
      Explain select * from staffs where name='july' AND age=23;
      Explain select * from staffs where name='july' AND age=23 AND pos='dev';
      

    image-20200908143856734

    • 改变查询语句

      Explain select * from staffs where age=23 AND pos='dev';
      Explain select * from staffs where pos='dev';
      

    image-20200908144138957

    未遵循最佳左前缀法则,导致了索引失效

  3. 不要在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描

    • 索引列 name 上做了函数操作image-20200908155049042
  4. 存储引擎不能使用索引中范围条件右边的列,范围之后全失效

    • Explain Select * from staffs where name='july' and age=25 and pos='manager';
      Explain Select * from staffs where name='july' and age>25 and pos='manager';
      

      image-20200908160325007

    • 范围 若有索引则能使用到索引,范围条件右边的索引会失效(范围条件右边与范围条件使用的同一个组合索引,右边的才会失效。若是不同索引则不会失效)

  5. 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *

    image-20200908161321041

  6. mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描

    image-20200908162939596

  7. is not null 也无法使用索引,但是is null是可以使用索引的

    image-20200908170126343

  8. like以通配符开头(’%abc‘)mysql索引失效会变成全表扫描的操作

    • % 写在右边可以避免索引失效

      image-20200908170639510

    • 问题:解决like ‘%字符串%’时索引不被使用的方法??------覆盖索引(完全重合或包含,但不能超过)

      • 覆盖索引:建的索引和查询的字段,最好完全一致或者包含,但查询字段不能超出索引列

      • 建表SQL

        CREATE TABLE `tbl_user` (
         `id` INT(11) NOT NULL AUTO_INCREMENT,
         `NAME` VARCHAR(20) DEFAULT NULL,
         `age` INT(11) DEFAULT NULL,
         email VARCHAR(20) DEFAULT NULL,
         PRIMARY KEY (`id`)
        ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
        
        INSERT INTO tbl_user(NAME,age,email) VALUES('1aa1',21,'b@163.com');
        INSERT INTO tbl_user(NAME,age,email) VALUES('2aa2',222,'a@163.com');
        INSERT INTO tbl_user(NAME,age,email) VALUES('3aa3',265,'c@163.com');
        INSERT INTO tbl_user(NAME,age,email) VALUES('4aa4',21,'d@163.com');
        INSERT INTO tbl_user(NAME,age,email) VALUES('aa',121,'e@163.com');
        
      • before index 未创建索引

        EXPLAIN SELECT NAME,age    FROM tbl_user WHERE NAME LIKE '%aa%';
         
        EXPLAIN SELECT id    FROM tbl_user WHERE NAME LIKE '%aa%';
        EXPLAIN SELECT NAME     FROM tbl_user WHERE NAME LIKE '%aa%';
        EXPLAIN SELECT age   FROM tbl_user WHERE NAME LIKE '%aa%';
        EXPLAIN SELECT id,NAME    FROM tbl_user WHERE NAME LIKE '%aa%';
        EXPLAIN SELECT id,NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';
        EXPLAIN SELECT NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';
        
        

        image-20200908171856221

      • 创建索引后,再观察变化(使用覆盖索引来解决like 导致索引失效的问题)

        CREATE INDEX idx_user_nameAge ON tbl_user(NAME,age);
         
        #DROP INDEX idx_user_nameAge ON tbl_user
        
        EXPLAIN SELECT name,age FROM tbl_user WHERE NAME like '%aa%';
        EXPLAIN SELECT name FROM tbl_user WHERE NAME like '%aa%';
        EXPLAIN SELECT age  FROM tbl_user WHERE NAME like '%aa%';
        EXPLAIN SELECT id,name  FROM tbl_user WHERE NAME like '%aa%';
        EXPLAIN SELECT id,name,age  FROM tbl_user WHERE NAME like '%aa%';
        EXPLAIN SELECT name,age  FROM tbl_user WHERE NAME like '%aa%';
        

        image-20200908172216943

        完全一致或者包含的情况,成功使用覆盖索引(match匹配)image-20200908172847457

        查询字段,不一致或者超出建立的索引列image-20200908173437343

  9. 字符串不加单引号索引失效

    • mysql 优化分析,会将int类型的777 自动转化为String类型,但违背了不能再索引列上进行手动或自动的转换(索引失效--案例3),导致索引失效

    • image-20200908173702985

  10. 少用or,用它来连接时会索引失效

    • image-20200908173856916
  11. 小总结

    • image-20200908174229766
面试题讲解(*)
  • 题目SQL

    • create table test03(
       id int primary key not null auto_increment,
       c1 char(10),
       c2 char(10),
       c3 char(10),
       c4 char(10),
       c5 char(10)
      );
       
      insert into test03(c1,c2,c3,c4,c5) values('a1','a2','a3','a4','a5');
      insert into test03(c1,c2,c3,c4,c5) values('b1','b2','b3','b4','b5');
      insert into test03(c1,c2,c3,c4,c5) values('c1','c2','c3','c4','c5');
      insert into test03(c1,c2,c3,c4,c5) values('d1','d2','d3','d4','d5');
      insert into test03(c1,c2,c3,c4,c5) values('e1','e2','e3','e4','e5');
       
      select * from test03;
      

      image-20200909104337018

    • //创建复合索引
      create index idx_test03_c1234 on test03(c1,c2,c3,c4);
      show index from test03;
      

      image-20200909104517859

    • 问题:创建了复合索引idx_test03_c1234 ,根据以下SQL分析索引使用情况?

      • #基础
        explain select * from test03 where c1='a1';
        explain select * from test03 where c1='a1' and c2='a2';
        explain select * from test03 where c1='a1' and c2='a2' and c3='a3';
        explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4';
        

        1.基础使用image-20200909104854866

      • explain select * from test03 where c1='a1' and c2='a2' and c4='a4' and c3='a3'; 
        

        2.效果与 c1、c2、c3、c4按顺序使用一样,mysql底层优化器会自动优化语句,尽量保持顺序一致,可避免底层做一次翻译image-20200909105521154

      •  explain select * from test03 where c1='a1' and c2='a2' and c3>'a3' and c4='a4';
        

        3.索引只用到部分c1、c2、c3(只用来排序,无法查找)。范围之后全失效,c4完全没有用到image-20200909110720517

      • explain select * from test03 where c1='a1' and c2='a2' and c4>'a4' and c3='a3';
        

        4.同理2,mysql底层优化器会自动调整语句顺序,因此索引c1、2、3、4全起效image-20200909110245254

      • explain select * from test03 where c1='a1' and c2='a2' and c4='a4' order by c3;
        

        5.c3作用在排序而不是查找(因此没有统计到ref) c1、c2、c3image-20200909110900400

      • explain select * from test03 where c1='a1' and c2='a2' order by c3;
        

        6.同理5 c1、c2、c3image-20200909111139157

      • explain select * from test03 where c1='a1' and c2='a2' order by c4; 
        

        7.出现filesort 文件排序 c1、c2image-20200909111358756

      • explain select * from test03 where c1='a1' and c5='a5' order by c2,c3; 
        

        8.1只用c1一个字段索引查找,但是c2、c3用于排序,无filesortimage-20200909111732039

        explain select * from test03 where c1='a1' and c5='a5' order by c3,c2;
        

        8.2出现了filesort,我们建的索引是1234,它没有按照顺序来,3 2 颠倒了image-20200909111845428

      • explain select * from test03 where c1='a1' and c2='a2' order by c2,c3;
        

        9.用c1、c2两个字段索引,但是c2、c3用于排序,无filesortimage-20200909112054301

      • explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c3,c2;  
        

        10.本例对比8.2 多了c2常量(无需排序),不会导致filesort

        image-20200909112606114

      • explain select c2,c3  from test03 where c1='a1' and c4='a4' group by c2,c3;
        explain select c2,c3  from test03 where c1='a1' and c4='a4' group by c3,c2;
        

        11.group by 分组之前必排序image-20200909113433673

  • 定值(常量)、范围(范围之后皆失效)还是排序(索引包含查找排序两部分),一般order by是给个范围

  • group by 基本上是需要进行排序,会有临时表产生

一般性建议

  • 对于单键索引,尽量选择针对当前query过滤性更好的索引
  • 在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前(左)越好
  • 在选择组合索引的时候,尽量选择能够包含当前query中的where字句更多字段的索引
  • 尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的

查看截取分析

  • 生产过程中优化的过程
    • 观察,至少跑一天,看看生产的慢SQL情况
    • 开启慢查询日志,设置阈值,比如超过5秒钟的就是慢SQL,并将它抓取出来
    • Explain+慢SQL分析
    • show profile
    • 运维经理 or DBA,进行SQL数据库服务器的参数调优
  • 总结
    • 慢查询的开启并捕获
    • explain+慢SQL分析
    • show profile查询SQL在MySQL服务器里面的执行细节和生命周期情况
    • SQL数据库服务器的参数调优

查询优化

永远小表驱动大表(子查询)

  • 案例

    • 优化原则:小表驱动大表,即小的数据集驱动大的数据集

      • 原理 in

        select * from A where id in(select id From B);
        ##等价于(嵌套循环)
        for select id from B
        for select * from A where A.id=B.id
        
      • 当B表的数据集必须小于A表的数据集时,用in优于exists

      • 原理 exists

        select ...from table where exists(subQuery)
        

        该语法可以理解为:将主查询的数据,放到子查询中做条件验证,根据验证结果(true或false)来决定主查询的数据结果是否得以保留

      • 提示:

        1. Exists(subquery)只返回TRUE或FALSE,因此子查询中的SELECT * 也可以是SELECT 1 或其他,官方说法是实际执行时会忽略掉SELECT清单,因此没有区别
        2. Exists子查询的实际执行过程可能经过了优化而不是我们理解上的逐条对比,如果担忧效率问题,可进行实际校验以确定是否有效率问题
        3. Exists子查询往往也可以用条件表达式、其他子查询或者JOIN来替代,何种最优需要计提问题具体分析
      • 当A表的数据集小于B表的数据集时,用exists优于in

        select * from A where exists (select 1 from B where B.id=A.id)
        ##等价于
        for select * from A
        for select * from B where B.id=A.id		
        
      • 注意:A表与B表的ID字段应建立索引

ORDER BY 关键字优化

  • ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序

    • 建表SQL

       
      CREATE TABLE tblA(
        id int primary key not null auto_increment,
        age INT,
        birth TIMESTAMP NOT NULL,
        name varchar(200)
      );
       
      INSERT INTO tblA(age,birth,name) VALUES(22,NOW(),'abc');
      INSERT INTO tblA(age,birth,name) VALUES(23,NOW(),'bcd');
      INSERT INTO tblA(age,birth,name) VALUES(24,NOW(),'def');
       
      CREATE INDEX idx_A_ageBirth ON tblA(age,birth,name);
       
      SELECT * FROM tblA; 
      
      

      image-20200909161345508

    • Case

      Explain Select * From tblA where age>20 order by age;
      Explain Select * From tblA where age>20 order by age,birth;
      #是否产生filesort
      Explain Select * From tblA where age>20 order by birth;
      Explain Select * From tblA where age>20 order by birth,age;
      

      image-20200909161938105

      explain select * From tblA order by birth;
      explain select * From tblA Where birth >'2020-9-09 00:00:00' order by birth;
      
      explain select * From tblA Where birth >'2020-9-09 00:00:00' order by age;
      
      explain select * From tblA  order by age ASC,birth DESC; #mysql默认升序
      
      

      image-20200909162616690

    • MySQL支持两种方式排序,FileSort和Index,Index效率高,它指MySQL扫描索引本身完成排序,FileSort方式效率较低

    • ORDER BY满足两种情况,会使用Index方式排序:

      • ORDER BY语句使用索引最左前列
      • 使用WHERE子句与ORDER BY子句条件列组合满足索引最左前列
  • 尽可能在索引列上完成排序操作,遵循索引建的最佳左前缀

  • 如果不在索引列上,filesort有两种算法:

    • 双路排序
      • MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出
      • 从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段
      • 取一批数据,要对磁盘进行了两次扫描,众所周知,I/O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序
    • 单路排序
      • 从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了
    • 结论及引申出的问题
      • 由于单路是后出的,总体而言好过双路
      • 但是用单路有问题: 在sort_buffer中,方法B比方法A要多占用很多空间,因为方法B是把所有字段都取出, 所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取取sort_buffer容量大小,再排……从而多次I/O,本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失
  • 优化策略

    • 增大sort_buffer_size参数的设置
      • 用于单路排序的内存大小
    • 增大max_length_for_sort_data参数的设置
      • 单次排序字段大小(单次排序请求)
    • 提高ORDER BY的速度
      • Order by时select * 是一个大忌只Query需要的字段, 这点非常重要。在这里的影响是:
        • 当Query的字段大小总和小于max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 否则用老算法——多路排序
        • 两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size
      • 尝试提高 sort_buffer_size。不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的
      • 尝试提高 max_length_for_sort_data。提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率
  • 小总结(*)

    • 为排序使用索引
      • MySQL两种排序方式:文件排序或扫描有序索引排序
      • MySQL能为排序与查询使用相同的索引
      • KEY a_b_c(a,b,c)
        • order by 能使用索引最左前缀
          • ORDER BY a
          • ORDER BY a,b
          • ORDER BY a,b,c
          • ORDER BY a DESC,b DESC,c DESC
        • 如果WHERE使用索引的最左前缀定义为常量,则ORDER BY能使用索引
          • WHERE a = const ORDER BY b,c
          • WHERE a = const AND b=const ORDER BY c
          • WHERE a = const AND b > const ORDER BY b,c
        • 不能使用索引进行排序
          • ORDER BY a ASC,b DESC,c DESC //排序不一致
          • WHERE g = const ORDER BY b,c // 丢失a索引
          • WHERE a = const ORDER BY c //丢失b索引
          • WHERE a = const ORDER BY a,d //d不是索引的一部分

GROUP BY 关键字优化

  • GROUP BY 实质是先排序后进行分组,遵照索引建的最佳左前缀(其他大致同 ORDER BY)
  • 当无法使用索引列,增大max_length_for_sort_data 参数的设置 + 增大sort_buffer_size参数的设置
  • WHERE高于HAVING,能写在WHERE限定的条件就不要去HAVING限定了

慢查询日志

是什么

  • MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中
  • 具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10秒以上的语句
  • 由他来查看哪些SQL超出了我们的最大忍耐时间值,比如一条sql执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的sql,结合之前explain进行全面分析

怎么玩

说明
  • 默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件
查看是否开启及如何开启
  • 默认

    • SHOW VARIABLES LIKE '%slow_query_log%';
      
    • 默认情况下slow_query_log的值为OFF,表示慢查询日志是禁用的,可以通过设置slow_query_log的值来开启

      image-20200910092846735

  • 开启

    • set global slow_query_log=1;
      
    • 使用该命令开启了慢查询日志只对当前数据库生效,如果MySQL重启后则会失效

      image-20200910093127655

    • 如果要永久生效,就必须修改配置文件my.cnf(其它系统变量也是如此)

      修改my.cnf文件,[mysqld]下增加或修改参数slow_query_logslow_query_log_file后,重启mysql服务器

      slow_query_log=1
      slow_query_log_file=/var/lib/mysql/touchair-slow.log
      
    • 关于慢查询的参数 slow_query_log_file, 它指定慢查询日志文件的存放路径,系统默认会给一个缺省的文件 host_name-slow,log (如果没有指定参数 slow_query_log_file 的话)

查看慢查询内容
  • 这个是由参数long_query_time控制,默认情况下long_query_time的值为10秒

    • 命令:

      SHOW VARIABLES LIKE 'long_query_time%';
      

      image-20200910094442577

      假如运行时间正好等于long_query_time的情况,并不会被记录下来。也就是说,在mysql源码里是判断大于long_query_time,而非大于等于

  • 使用命令设置阙值超过3秒钟就是慢SQL

    • set global long_query_time=3;
      //再次查看
      SHOW VARIABLES LIKE 'long_query_time%';
      
  • 会发现查看 long_query_time 的值并没有改变?原因:

    • 需要重新连接或者新开一个会话才能看到修改值

    • 或修改查看命令

      SHOW global VARIABLES LIKE 'long_query_time%';
      

      image-20200910094852252

Case
  • 记录慢SQL,并后续分析

    • 执行一条休眠4秒的SQL

      SELECT SLEEP(4);
      
    • 查看日志文件

      前面配置的日志文件路径或者默认路径

      cat /var/lib/mysql/localhost-slow.log 
      

      image-20200910100054048

  • 查询当前系统中有多少条慢查询记录

    • show global status like '%Slow_queries%';
      

      image-20200910100526099

配置版
  • 【mysqld】下配置:

    slow_query_log=1;
    slow_query_log_file=/var/lib/mysql/touchair-slow.log
    long_query_time=3;
    log_output=FILE
    

日志分析工具mysqldumpslow(*)

  • 查看mysqldumpslow的帮助信息

    • mysqldumpslow --help;

      image-20200910101515571

    • s: 是表示按照何种方式排序;

    • c: 访问次数

    • l: 锁定时间

    • r: 返回记录

    • t: 查询行数

    • al:*均锁定时间

    • ar:*均返回记录数

    • at:*均查询时间

    • t:即为返回前面多少条的数据;

    • g:后边搭配一个正则匹配模式,大小写不敏感的;

  • 工作常用参考

    • 得到返回记录集最多的10个SQL

      mysqldumpslow -s r -t 10 /var/lib/mysql/localhost-slow.log
      
    • 得到访问次数最多的10个SQL

      mysqldumpslow -s c -t 10 /var/lib/mysql/localhost-slow.log
      
    • 得到安装时间排序的前10条里面含有左连接的查询语句

      mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/localhost-slow.log
      
    • 另外建议在使用这些命令时,结合 | 和 more使用,否则有可能出现爆屏情况

      mysqldumpslow -s r -t 10 /var/lib/mysql/localhost-slow.log | more
      

批量数据库脚本(模拟大批量数据)

  • 往数据库表里插1000w条数据

    1. 建表SQL

      # 新建库
      create database bigData;
      use bigData;
      
      #1 建表dept
      CREATE TABLE dept(  
      id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,  
      deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,   
      dname VARCHAR(20) NOT NULL DEFAULT "",  
      loc VARCHAR(13) NOT NULL DEFAULT ""  
      ) ENGINE=INNODB DEFAULT CHARSET=UTF8 ;  
      
      #2 建表emp
      CREATE TABLE emp  
      (  
      id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,  
      empno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, /*编号*/  
      ename VARCHAR(20) NOT NULL DEFAULT "", /*名字*/  
      job VARCHAR(9) NOT NULL DEFAULT "",/*工作*/  
      mgr MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,/*上级编号*/  
      hiredate DATE NOT NULL,/*入职时间*/  
      sal DECIMAL(7,2) NOT NULL,/*薪水*/  
      comm DECIMAL(7,2) NOT NULL,/*红利*/  
      deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0 /*部门编号*/  
      )ENGINE=INNODB DEFAULT CHARSET=UTF8 ; 
      
    2. 设置参数log_bin_trust_function_creators

      • 创建函数,假如报错:This function has none of DETERMINISTIC......,由于开启过慢查询日志,因为我们开启了 bin-log, 我们就必须为我们的function指定一个参数

      • show variables like 'log_bin_trust_function_creators';
        set global log_bin_trust_function_creators=1;
        

        image-20200910104106990

      • 这样添加了参数以后,如果mysqld重启,上述参数又会消失,永久方法:

        • windows下my.ini[mysqld]加上log_bin_trust_function_creators=1
        • linux下 /etc/my.cnf下my.cnf[mysqld]加上log_bin_trust_function_creators=1
    3. 创建函数,保证每条数据都不同

      • 随机产生字符串

        DELIMITER $$
        CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
        BEGIN    ##方法开始
         DECLARE chars_str VARCHAR(100) DEFAULT   'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ'; 
         ##声明一个 字符窜长度为 100 的变量 chars_str ,默认值 
         DECLARE return_str VARCHAR(255) DEFAULT '';
         DECLARE i INT DEFAULT 0;
        ##循环开始
         WHILE i < n DO  
         SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
        ##concat 连接函数  ,substring(a,index,length) 从index处开始截取
         SET i = i + 1;
         END WHILE;
         RETURN return_str;
        END $$
         
        #假如要删除
        #drop function rand_string;
        
      • 随机产生部门编号

        #用于随机产生部门编号
        DELIMITER $$
        CREATE FUNCTION rand_num( ) 
        RETURNS INT(5)  
        BEGIN   
         DECLARE i INT DEFAULT 0;  
         SET i = FLOOR(100+RAND()*10);  
        RETURN i;  
         END $$
         
         
        #假如要删除
        #drop function rand_num;
        
      1. 创建存储过程

        • 创建往emp表中插入数据的存储过程

          DELIMITER $$
          CREATE PROCEDURE insert_emp(IN START INT(10),IN max_num INT(10))  
          BEGIN  
          DECLARE i INT DEFAULT 0;   
          #set autocommit =0 把autocommit设置成0  ;提高执行效率
           SET autocommit = 0;    
           REPEAT  ##重复
           SET i = i + 1;  
           INSERT INTO emp(empno, ename ,job ,mgr ,hiredate ,sal ,comm ,deptno ) VALUES ((START+i) ,rand_string(6),'SALESMAN',0001,CURDATE(),FLOOR(1+RAND()*20000),FLOOR(1+RAND()*1000),rand_num());  
           UNTIL i = max_num   ##直到  上面也是一个循环
           END REPEAT;  ##满足条件后结束循环
           COMMIT;   ##执行完成后一起提交
           END $$
           
          #删除
          # DELIMITER ;
          # drop PROCEDURE insert_emp;
          
        • 创建往dept表中插入数据的存储过程

          #执行存储过程,往dept表添加随机数据
          DELIMITER $$
          CREATE PROCEDURE insert_dept(IN START INT(10),IN max_num INT(10))  
          BEGIN  
          DECLARE i INT DEFAULT 0;   
           SET autocommit = 0;    
           REPEAT  
           SET i = i + 1;  
           INSERT INTO dept (deptno ,dname,loc ) VALUES (START +i ,rand_string(10),rand_string(8));  
           UNTIL i = max_num  
           END REPEAT;  
           COMMIT;  
           END $$ 
           
          #删除
          # DELIMITER ;
          # drop PROCEDURE insert_dept;
          
        1. 调用存储过程

          • dept

            DELIMITER ;
            CALL insert_dept(100,10); 
            
          • emp

            #执行存储过程,往emp表添加50万条数据
            DELIMITER ;    #将 结束标志换回 ;
            CALL insert_emp(100001,500000); 
            

            插入50w条数据,耗时约24simage-20200910110608068

            查询50w条数据,耗时约0.67simage-20200910110438640

Show Profile(生命周期)

是什么

  • 是mysql提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量
  • 官网

默认情况下,参数处于关闭状态,并保存最*15次的运行结果

分析步骤

1.是否支持,看看当前的mysql版本是否支持
  • 默认关闭,使用前需要开启

  • 查看状态

    SHOW VARIABLES LIKE 'profiling';
    

    image-20200910112854968

2.开启功能,默认是关闭,使用前需要开启
  • 开启命令

    set profiling=1;
    

    image-20200910113003706

3.运行SQL
  • select * from emp group by id%10 limit 150000;

    • 执行不通过,原因:

      • SQL 标准中不允许 SELECT 列表,HAVING 条件语句,或 ORDER BY 语句中出现 GROUP BY 中未列表的可聚合列。而 MySQL 中有一个状态 ONLY_FULL_GROUP_BY 来标识是否遵从这一标准,默认为开启状态

      • 关闭命令

        SET SESSION sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY,',''));
        
    • image-20200910114442250

  • select * from emp group by id%20 order by 5;

    • image-20200910114602169
4.查看结果,show profiles
  • 命令

    show profiles;
    

    image-20200910133856680

5.诊断SQL
  • show profile cpu,block io for query n (n为上一步前面的问题SQL数字号码)

  • 参数说明

    TYPE 解释说明
    |ALL 显示所有的开销信息
    |BLOCK IO 显示块IO相关开销
    |CONTEXT SWITCHES 上下文切换相关开销
    |CPU 显示CPU相关开销信息
    |IPC 显示发送和接收相关开销信息
    |MEMORY 显示内存相关开销信息
    |PAGE FAULTS 显示页面错误相关开销信息
    |SOURCE 显示和Source_function,Source_file,Source_line相关的开销信息
    |SWAPS 显示交换次数相关开销的信息
  • show profile cpu,block io for query  17;
    

    image-20200910133815297

6.日常开发需要注意的结论
  • 出现 converting HEAP to MyISAM :查询结果太大,内存都不够用了往磁盘上搬了
  • 出现 Creating tmp table:创建临时表
    • 拷贝数据到临时表
    • 用完再删除
  • 出现 Copying to tmp table on disk:把内存中临时表复制到磁盘,危险!!!
  • 出现 locked

全局日志查询

配置启用

  • 在mysql的my.cnf中,设置如下:

    #开启
    general_log=1   
    # 记录日志文件的路径
    general_log_file=/path/logfile
    #输出格式
    log_output=FILE
    

编码启用

  • 开启命令

    set global general_log=1;
    
  • 全局日志可以存放到日志文件中,也可以存放到Mysql系统表中。存放到日志中性能更好一些,存储到表中

    set global log_output='TABLE';
    
  • 此后 ,你所编写的sql语句,将会记录到mysql库里的general_log表,可以用下面的命令查看

    select * from mysql.general_log;
    

    image-20200910135010674

    image-20200910135021407

尽量不要在生产环境开启这个功能

MySQL锁机制

概述

定义

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

锁的分类

从对数据操作的类型分(读/写)
  • 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响
  • 写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁
从对数据操作的粒度分
  • 为了尽可能提高数据库的并发度,每次锁定的数据范围越小越好,理论上每次只锁定当前操作的数据的方案会得到最大的并发度,但是管理锁是很耗资源的事情(涉及获取,检查,释放锁等动作),因此数据库系统需要在高并发响应和系统性能两方面进行*衡,这样就产生了“锁粒度(Lock granularity)”的概念
  • 一种提高共享资源并发发性的方式是让锁定对象更有选择性。尽量只锁定需要修改的部分数据,而不是所有的资源。更理想的方式是,只对会修改的数据片进行精确的锁定。任何时候,在给定的资源上,锁定的数据量越少,则系统的并发程度越高,只要相互之间不发生冲突即可
  • 表锁
  • 行锁

三锁

  • 开销、加锁速度、死锁、粒度、并发性能;只能就具体应用的特点来说哪种锁更合适

表锁(偏读)

特点
  • 偏向MyISAM存储引擎,开销小,加锁快;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低
案例分析
  • 建表SQL

    create table mylock(
     id int not null primary key auto_increment,
     name varchar(20)
    )engine myisam;
     
    insert into mylock(name) values('a');
    insert into mylock(name) values('b');
    insert into mylock(name) values('c');
    insert into mylock(name) values('d');
    insert into mylock(name) values('e');
     
    select * from mylock;
    

    image-20200910140755801

  • 手动增加表锁

    lock table 表名字1 read(write),表名字2 read(write),其它;
    
  • 查看表上加过的锁

    show open tables;
    

    IN_USE 0 代表当前没有锁image-20200910141334740

  • 释放表锁

    unlock tables;
    
  • mylockdept表分别添加读锁和写锁

    lock table mylock read,dept write;
    

    image-20200910142014886

    再次查看表上加过的锁

    image-20200910142041863

    image-20200910142110811

    image-20200910142131876

    释放锁

    image-20200910142241609

  • 加读锁

    • 新建一个MySQL会话,方便测试

    • session1 session2
      session1可以读image-20200910142937434 session2可以读image-20200910142911311
      session1无法查询其它没有锁定的表image-20200910143231126 session2查询其它表不受影响image-20200910143302934
      当前session1插入或者更新读锁锁定的表,会直接报错image-20200910143414509 session2插入或更新会一直等待获取锁image-20200910143501469
      当前session1释放锁image-20200910143646421 session2获得锁资源:完成上一步一直等待的更新操作image-20200910143627432
  • 加写锁

    • mylockwrite(MyISAM)

    • lock table mylock write;
      

      image-20200910152552489

    session1 session2
    当前session1对锁定的表的查询+更新+插入操作都可以执行image-20200910153707795 其他session对锁定的表的查询被阻塞,需等待锁被释放image-20200910153758366
    在锁表前,如果session2有数据缓存,锁表以后,在锁住的表不发生改变的情况下session2可以读出缓存数据,一旦数据发生改变,缓存将失效,操作将被阻塞住,最好使用不同的id进行测试
    session1释放锁image-20200910154022340 session2获得锁,返回查询结果image-20200910154105524
案例结论
  • MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁

  • MySQL的表级锁有两种模式:

    • 表共享读锁(Table Read Lock)
    • 表独占写锁(Table Write Lock)
    锁类型 可否兼容 读操作 写操作
    读锁
    写锁
  • 结论:

    结合上表,所有对MyISAM表进行操作,会有以下情况:

    • 对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求,只有当读锁释放后,才会执行其他进程的写操作
    • 对MyISAM表的写操作(加写锁),会阻塞其他进程读同一表的读和写操作,只有当写锁释放后,才会执行其他进程的读写操作
    • 简而言之,就是读锁会阻塞写,但不阻塞读操作。而写操作则会把读和写都阻塞
表锁分析
  • 看看哪些表被加锁了

    show open tables;
    
  • 如何分析表锁定

    show status like 'table%';
    

    通过检查table_locks_waited 和 table_locks_immediate 状态变量来分析系统上的表锁定

    image-20200910155628663

  • 这里有两个状态变更记录MySQL内部表级锁定的情况,两个变量说明如下:

    • Table_locks_immediate:产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁值加1

    • Table_locks_waited:出现表级锁定争用而发生等待次数(不能立即获取锁的次数,每等待一次锁值加1),此值高则说明存在着较严重的表级锁争用情况

  • 此外,MyISAM的读写锁调度是写优先,这也是MyISAM不适合做写为主的表的引擎,因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永久阻塞

行锁(偏写)

特点
  • 偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高
  • InnoDB与MyISAM的最大不同有两点:
    • 一是支持事务(TRANSACTION)
    • 二是采用了行级锁
复习老知识点
  • 事务及其ACID属性

    • 原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行
    • 一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的
    • 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然
    • 持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持
  • 并发事务处理带来的问题

    • 更新丢失(Lost Update)
      • 当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题--最后的更新覆盖了由其他事务所做的更新
    • 脏读(Dirty Reads)
      • 一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做”脏读”
      • 一句话:事务A读取到了事务B已修改但尚未提交的的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求
    • 不可重复读(Non-Repeatable Reads)
      • 在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据。那么,在第一个事务的两次读数据之间。由于第二个事务的修改,那么第一个事务读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复
      • 句话:一个事务范围内两个相同的查询却返回了不同数据
    • 幻读(Phantom Reads)
      • 一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”
      • 一句话:事务A 读取到了事务B提交的新增数据,不符合隔离性
  • 事务隔离级别

    • 脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决

    • 读数据一致性及允许的并发副作用隔离级别 读数据一致性 脏读 不可重复读 幻读
      未提交读(Read uncommitted) 最低级别,只能保证物理上损坏的数据
      以提交读(Read committed) 语句级
      可重复读(Repeatable read) 事务级
      可序列化(Serializable) 最高级别,事务级
    • 数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上 “串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力

    • 差看当前数据库的事务隔离级别:show variables like 'tx_isolation';

案例分析
  • 建表SQL

     
    create table test_innodb_lock (a int(11),b varchar(16))engine=innodb;
     
    insert into test_innodb_lock values(1,'b2');
    insert into test_innodb_lock values(3,'3');
    insert into test_innodb_lock values(4,'4000');
    insert into test_innodb_lock values(5,'5000');
    insert into test_innodb_lock values(6,'6000');
    insert into test_innodb_lock values(7,'7000');
    insert into test_innodb_lock values(8,'8000');
    insert into test_innodb_lock values(9,'9000');
    insert into test_innodb_lock values(1,'b1');
     
    create index test_innodb_a_ind on test_innodb_lock(a);
     
    create index test_innodb_lock_b_ind on test_innodb_lock(b);
     
    select * from test_innodb_lock;
    

    image-20200910163316885

  • 行锁定基本演示

    • 关闭自动提交 session1、session2

      set autocommit=0;
      

      image-20200910163643444

    • 正常情况,各自锁定各自的行,互相不影响,一个2000另一个3000

  • 无索引行锁升级为表锁

    • 正常情况,各自锁定各自的行互相不影响
    • 由于在column字段b上面建了索引,如果没有正常使用,会导致行锁变表锁
      • 比如没加单引号导致索引失效,行锁变表锁
      • 被阻塞,等待。只到Session_1提交后才阻塞解除,完成更新
session1 session2
更新session1中的一条记录,未手动commit,故意写错b的类型image-20200911092824894
session1,commit提交image-20200911093141141 更新session2,阻塞等待锁释放image-20200911093043823
session2完成update
  • 间隙锁危害
    • 什么是间隙锁
      • 当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(GAP Lock)
    • 危害
      • 因为Query执行过程中通过过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在
      • 间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害
面试题
  • 常考如何锁定某一行

    image-20200911094557313

案例结论
  • Innodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,Innodb的整体性能和MyISAM相比就会有比较明显的优势了
  • 但是,Innodb的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让Innodb的整体性能表现不仅不能比MyISAM高,甚至可能会更差
行锁分析
  • 如何分析行锁定

    • 通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况

      show status like 'innodb_row_lock%';
      

      image-20200911095119419

  • 各个状态量说明

    • Innodb_row_lock_current_waits:当前正在等待锁定的数量
    • Innodb_row_lock_time:从系统启动到现在锁定总时间长度
    • Innodb_row_lock_time_avg:每次等待所花*均时间
    • Innodb_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间
    • Innodb_row_lock_waits:系统启动后到现在总共等待的次数
  • 对于这5个状态变量,比较重要的主要是

    • Innodb_row_lock_time_avg(等待*均时长)
    • Innodb_row_lock_waits(等待总次数)
    • Innodb_row_lock_time(等待总时长)
    • 尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划
  • 查询正在被锁阻塞的sql语句

    SELECT * FROM information_schema.INNODB_TRX\G;
    

    image-20200911095622273

优化建议
  • 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
  • 尽可能较少检索条件,避免间隙锁
  • 尽量控制事务大小,减少锁定资源量和时间长度
  • 锁住某行后,尽量不要去调别的行或表,赶紧处理被锁住的行然后释放掉锁
  • 涉及相同表的事务,对于调用表的顺序尽量保持一致
  • 在业务环境允许的情况下,尽可能低级别事务隔离

页锁

  • 开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
  • 了解一下即可,目前使用较少

主从复制

复制的基本原理

slave会从master读取binlong来进行数据同步

三步骤+原理图

image-20200911100510799

MySQL复制过程分成三步:

  1. master将改变记录到二进制日志(binary log),这些记录过程叫做二进制日志事件,binary log events
  2. slave将master的binary log events拷贝到它的中继日志(relay log)
  3. slave重做中继日志中的事件,将改变应用到自己的数据库中,MySQL复制是异步的且串行化的

复制的基本原则

每个slave只有一个Master

每个slave只能有一个唯一的服务器ID

每个Master可以有多个Slave

复制的最大问题

延时

一主一从常见配置

主从配置

  • 1.mysql版本一致且后台以服务运行 (同5.7,本机win和虚拟机centos)

  • 2.主从都配置在[mysqld]结点下,都是小写

  • 3.主机(win)修改my.ini配置文件

    • windows+r --- services.msc 找到mysql服务 右击属性查看配置文件my.ini的位置

      image-20200911114926051

    • 1.[必须]主服务器唯一ID

      • server-id=1
    • 2.[必须]启用二进制日志

      • log-bin=自己本地的路径/data/mysqlbin
      • log-bin=D:\Mysql\mysql-5.7.28-winx64\mysql-5.7.28-winx64\data\mysqlbin
    • 3.[可选]启用错误日志

      • log-err=D:\Mysql\mysql-5.7.28-winx64\mysql-5.7.28-winx64\data\mysqlerr
    • 4.[可选]根目录

      • basedir=D:\Mysql\mysql-5.7.28-winx64\mysql-5.7.28-winx64
    • 5.[可选]临时目录

      • tmpdir=D:\Mysql\mysql-5.7.28-winx64\mysql-5.7.28-winx64
    • 6.[可选]数据目录

      • datadir=D:\Mysql\mysql-5.7.28-winx64\mysql-5.7.28-winx64\data
    • 7.read-only=0

      • 主机,读写都可以
    • 8.[可选]设置不要复制的数据库

      • binlog-ignore-db=mysql
    • 9.[可选]设置需要复制的数据库

      • binlog-do-db=需要复制的主数据库名字

      image-20200911131817659

    • 10.配置完成后重启mysql服务

  • 4.从机修改my.cnf配置文件

    • [必须]从服务器唯一ID

    • [可选]启用二进制日志

    • vim /etc/my.cnf

      image-20200911105241412

    • 重启mysql服务

      service mysqld restart
      
  • 5.主机从机都关闭防火墙

    • windows手动关闭
    • 关闭虚拟机linux防火墙 service iptables stop
  • 6.在Windows主机上建立帐户并授权slave

    • 授权命令

      GRANT REPLICATION SLAVE ON *.* TO 'zhangsan'@'从机器数据库IP' IDENTIFIED BY '123456';
      
      GRANT REPLICATION SLAVE ON *.* TO 'root'@'192.168.83.133' IDENTIFIED BY '123456';
      

      image-20200911110354863

    • flush privileges; (刷新)

      image-20200911110611897

    • 查询master的状态

      • show master status;
        

        image-20200911135427088

        记下File 和 Position 的值

    • 执行完此步骤后不要再操作主服务器MYSQL,防止主服务器状态值变化

  • 7.在Linux从机上配置需要复制的主机

    • 从机命令(如果之前同步过,先停止(stop slave;)再次授权)

      CHANGE MASTER TO MASTER_HOST='192.168.1.8', 
      MASTER_USER='root',
      MASTER_PASSWORD='123456',
      #MASTER_LOG_FILE='mysqlbin.具体数字',MASTER_LOG_POS=具体值;
      MASTER_LOG_FILE='mysqlbin.000002',MASTER_LOG_POS=154;
      

      image-20200911135453266

    • 启动从服务器复制功能

      start slave;
      
    • 查看slave状态

      show slave status\G;
      

      image-20200911135552224

    Slave_IO_Running: Yes

    Slave_SQL_Running:Yes

    两个参数都是Yes,说明主从配置成功!

测试主从效果

  • 主机新建库、新建表、insert记录,从机复制

    #建库
    create database mydb77;
    #建表
    create table touchair(id int not null,name varchar(20));
    #插入数据
    insert into touchair valies(1,'a');
    insert into touchair values(2,'b');
    

    image-20200911140232776

    • 从机上查看是否有库、表、数据

      use mydb77;
      select * from touchair;
      

      image-20200911140341601

  • 主从复制成功!

  • 如何停止从服务复制功能

    stop slave;
    
posted @ 2020-09-11 14:13  12138Ok  阅读(249)  评论(0编辑  收藏  举报