MySQL泛泛而谈(3W字)
阅读原文时间:2021年06月10日阅读:1

下面对于MySQL进行相关介绍,文档的内容较为基础,仅仅设计操作,少量原理,大佬请绕道哦。

废话少说,开冲!

一、MySQL架构介绍

概述

MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司。

MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。

MySQL是开源的,所以你不需要支付额外的费用。

MySQL支持大型的数据库。可以处理拥有上千万条记录的大型数据库。MySQL使用标准的SQL数据语言形式。

MySQL可以允许于多个系统上,并且支持多种语言。这些编程语言包括C、C++、Python、Java、Per、PHP、Eifel、Ruby和Tcl等。Mysql对PHP有很好的支持,PHP是目前最流行的Web开发语言。

MySQL支持大型数据库,支持5000万条记录的数据仓库,32位系统表文件最大可支持4GB,64位系统支持最大的表文件为8TB。Mysql是可以定制的,采用了GPL协议,你可以修改源码来开发自己的MySQL系统。

①下载地址

MySQL Linux版(CentOS)官网下载地址

②检查当前系统是否安装过MySQL

  • 查询命令:rpm -qa|grep -i mysql
  • 删除命令:rpm -e RPM软件包名(该名字是上一个命令查出来的名字)`

没装过:

已装过

③安装MySQL服务端(注意提示)

  • 安装命令:rpm -ivh RPM软件包名

如果报错,可以说缺包:

rpm -ivh MySQL-server-5.5.48-1.linux2.6.i386.rpm --nodeps --force 解决了依赖性关系,强制安装

yum -y install glibc.i686

yum -y install libncurses.so.5

yum -y install libaio.so.1

yum -y install libz.so.1

④安装MySQL客户端

  • 安装命令:rpm -ivh RPM软件包名

rpm -ivh MySQL-server-5.5.48-1.linux2.6.i386.rpm --nodeps --force 解决了依赖性关系,强制安装

查看MySQL安装时创建的MySQL用户和MySQL组

  • cat /etc/passwd |grep mysql
  • cat /etc/group |grep mysql

或者可以执行 mysqladmin --version 命令。

或者ps -ef | grep mysql

⑤MySQL服务启动与暂停

  • service mysql stop/restart/start
  • ps -ef | grep mysql查看

启动 service mysql start 可以报错

因为这个文件的问题:

mv /etc/my.cnf /etc/my.cnf.bak

⑥ROOT密码设置和开机自启动

mysql服务启动后,开始连接。

首次连接成功

  • 输入命令mysql 因为初始的root用户没有密码的,所以直接输入mysql就可以登录了
  • 退出命令exit

按照安装Server中的提示修改登录密码

  • /usr/bin/mysqladmin -u root password coderxz

密码其实对应着mysql中的mysql数据库中的user表:

也可以具体到IP 192.168.1.%

自启动mysql服务

⑦修改配置文件位置

  • cp /usr/share/mysql/my-huge.cnf /etc/my.cnf

为什么mysql会自己读取/etc/my.cnf

打开mysql.server你就会发现conf=/etc/my.cnf居然是写死的。

link

在Linux下查看安装目录ps -ef | grep mysql

⑧修改字符集

查看字符集

  • show variables like 'character%";
  • show variables like ‘%char%";

默认的是客户端和服务器都用了latin1,所以会乱码。

修改字符集和数据存储路径

  • vim /etc/my.cnf

[client]
#password = your_password
port   = 3306
socket    = /var/lib/mysql/mysql.sock
default-character-set=utf8
[mysqld]
port   = 3306
character_set_server=utf8
character_set_client=utf8
socket  = /var/lib/mysql/mysql.sock
collation-server=utf8_general_ci
#(注意linux下mysql安装完毕是默认:表名区分大小写;0:区分大小写;1:不分区大小写)
lower_case_table_names=1
#(设置最大连接数,默认为151,MySQL服务器允许的最大的连接数为16384)
max_connections=1000
[mysql]
default-character-set = utf8

重启数据库

  • service mysql start
  • service mysql stop

重新连接后重新create databse并使用新建库,然后再重新建表试试

主要配置文件

二进制日志log-bin

  • 用于主从复制

错误日志log-error

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

查询日志log

  • 默认关闭,记录查询的sql语句,如果开启会减低mysql的整体性能,因为记录日志也是需要消耗系统资源

数据文件

  • 两系统

    • windows

      • 输入mysqlselect @@database;
    • linux

      • 默认路径:/var/lib/mysql
  • frm文件(form)

    • 存放表结构
  • myd文件(my data)

    • 存放表数据
  • myi文件(my index)

    • 存放表索引

如何配置

  • Windows - my.ini文件
  • Linux - /etc/my.cnf文件

总体概览

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

1.连接层

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

2.服务层

第二层架构主要完成大多少的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化及部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定查询表的顺序是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存。如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。

3.引擎层

存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同这样我们可以根据自己的实际需要进行选取,后面介绍MyISAM和InnoDB。

4.存储层

数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。

  • 1.Connectors

指的是不同语言中与SQL的交互

  • 2 Management Serveices & Utilities:

系统管理和控制工具

  • 3 Connection Pool: 连接池

管理缓冲用户连接,线程处理等需要缓存的需求。

负责监听对 MySQL Server 的各种请求,接收连接请求,转发所有连接请求到线程管理模块。每一个连接上 MySQL Server 的客户端请求都会被分配(或创建)一个连接线程为其单独服务。而连接线程的主要工作就是负责 MySQL Server 与客户端的通信,

接受客户端的命令请求,传递 Server 端的结果信息等。线程管理模块则负责管理维护这些连接线程。包括线程的创建,线程的 cache 等。

  • 4 SQL Interface: SQL接口。

接受用户的SQL命令,并且返回用户需要查询的结果。比如select from就是调用SQL Interface

  • 5 Parser: 解析器。

SQL命令传递到解析器的时候会被解析器验证和解析。解析器是由Lex和YACC实现的,是一个很长的脚本。

在 MySQL中我们习惯将所有 Client 端发送给 Server 端的命令都称为 query ,在 MySQL Server 里面,连接线程接收到客户端的一个 Query 后,会直接将该 query 传递给专门负责将各种 Query 进行分类然后转发给各个对应的处理模块。

主要功能:

a . 将SQL语句进行语义和语法的分析,分解成数据结构,然后按照不同的操作类型进行分类,然后做出针对性的转发到后续步骤,以后SQL语句的传递和处理就是基于这个结构的。

b. 如果在分解构成中遇到错误,那么就说明这个sql语句是不合理的

  • 6 Optimizer: 查询优化器。

SQL语句在查询之前会使用查询优化器对查询进行优化。就是优化客户端请求的 query(sql语句) ,根据客户端请求的 query 语句,和数据库中的一些统计信息,在一系列算法的基础上进行分析,得出一个最优的策略,告诉后面的程序如何取得这个 query 语句的结果(SQL查询顺序优先级从高到低:from-where-groupby-having-select-orderby-limit)

他使用的是“选取-投影-联接”策略进行查询。

用一个例子就可以理解: select uid,name from user where gender = 1;

这个select 查询先根据where 语句进行选取,而不是先将表全部查询出来以后再进行gender过滤

这个select查询先根据uid和name进行属性投影,而不是将属性全部取出以后再进行过滤

将这两个查询条件联接起来生成最终查询结果

  • 7 Cache和Buffer: 查询缓存。

他的主要功能是将客户端提交 给MySQL 的 Select 类 query 请求的返回结果集 cache 到内存中,与该 query 的一个 hash 值 做一个对应。该 Query 所取数据的基表发生任何数据的变化之后, MySQL 会自动使该 query 的Cache 失效。在读写比例非常高的应用系统中, Query Cache 对性能的提高是非常显著的。当然它对内存的消耗也是非常大的。

如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等

  • 8 、存储引擎接口

存储引擎接口模块可以说是 MySQL 数据库中最有特色的一点了。目前各种数据库产品中,基本上只有 MySQL 可以实现其底层数据存储引擎的插件式管理。这个模块实际上只是 一个抽象类,但正是因为它成功地将各种数据处理高度抽象化,才成就了今天 MySQL 可插拔存储引擎的特色。

从图中还可以看出,MySQL区别于其他数据库的最重要的特点就是其插件式的表存储引擎。MySQL插件式的存储引擎架构提供了一系列标准的管理和服务支持,这些标准与存储引擎本身无关,可能是每个数据库系统本身都必需的,如SQL分析器和优化器等,而存储引擎是底层物理结构的实现,每个存储引擎开发者都可以按照自己的意愿来进行开发。

注意:存储引擎是基于表的,而不是数据库。

查看命令:

  • 查看mysql以提供什么存储引擎

    • show engines;

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

    • show variables like '%storage_engine%';

    mysql> show variables like '%storage_engine%';
    +----------------------------------+--------+
    | Variable_name | Value |
    +----------------------------------+--------+
    | default_storage_engine | InnoDB |
    | default_tmp_storage_engine | InnoDB |
    | disabled_storage_engines | |
    | internal_tmp_disk_storage_engine | InnoDB |
    +----------------------------------+--------+
    4 rows in set, 1 warning (0.01 sec)

MyISAM和InnoDB

阿里巴巴、淘宝用哪个?

  • Percona为MySQL数据库服务器进行了改进,在功能和性能上较MySQL有着很显著的提升。该版本提升了在高负载情况下的InnoDB的性能、为DBA提供一些非常有用的性能诊断工具;另外有更多的参数和命令来控制服务器行为。

  • 该公司新建了一款存储引擎叫xtradb完全可以替代innodb,并且在性能和并发上做得更好,

  • 阿里巴巴大部分mysql数据库其实使用的percona的原型加以修改。

  • AliSql+AliRedis

二、索引优化分析

1.查询语句写的烂 没建索引

2.索引失效

  • 单值

    select * from user where name='';
    create index idx_user_name on user(name);

  • 复合

    select * from user where name='' and email='';
    create index idx_user_name on user(name, email);

3.关联查询太多join(设计缺陷或不得已的需求)

4.服务器调优及各个参数设置(缓冲、线程数等)

SELECT DISTINCT
    <select_list>
FROM
    <left_table> <join_type>
JOIN
    <right_table>
ON
    <join_condition>
WHERE
    <where_condition>
GROUP BY
    <group_by_list>
HAVING
    <having_condition>
ORDER BY
    <order_by_condition>
LIMIT
    <limit_number>

机读 先从from开始读

1 FROM <left_table>
2 ON <join_condition>
3 <join_type> JOIN <right_table>
4 WHERE <where_condition>
5 GROUP BY <group_by_list>
6 HAVING <having_condition>
7 SELECT
8 DISTINCT <select_list>
9 ORDER BY <order_by_condition>
10 LIMIT <limit_number>

总结

  • 针对情况1

select * from A inner join B on A.key=B.key inner表示需要内查询 AB的公共部分

  • 针对情况2

select * from A left join B on A.key=B.key left左表示需要左表的全部 AB的公共部分+A的独有

  • 针对情况3

select * from A right join B on A.key=B.key right右表示需要右表的全部 AB的公共部分+B的独有

  • 针对情况4 A要独占

select * from A left join B on A.key=B.key where B.key is NULL left 左表示需要左表的全部 A所有的部分+非AB的公共部分

  • 针对情况5 B要独占

select * from A right join B on A.key=B.key where B.key is NULL right 右表示需要右表的全部 B所有的部分+非AB的公共部分

七种JOIN的SQL编写

准备工作,创建以下表,插入新数据:

CREATE TABLE tbl_dept(
    id INT(11) NOT NULL AUTO_INCREMENT,
    deptName VARCHAR(30) DEFAULT NULL,
    locAdd VARCHAR(40) DEFAULT NULL,
    PRIMARY KEY(id)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE tbl_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 'tbl_dept'('Id')
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO tbl_dept(deptName,locAdd) VALUES('RD',11);
INSERT INTO tbl_dept(deptName,locAdd) VALUES('HR',12);
INSERT INTO tbl_dept(deptName,locAdd) VALUES('MK',13);
INSERT INTO tbl_dept(deptName,locAdd) VALUES('MIS',14);
INSERT INTO tbl_dept(deptName,locAdd) VALUES('FD',15);

INSERT INTO tbl_emp(NAME,deptId) VALUES('z3',1);
INSERT INTO tbl_emp(NAME,deptId) VALUES('z4',1);
INSERT INTO tbl_emp(NAME,deptId) VALUES('z5',1);
INSERT INTO tbl_emp(NAME,deptId) VALUES('w5',2);
INSERT INTO tbl_emp(NAME,deptId) VALUES('w6',2);
INSERT INTO tbl_emp(NAME,deptId) VALUES('s7',3);
INSERT INTO tbl_emp(NAME,deptId) VALUES('s8',4);
INSERT INTO tbl_emp(NAME,deptId) VALUES('s9',51);

执行后的结果:

mysql> select * from tbl_dept;
+----+----------+--------+
| id | deptName | locAdd |
+----+----------+--------+
|  1 | RD       | 11     |
|  2 | HR       | 12     |
|  3 | MK       | 13     |
|  4 | MIS      | 14     |
|  5 | FD       | 15     |
+----+----------+--------+
5 rows in set (0.00 sec)

mysql> select * from tbl_emp;
+----+------+--------+
| id | NAME | deptId |
+----+------+--------+
|  1 | z3   |      1 |
|  2 | z4   |      1 |
|  3 | z5   |      1 |
|  4 | w5   |      2 |
|  5 | w6   |      2 |
|  6 | s7   |      3 |
|  7 | s8   |      4 |
|  8 | s9   |     51 |
+----+------+--------+
8 rows in set (0.00 sec)
#这就是为什么需要join from优先级最高,而笛卡尔积查询的太多了
mysql> select * from tbl_dept,tbl_emp;
+----+----------+--------+----+------+--------+
| id | deptName | locAdd | id | NAME | deptId |
+----+----------+--------+----+------+--------+
|  1 | RD       | 11     |  1 | z3   |      1 |
|  2 | HR       | 12     |  1 | z3   |      1 |
|  3 | MK       | 13     |  1 | z3   |      1 |
|  4 | MIS      | 14     |  1 | z3   |      1 |
|  5 | FD       | 15     |  1 | z3   |      1 |
|  1 | RD       | 11     |  2 | z4   |      1 |
|  2 | HR       | 12     |  2 | z4   |      1 |
|  3 | MK       | 13     |  2 | z4   |      1 |
|  4 | MIS      | 14     |  2 | z4   |      1 |
|  5 | FD       | 15     |  2 | z4   |      1 |
|  1 | RD       | 11     |  3 | z5   |      1 |
|  2 | HR       | 12     |  3 | z5   |      1 |
|  3 | MK       | 13     |  3 | z5   |      1 |
|  4 | MIS      | 14     |  3 | z5   |      1 |
|  5 | FD       | 15     |  3 | z5   |      1 |
|  1 | RD       | 11     |  4 | w5   |      2 |
|  2 | HR       | 12     |  4 | w5   |      2 |
|  3 | MK       | 13     |  4 | w5   |      2 |
|  4 | MIS      | 14     |  4 | w5   |      2 |
|  5 | FD       | 15     |  4 | w5   |      2 |
|  1 | RD       | 11     |  5 | w6   |      2 |
|  2 | HR       | 12     |  5 | w6   |      2 |
|  3 | MK       | 13     |  5 | w6   |      2 |
|  4 | MIS      | 14     |  5 | w6   |      2 |
|  5 | FD       | 15     |  5 | w6   |      2 |
|  1 | RD       | 11     |  6 | s7   |      3 |
|  2 | HR       | 12     |  6 | s7   |      3 |
|  3 | MK       | 13     |  6 | s7   |      3 |
|  4 | MIS      | 14     |  6 | s7   |      3 |
|  5 | FD       | 15     |  6 | s7   |      3 |
|  1 | RD       | 11     |  7 | s8   |      4 |
|  2 | HR       | 12     |  7 | s8   |      4 |
|  3 | MK       | 13     |  7 | s8   |      4 |
|  4 | MIS      | 14     |  7 | s8   |      4 |
|  5 | FD       | 15     |  7 | s8   |      4 |
|  1 | RD       | 11     |  8 | s9   |     51 |
|  2 | HR       | 12     |  8 | s9   |     51 |
|  3 | MK       | 13     |  8 | s9   |     51 |
|  4 | MIS      | 14     |  8 | s9   |     51 |
|  5 | FD       | 15     |  8 | s9   |     51 |
+----+----------+--------+----+------+--------+
40 rows in set (0.04 sec)

1.inner join

mysql> select * from tbl_emp a inner join tbl_dept b on a.deptId = b.id;
+----+------+--------+----+----------+--------+
| id | NAME | deptId | id | deptName | locAdd |
+----+------+--------+----+----------+--------+
|  1 | z3   |      1 |  1 | RD       | 11     |
|  2 | z4   |      1 |  1 | RD       | 11     |
|  3 | z5   |      1 |  1 | RD       | 11     |
|  4 | w5   |      2 |  2 | HR       | 12     |
|  5 | w6   |      2 |  2 | HR       | 12     |
|  6 | s7   |      3 |  3 | MK       | 13     |
|  7 | s8   |      4 |  4 | MIS      | 14     |
+----+------+--------+----+----------+--------+
7 rows in set (0.00 sec)

2.left join

mysql> select * from tbl_emp a left join tbl_dept b on a.deptId = b.id;
+----+------+--------+------+----------+--------+
| id | NAME | deptId | id   | deptName | locAdd |
+----+------+--------+------+----------+--------+
|  1 | z3   |      1 |    1 | RD       | 11     |
|  2 | z4   |      1 |    1 | RD       | 11     |
|  3 | z5   |      1 |    1 | RD       | 11     |
|  4 | w5   |      2 |    2 | HR       | 12     |
|  5 | w6   |      2 |    2 | HR       | 12     |
|  6 | s7   |      3 |    3 | MK       | 13     |
|  7 | s8   |      4 |    4 | MIS      | 14     |
|  8 | s9   |     51 | NULL | NULL     | NULL   |
+----+------+--------+------+----------+--------+
8 rows in set (0.00 sec)

3.right join

mysql> select * from tbl_emp a right join tbl_dept b on a.deptId = b.id;
+------+------+--------+----+----------+--------+
| id   | NAME | deptId | id | deptName | locAdd |
+------+------+--------+----+----------+--------+
|    1 | z3   |      1 |  1 | RD       | 11     |
|    2 | z4   |      1 |  1 | RD       | 11     |
|    3 | z5   |      1 |  1 | RD       | 11     |
|    4 | w5   |      2 |  2 | HR       | 12     |
|    5 | w6   |      2 |  2 | HR       | 12     |
|    6 | s7   |      3 |  3 | MK       | 13     |
|    7 | s8   |      4 |  4 | MIS      | 14     |
| NULL | NULL |   NULL |  5 | FD       | 15     |
+------+------+--------+----+----------+--------+
8 rows in set (0.00 sec)

4.left join 2

mysql> select * from tbl_emp a left join tbl_dept b on a.deptId = b.id where b.id is null;
+----+------+--------+------+----------+--------+
| id | NAME | deptId | id   | deptName | locAdd |
+----+------+--------+------+----------+--------+
|  8 | s9   |     51 | NULL | NULL     | NULL   |
+----+------+--------+------+----------+--------+
1 row in set (0.00 sec)

5.right join 2

mysql> select * from tbl_emp a right join tbl_dept b on a.deptId = b.id where a.deptid is null;
+------+------+--------+----+----------+--------+
| id   | NAME | deptId | id | deptName | locAdd |
+------+------+--------+----+----------+--------+
| NULL | NULL |   NULL |  5 | FD       | 15     |
+------+------+--------+----+----------+--------+
1 row in set (0.00 sec)

6.full join

MySQL不支持full join,不过可以换种方法表示

mysql> select * from tbl_emp a left join tbl_dept b on a.deptId = b.id
    -> union
    -> select * from tbl_emp a right join tbl_dept b on a.deptId = b.id;
+------+------+--------+------+----------+--------+
| id   | NAME | deptId | id   | deptName | locAdd |
+------+------+--------+------+----------+--------+
|    1 | z3   |      1 |    1 | RD       | 11     |
|    2 | z4   |      1 |    1 | RD       | 11     |
|    3 | z5   |      1 |    1 | RD       | 11     |
|    4 | w5   |      2 |    2 | HR       | 12     |
|    5 | w6   |      2 |    2 | HR       | 12     |
|    6 | s7   |      3 |    3 | MK       | 13     |
|    7 | s8   |      4 |    4 | MIS      | 14     |
|    8 | s9   |     51 | NULL | NULL     | NULL   |
| NULL | NULL |   NULL |    5 | FD       | 15     |
+------+------+--------+------+----------+--------+
9 rows in set (0.00 sec)

7.full join 2

mysql> select * from tbl_emp a left join tbl_dept b on a.deptId = b.id where b.id is null
     -> union
     -> select * from tbl_emp a right join tbl_dept b on a.deptId = b.id where a.deptId is null;
+------+------+--------+------+----------+--------+
| id   | NAME | deptId | id   | deptName | locAdd |
+------+------+--------+------+----------+--------+
|    8 | s9   |     51 | NULL | NULL     | NULL   |
| NULL | NULL |   NULL |    5 | FD       | 15     |
+------+------+--------+------+----------+--------+
2 rows in set (0.00 sec)

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

索引的目的在于提高查询效率,可以类比字典。

如果要查“mysql”这个单词,我们肯定需要定位到m字母,然后从下往下找到y字母,再找到剩下的sql。如果没有索引,那么你可能需要逐个逐个寻找,如果我想找到Java开头的单词呢?或者Oracle开头的单词呢?

你可以简单理解为“排好序的快速查找数据结构”。

  • 排好序
  • 快速查找

在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图就是一种可能的索引方式示例:

左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址。

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

总结来说,为什么索引查找会快呢?想想使用二叉查找树去查询肯定比顺序遍历要快啊!一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。

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

一个注意点:

一般在企业中,delete了一条数据,仅仅只是把这一条记录的active修改了false_,其中调用service层的delete实际上很多都是update_,只能说是逻辑上进行了删除,但是其底层数据库的记录依旧保留着。其原因之一:一个是为了数据分析;另外一个就是索引(数据被删除了索引可能不准确了,其位置不再固定了)。当数据频繁的增加,修改后,这个二叉查找树就不准确了。(干脆把索引重建一遍)。

①索引优劣势

  • 优势

类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本。

通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗。

  • 劣势

实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的(占空间)

虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。

索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询。

  • 总结

索引,空间换取时间。

②索引分类和建索引命令语句

MySQL索引分类:

  • 单值索引:即一个索引只包含单个列,一个表可以有多个单列索引。(建议一张表的索引不要超过5个 优先考虑复合索引

  • 唯一索引:索引列的值必须唯一,但允许有空值。

  • 复合索引:即一个索引包含多个列。

  • 基本语法:

    • 创建

      • CREATE [UNIQUE] INDEX indexName ON mytable(columnName(length));
      • ALTER mytable ADD [UNIQUE] INDEX [indexName] ON (columnName(length));
    • 删除

      • DROP INDEX [indexName] ON mytable;
    • 查看

      • SHOW INDEX FROM tableName
    • 使用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,用于全文索引。

③索引结构与检索原理

MySQL索引结构

  • BTree索引
  • Hash索引
  • full-text全文索引
  • R-Tree索引

BTree索引检索原理

初始化介绍

一颗b+树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,

P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。

_真实的数据存在于叶子节点即_3、5、9、10、13、15、28、29、36、60、75、79、90、99。

非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。(作为参考比较值)

查找过程

如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO。在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。树越矮越好

真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。

④哪些情况适合建索引

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

⑤哪些情况不适合建索引

  1. 表记录太少
  2. 经常增删改的表(不建,虽然提高了查询速度,但是降低了更新的效率,不但要更新字段,还得更新对应的索引信息)
  3. 数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。注意,如果某列包含许多重复的内容,为它建立索引就没有太大的实际效果。

假如一个表有10万行记录,有一个字段A只有T和F两种值,且每个值的分布概率天约为50%,那么对这种表A字段建索引一般不会提高数据库的查询速度

索引的选择性是指索引列中不同值的数目与表中记录数的比。如果一个表中有2000条记录,表索引列有1980个不同的值,那么这个索引的选择性就是1980/2000=0.99。一个索引的选择性越接近于1,这个索引的效率就越高。

MySQL Query Optimizer

Mysql中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划(他认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间)

当客户端向MySQL请求一条Query,命令解析器模块完成请求分类,区别出是SELECT并转发给MySQL Query Optimizer时,MySQL Query Optimizer首先会对整条Query进行优化,处理掉一些常量表达式的预算直接换算成常量值。并对Query中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析Query 中的 Hint信息(如果有),看显示Hint信息是否可以完全确定该Query的执行计划。如果没有Hint 或Hint信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据Query进行写相应的计算分析,然后再得出最后的执行计划。

MySQL常见瓶颈

  • CPU:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候
  • IO:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候
  • 服务器硬件的性能瓶颈:top,free,iostat和vmstat来查看系统的性能状态

①explain使用简介

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

官网地址

能干嘛

  • 表的读取顺序
  • 数据读取操作的操作类型
  • 哪些索引可以使用
  • 哪些索引被实际使用
  • 表之间的引用
  • 每张表有多少行被优化器查询

怎么玩

  • explain + SQL语句

  • 执行计划包含的信息

    • | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |

    mysql> select * from tbl_emp;
    +----+------+--------+
    | id | NAME | deptId |
    +----+------+--------+
    | 1 | z3 | 1 |
    | 2 | z4 | 1 |
    | 3 | z5 | 1 |
    | 4 | w5 | 2 |
    | 5 | w6 | 2 |
    | 6 | s7 | 3 |
    | 7 | s8 | 4 |
    | 8 | s9 | 51 |
    +----+------+--------+
    8 rows in set (0.01 sec)

    mysql> explain select * from tbl_emp;
    +----+-------------+---------+------+---------------+------+---------+------+------+-------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+---------+------+---------------+------+---------+------+------+-------+
    | 1 | SIMPLE | tbl_emp | ALL | NULL | NULL | NULL | NULL | 8 | |
    +----+-------------+---------+------+---------------+------+---------+------+------+-------+
    1 row in set (0.01 sec)

②explain之id介绍

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

三种情况:

  • id相同,执行顺序由上至下

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

  • id相同不同,同时存在

id相同,执行顺序由上至下

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

id相同不同,同时存在

小结

  • id如果相同,可以认为是一组,从上往下顺序执行;
  • 在所有组中,id值越大,优先级越高,越先执行,衍生=DERIVED(就是那张虚表)drived2中的那个2表示是id为2的那个表延申出来的虚表
  • 永远是id越大越先查询

③explain之select_type和table介绍

select_type:查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。

select_type有哪些?

  1. SIMPLE - 简单的select查询,查询中不包含子查询或者UNION。
  2. PRIMARY - 查询中若包含任何复杂的子部分,最外层查询则被标记为。(相当于鸡蛋壳,最后被加载的)
  3. SUBQUERY - 在SELECT或WHERE列表中包含了子查询。(鸡蛋黄,先被加载的)
  4. DERIUED - 在FROM列表中包含的子查询被标记为DERIVED(衍生)MySQL会递归执行这些子查询,把结果放在临时表里。
  5. UNION - 若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中外层SELECT将被标记为:DERIVED。
  6. UNION RESULT - 从UNION表获取结果的SELECT。

table:显示这一行的数据是关于哪张表的。

④explain之type介绍

访问类型排列

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类型的特列,平时不会出现,这个也可以忽略不计。(单表一条记录

  • const

表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快如将主键置于where列表中,MySQL就能将该查询转换为一个常量。下面的system是因为符合查询中只会返回一条记录作为临时表。

  • 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。

⑤explain之possible_keys和key介绍

possible_keys

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

key

是否使用到了索引;在多个索引存在的情况下,MySQL究竟使用到了哪一个索引。

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

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

⑥explain之key_len介绍

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好

key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的

⑦explain之ref介绍

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

由key_len可知t1表的idx_col1_col2_索引被充分使用_,col1匹配t2表的col1,col2匹配了一个常量,即 ‘ac’。

shared库的t2表的col1;

查询中与其它表关联的字段,外键关系建立索引。

⑧explain之rows介绍

根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。(每张表有多少行被优化器查询过)

⑨explain之Extra介绍

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

①Using filesort 九死一生

说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为"文件排序" \G表示使用key-value的显示方式进行输出

你建立的索引我只是部分使用到了,查询的时候我只是部分使用到了索引,排序我并没有用到。

对比下面两者,其中上面只用到了col1和col3,中间col2没了;下者使用到了col1、col2、col3;

把建索引比喻成修好的楼梯,col1、col2、col3修了三条路;

上面的部分相当于自己又进行了一次排序的步骤,内部的折腾多了一次;而下面的就是一步步来的,显然下面的效率要高于上面的部分。

②Using temporary 必死

使了用临时表保存中间结果,MysQL在对查询结果排序时使用临时表。常见于排序order by和分组查询group by。

order by和分组查询group by经常是拖慢SQL的元凶。

上面的部分:建立的复合索引(col1,col2),但是只用到了col2,没有一楼而直接上二楼,所以需要临时表和文件排序进行处理;

下面的部分:建立的是col1、col2,使用的也是col1、col2;

order by和分组查询group by最好和建立的索引顺序一致;

③Using index

表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!

如果同时出现using where,表明索引被用来执行索引键值的查找;

如果没有同时出现using where,表明索引用来读取数据而非执行查找动作。

如果同时出现using where,表明索引被用来执行索引键值的查找;

覆盖索引(Covering Index),一说为索引覆盖。

1、建是索引是复合索引(1,2,3);

2、没有写select * ;刚好查的就是 select 1 2 3或者select 1 2;这就是覆盖索引;

理解方式一:就是select的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖

理解方式二:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了(或覆盖了)满足查询结果的数据就叫做覆盖索引。

注意:

如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select*,因为

如果将所有字段一起做索引会导致索引文件过大,查询性能下降。

What is a Covering Index?

A covering index is an index that contains all of, and possibly more, the columns you need for your query.

For instance, this:

SELECT *

FROM tablename

WHERE criteria

will typically use indexes to speed up the resolution of which rows to retrieve using criteria, but then it will go to the full table to retrieve the rows.

However, if the index contained the columns column1, column2 and column3, then this sql:

SELECT column1, column2

FROM tablename

WHERE criteria

and, provided that particular index could be used to speed up the resolution of which rows to retrieve, the index already contains the values of the columns you’re interested in, so it won’t have to go to the table to retrieve the rows, but can produce the results directly from the index.

This can also be used if you see that a typical query uses 1-2 columns to resolve which rows, and then typically adds another 1-2 columns, it could be beneficial to append those extra columns (if they’re the same all over) to the index, so that the query processor can get everything from the index itself.

④Using where

表明使用了where过滤。

⑤Using join buffer

使用了连接缓存。

⑥impossible where

where子句的值总是false,不能用来获取任何元组。

⑦select tables optimized away(用的不多)

在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作,或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。

⑧distinct(用的不多)

优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作。

⑩explain之热身Case

第一行(执行顺序4):id列为1,表示是union里的第一个select,select_type列的primary表示该查询为外层查询,table列被标记为,表示查询结果来自一个衍生表,其中derived3中3代表该查询衍生自第三个select查询,即id为3的select。【select d1.name… 】

第二行(执行顺序2):id为3,是整个查询中第三个select的一部分。因查询包含在from中,所以为derived。【select id,namefrom 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列的表示用第一个和第四个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 VARCHAR(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');
mysql> select * from article;
+----+-----------+-------------+-------+----------+-------+---------+
| id | author_id | category_id | views | comments | title | content |
+----+-----------+-------------+-------+----------+-------+---------+
|  1 |         1 |           1 |     1 |        1 | 1     | 1       |
|  2 |         2 |           2 |     2 |        2 | 2     | 2       |
|  3 |         1 |           1 |     3 |        3 | 3     | 3       |
+----+-----------+-------------+-------+----------+-------+---------+
3 rows in set (0.00 sec)
  • 查询category_id为1且comments 大于1的情况下,views最多的article_id (只有一条)

    mysql> SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
    +----+-----------+
    | id | author_id |
    +----+-----------+
    | 3 | 1 |
    +----+-----------+
    1 row in set (0.00 sec)

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

开始优化

新建索引+删除索引

mysql> create index idx_article_ccv on article(category_id,comments,views);
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

或者用下面sql创建索引:

ALTER TABLE 'article' ADD INDEX idx_article_ccv ( 'category_id , 'comments', 'views' );

创建后的效果

Extra里还是出现了Using filesort,创建这索引作用不大。

如果comments > 1换成comments = 1,可以让Using filesort消失,但不符题目要求。

既然创建这索引作用不大,删了它吧。

mysql> DROP INDEX idx_article_ccv ON article;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

缘由

type变成了range,这是可以忍受的。但是extra里使用Using filesort仍是无法接受的。

但是我们已经建立了索引,为啥没用呢?

这是因为按照BTree索引的工作原理:

  • 先排序category_id
  • 如果遇到相同的category_id则再排序comments
  • 如果遇到相同的comments 则再排序views。

当comments字段在联合索引里处于中间位置时,因comments > 1条件是一个范围值(所谓range),范围以后的索引会失效,MySQL无法利用索引再对后面的views部分进行检索,即range类型查询字段后面的索引无效。

改进

跟上次创建索引相比,这次不为comments字段创建索引。

mysql> create index idx_article_cv on article(category_id, views);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

再次explain

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

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

总结:

尽量不要对range类型(一般就是在你的where语句中出现了between、<、>、in等的查询)的查询字段建索引!

②索引两表优化案例

新建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 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)));

创建后的结果:

mysql> select * from class;
+----+------+
| id | card |
+----+------+
|  1 |   17 |
|  2 |    2 |
|  3 |   18 |
|  4 |    4 |
|  5 |    4 |
|  6 |    8 |
|  7 |    9 |
|  8 |    1 |
|  9 |   18 |
| 10 |    6 |
| 11 |   15 |
| 12 |   15 |
| 13 |   12 |
| 14 |   15 |
| 15 |   18 |
| 16 |    2 |
| 17 |   18 |
| 18 |    5 |
| 19 |    7 |
| 20 |    1 |
| 21 |    2 |
+----+------+
21 rows in set (0.00 sec)

mysql> select * from book;
+--------+------+
| bookid | card |
+--------+------+
|      1 |    8 |
|      2 |   14 |
|      3 |    3 |
|      4 |   16 |
|      5 |    8 |
|      6 |   12 |
|      7 |   17 |
|      8 |    8 |
|      9 |   10 |
|     10 |    3 |
|     11 |    4 |
|     12 |   12 |
|     13 |    9 |
|     14 |    7 |
|     15 |    6 |
|     16 |    8 |
|     17 |    3 |
|     18 |   11 |
|     19 |    5 |
|     20 |   11 |
+--------+------+
20 rows in set (0.00 sec)

开始explain分析 两个表有主外键,索引应该加在哪张表上?

mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;

type都是all,需要优化,且查询的记录数量为 21+20!

为book.card创建索引

mysql> ALTER TABLE `book` ADD INDEX Y(`card`);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

再次explain分析

mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;

可以看到第二行的type变为了ref,rows也变少了,优化比较明显。这是由左连接特性决定的。LEFT JOIN的特性就是左表全都有;条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要在右表建立索引。

删除为book.card创建索引

mysql> drop index y on book;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

为class.card创建索引

mysql> ALTER TABLE `class` ADD INDEX Y(`card`);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

再次explain分析

mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;

可见右边是我们的关键点,要想优化需要在右表建立索引。

然后我们换用右连接RIGHT JOIN查询

mysql> EXPLAIN SELECT * FROM class right JOIN book ON class.card = book.card;

换成左边是我们的关键点,要想优化需要在左表建立索引。

小结

索引两表优化,左连接右表建索引,右连接左表建索引。

③索引三表优化案例

新建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)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));

建后效果

mysql> select * from phone;
+---------+------+
| phoneid | card |
+---------+------+
|       1 |   10 |
|       2 |   13 |
|       3 |   17 |
|       4 |    5 |
|       5 |   12 |
|       6 |    7 |
|       7 |   15 |
|       8 |   17 |
|       9 |   17 |
|      10 |   14 |
|      11 |   19 |
|      12 |   13 |
|      13 |    5 |
|      14 |    8 |
|      15 |    2 |
|      16 |    8 |
|      17 |   11 |
|      18 |   14 |
|      19 |   13 |
|      20 |    5 |
+---------+------+
20 rows in set (0.00 sec)

复用到上一节book,class两表,移除它们原有的索引。

mysql> show index from class;
mysql> drop index y on class;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> show index from book;

为phone.card和book.card创建新的索引。

mysql> alter table `phone` add index z(`card`);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table `book` add index y(`card`);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

explain三表连接 三表至少两次关联,索引应该建在哪张表上?

mysql> explain SELECT * FROM class LEFT JOIN book ON class.card = book.card LEFT JOIN phone ON book.card = phone.card;

后2行的 type 都是ref且总 rows优化很好,效果不错。因此索引最好设置在需要经常查询的字段中。

结论

Join语句的优化

尽可能减少Join语句中的NestedLoop的循环总次数(不要join过多或者嵌套):“永远用小结果集驱动大的结果集”。

优先优化NestedLoop的内层循环,保证Join语句中被驱动表上Join条件字段已经被索引。

当无法保证被驱动表的Join条件字段被索引且内存资源充足的前提下,不要太吝惜JoinBuffer(在my.cnf文件中)的设置。

索引失效的各种原因:(应该避免)

  1. 全值匹配我最爱
  2. 最佳左前缀法则 - 如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过复合索引中间列
  3. 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描。
  4. 存储引擎不能使用索引中范围条件右边的列。
  5. 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *。
  6. mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描。
  7. is null, is not null 也无法使用索引。
  8. like以通配符开头(’%abc…’),mysql索引失效会变成全表扫描的操作。
  9. 字符串不加单引号索引失效。
  10. 少用or,用它来连接时会索引失效。

①全值匹配我最爱

新建SQL

CREATE TABLE staffs(
    id INT 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());

ALTER TABLE staffs ADD INDEX index_staffs_nameAgePos(`name`,`age`,`pos`);

三种情况:

mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July';

mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND age=25;

mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND age=25 AND pos='dev';

注意下面的explain 我们建立索引的顺序是:(name、age、pos)

mysql> EXPLAIN SELECT * FROM staffs WHERE age=25 AND pos='dev';

mysql> EXPLAIN SELECT * FROM staffs WHERE pos='dev';

上面两个索引失效了。如果查询字段的顺序与索引的建立的顺序不一致,需要满足最佳左前缀法则。

②最佳左前缀法则

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

带头大哥不能死! 中间兄弟不能断!

mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND pos='dev';

ref只有一个const,说明上述其实只是使用到了部分索引,而且只是用到了第一个索引,name。

②索引列上做额外操作

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

mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July';
mysql> EXPLAIN SELECT * FROM staffs WHERE left(NAME,4)='July';

③限定复合索引某列的范围

存储引擎不能使用索引中范围条件右边的列(我理解为限定复合索引某字段的范围会时索引失效,也就是>,<,between…and…谨慎用在复合索引某字段)。

mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July';

mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND age=25;

mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND age=25 AND pos='dev';

mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND age>25 AND pos='dev';

由age=25变成age>25后,type从ref变成range。

④select *

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

mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND age=25 AND pos='manager';
mysql> EXPLAIN SELECT name,age,pos FROM staffs WHERE NAME='July' AND age=25 AND pos='dev';

我们最好按需索取,少用select *

mysql> EXPLAIN SELECT name,age,pos FROM staffs WHERE NAME='July' AND pos='dev';

mysql> EXPLAIN SELECT name,age,pos FROM staffs WHERE NAME='July' AND age>25 AND pos='dev';

mysql> EXPLAIN SELECT name FROM staffs WHERE NAME='July' AND age=25;

⑤!=或者<>

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

mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July';
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME!='July';
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME<>'July';

⑥is null或者is not null

is null, is not null 也无法使用索引

mysql> EXPLAIN SELECT * FROM staffs WHERE NAME is null;

mysql> EXPLAIN SELECT * FROM staffs WHERE NAME is not null;

Extra打印为Impossible WHERE,是因为我们在创建staffs表,设置name字段的属性为not null。

下面额外演示Extra为Impossible WHERE情况。

mysql> EXPLAIN SELECT * FROM staffs WHERE 1=1;
mysql> EXPLAIN SELECT * FROM staffs WHERE 1!=1;

⑦like以通配符%开头字符串

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

mysql> EXPLAIN SELECT * FROM staffs WHERE NAME='July';
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME like '%July%';
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME like '%July';
mysql> EXPLAIN SELECT * FROM staffs WHERE NAME like 'July%';

问题:解决like '%字符串%'时索引不被使用的方法?

  • 可以使用主键索引
  • 推荐使用覆盖索引,查询字段必须是建立覆盖索引字段
  • 当覆盖索引指向的字段是varchar(380)及380以上的字段时,覆盖索引会失效!

新建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,'a@163.com');
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('2bb2',23,'b@163.com');
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('3cc3',24,'c@163.com');
INSERT INTO tbl_user(`name`,`age`,`email`)VALUES('4dd4',26,'d@163.com');


mysql> select * from tbl_user;
+----+------+------+-----------+
| id | name | age  | email     |
+----+------+------+-----------+
|  1 | 1aa1 |   21 | a@163.com |
|  2 | 2bb2 |   23 | b@163.com |
|  3 | 3cc3 |   24 | c@163.com |
|  4 | 4dd4 |   26 | d@163.com |
+----+------+------+-----------+
4 rows in set (0.00 sec)

创建索引前,先看看以下explain:

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%';
EXPLAIN SELECT * FROM tbl_user WHERE NAME LIKE '%aa%';
EXPLAIN SELECT id,NAME,age,email FROM tbl_user WHERE NAME LIKE '%aa%';


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

mysql> EXPLAIN SELECT id FROM tbl_user WHERE NAME LIKE '%aa%';

mysql> EXPLAIN SELECT NAME FROM tbl_user WHERE NAME LIKE '%aa%';

mysql> EXPLAIN SELECT age FROM tbl_user WHERE NAME LIKE '%aa%';

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

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

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

mysql> EXPLAIN SELECT * FROM tbl_user WHERE NAME LIKE '%aa%';
mysql> EXPLAIN SELECT id,NAME,age,email FROM tbl_user WHERE NAME LIKE '%aa%';

没有创建索引,都全表查找。

现在创建索引

mysql> CREATE INDEX idx_user_nameAge ON tbl_user(NAME,age);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

再执行上述一系列explain

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

看得出,用上索引(覆盖索引)

mysql> EXPLAIN SELECT id FROM tbl_user WHERE NAME LIKE '%aa%';

mysql> EXPLAIN SELECT NAME FROM tbl_user WHERE NAME LIKE '%aa%';

mysql> EXPLAIN SELECT age FROM tbl_user WHERE NAME LIKE '%aa%';

看得出,都用上索引(覆盖索引)

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

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

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

看得出,都用上索引(覆盖索引)

mysql> EXPLAIN SELECT * FROM tbl_user WHERE NAME LIKE '%aa%';
mysql> EXPLAIN SELECT id,NAME,age,email FROM tbl_user WHERE NAME LIKE '%aa%';

看得出,都没用上索引了,有email字段再,只能全表搜索。

2)覆盖索引(Covering Index)

一说为索引覆盖。 类比锅和锅盖,查询的字段要小于等于创建的索引 (注意:主键id天生自带索引,故而主键字段对与覆盖索引无影响)。

理解方式一:就是select的数据列只用从索引中就能够取得,不必读取数据行, MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖

理解方式二:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了(或覆盖了)满足查询结果的数据就叫做覆盖索引。

注意:

如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select*,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。

小结

解决like '%字符串%'时索引不被使用的方法?复合索引,然后覆盖索引。

⑧数目字符串不加单引号

数目字符串不加单引号索引失效。 varchar类型一定要加单引号!!!

MySQL底层发送了隐式的类型转换,影响效率。

mysql> SELECT * FROM staffs WHERE NAME=2000;
+----+------+-----+-----+---------------------+
| id | name | age | pos | add_time            |
+----+------+-----+-----+---------------------+
|  3 | 2000 |  23 | dev | 2021-04-03 14:03:18 |
+----+------+-----+-----+---------------------+
1 row in set, 1 warning (0.00 sec)

mysql> SELECT * FROM staffs WHERE NAME='2000';
+----+------+-----+-----+---------------------+
| id | name | age | pos | add_time            |
+----+------+-----+-----+---------------------+
|  3 | 2000 |  23 | dev | 2021-04-03 14:03:18 |
+----+------+-----+-----+---------------------+
1 row in set (0.00 sec)

mysql> explain SELECT * FROM staffs WHERE NAME=2000;
mysql> explain SELECT * FROM staffs WHERE NAME='2000';

⑨用关键字OR

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

mysql> explain SELECT * FROM staffs WHERE NAME='July' or name='z3';
mysql> SELECT * FROM staffs WHERE NAME='July' or name='z3';
+----+------+-----+---------+---------------------+
| id | name | age | pos     | add_time            |
+----+------+-----+---------+---------------------+
|  1 | z3   |  22 | manager | 2021-04-03 14:03:18 |
|  2 | July |  23 | dev     | 2021-04-03 14:03:18 |
+----+------+-----+---------+---------------------+
2 rows in set (0.00 sec)

⑩小总结where之后order by之前

小总结

假设 创建的索引:index(a,b,c)

Where 语句

索引是否被使用

where a = 3

Y,使用到 a

where a = 3 and b = 5

Y,使用到 a,b

where a = 3 and b = 5 and c = 4

Y,使用到 a,b,c

where b = 3 或者 where b = 3 and c = 4 或者 where c = 4

N (没有大哥a

where a = 3 and c = 5

使用到 a, 但是 c 不可以,b 中间断了

where a = 3 and b > 4 and c = 5

使用到 a 和 b, c 不能用在范围之后,b 断了

where a is null and b is not null

is null 支持索引 但是 is not null 不支持,所以 a 可以使用索引,但是 b 不可以使用

where a <> 3

不能使用索引

where abs(a) =3

不能使用 索引

where a = 3 and b like 'kk%' and c = 4

Y,使用到 a,b,c

where a = 3 and b like '%kk' and c = 4

Y,只用到 a

where a = 3 and b like '%kk%' and c = 4

Y,只用到 a

where a = 3 and b like 'k%kk%' and c = 4

Y,使用到 a,b,c

优化总结口诀

全值匹配我最爱, 最左前缀要遵守;

带头大哥不能死, 中间兄弟不能断;

索引列上少计算, 范围之后全失效;

LIKE 百分写最右, 覆盖索引不写 *;

不等空值还有 OR, 索引影响要注意;

VAR 引号不可丢, 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');

create index idx_test03_c1234 on test03(c1,c2,c3,c4);


mysql> select * from test03;
+----+------+------+------+------+------+
| id | c1   | c2   | c3   | c4   | c5   |
+----+------+------+------+------+------+
|  1 | a1   | a2   | a3   | a4   | a5   |
|  2 | b1   | b2   | b3   | b4   | b5   |
|  3 | c1   | c2   | c3   | c4   | c5   |
|  4 | d1   | d2   | d3   | d4   | d5   |
|  5 | e1   | e2   | e3   | e4   | e5   |
+----+------+------+------+------+------+
5 rows in set (0.00 sec)
mysql> show index from test03;

问题:我们创建了复合索引idx_test03_c1234 (c1、c2、c3、c4) ,根据以下SQL分析下索引使用情况?

①案例1

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';


mysql> explain select * from test03 where c1='a1';

mysql> explain select * from test03 where c1='a1' and c2='a2';

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

mysql> explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4';
#换一下条件顺序
mysql> explain select * from test03 where c4='a4' and c3='a3' and c2='a2' and c1='a1';

为什么我建的是1、2、3、4,但是我查询的是4、3、2、1,依旧可以使用到索引呢?MySQL中的第二层中的Optimizer会把MySQL中的命令自动的进行调整和优化。。

②案例2

限定范围

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


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

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

③案例3

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

c3作用在排序而不是查找

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

上面两个explain的相同。上述两条说明:和c4='a4'这个没啥关系 直接在三楼(c3)排序就行,不找了。

order by c3换成order by c4

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

用到Using filesort。中间的兄弟断了,但是MySQL要交出结果,只好整一次内排序 Using filesort

④ order by的字段顺序与索引字段的顺序

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

只用c1一个字段索引,但是c2、c3用于排序,无filesort。

将order by c2,c3换成order by c3,c2。

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

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

⑤order by中的特例

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

用c1、c2两个字段索引,但是c2、c3用于排序,无filesort 和c5='a5'没啥关系

上述第二条SQL的order by违反了创建索引的顺序,那么为什么没有产生using filesort呢?

原因是:

  • 一般情况下:只要order by字段顺序与创建索引的顺序不一致就会产生using filesort;

  • 特例是:排序的字段已经是一个常量了,上述情况就是 c3和c2='a2'两个字段排序。

对比说明:

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

跳过c2,就用c3,就出现Using filesort。

⑥group by

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

group by表面上是分组,但是分组之前必排序,所以group by优化原则几乎和order by相同

定值、范围还是排序,一般order by是给个范围

group by基本上都需要进行排序,会有临时表产生(建的索引搞不定,MySQL自己内部再次排序)

一般性建议

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

假设index(a, b, c)

  • Y,使用到a,b,c

    #百分号在右边,相当于说左边有常量了,实际上用到了c2索引mysql> explain select * from test03 where c1='a1' and c2 like 'kk%' and c3='a3';

  • Y,使用到a

    #根据百分号在左边 c2直接用不了,导致中间兄弟断了,所以c3也用不了mysql> explain select * from test03 where c1='a1' and c2 like '%kk' and c3='a3';

  • Y,使用到a

    #理由同上mysql> explain select * from test03 where c1='a1' and c2 like '%kk%' and c3='a3';

  • Y,使用到a,b,c

    mysql> explain select * from test03 where c1='a1' and c2 like 'k%kk%' and c3='a3';

三、查询截取分析

1-查询优化

①小表驱动大表

通常SQL调优过程:(面试问:平时是咋样进行SQL优化的?)

  1. 观察,至少跑1天,看看生产的慢SQL情况。
  2. 开启慢查询日志,设置阙值,比如超过5秒钟的就是慢SQL,并将它抓取出来。(找出慢SQL)
  3. explain + 慢SQL分析。
  4. show profile。
  5. 运维经理 or DBA,进行SQL数据库服务器的参数调优。

总结:

  1. 慢查询的开启并捕获
  2. explain + 慢SQL分析
  3. show profile查询SQL在Mysql服务器里面的执行细节和生命周期情况
  4. SQL数据库服务器的参数调优。

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

for(int i=0;i<5;i++){
    for(int j=0;j<1000;j++){
        ...
    }
}
====================
for(int i=0;i<1000;i++){
    for(int j=0;j<5;j++){
        ...
    }
}

类似上述两个for循环,尽量选择上面的那种。数据库最耗时的是多表之间关系的建立。第一种:相当于两张表建立了5次连接;第二种:相当于两张表建立了1000次连接。也就是说被嵌套在里面的应该是小表!(因为是里面嵌套的先执行,相当于被嵌套在里面的SQL是外层for循环)

RBO原理

当B表的数据集小于A表的数据集时,用in优于exists。

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表的数据集时,用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字段应建立索引。

EXISTS关键字
SELECT ...FROM table WHERE EXISTS (subquery)

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

提示

  1. EXSTS(subquey)只返回TRUE或FALSE,因此子查询中的SELECT * 也可以是 SELECT 1 或select ‘X’,官方说法是实际执行时会忽略SELECT清单,因此没有区别。
  2. EXISTS子查询的实际执行过程可能经过了优化而不是我们理解上的逐条对比,如果担忧效率问题,可进行实际检验以确定是否有效率问题。
  3. EXISTS子查询往往也可以用条件表达式,其他子查询或者JOIN来替代,何种最优需要具体问题具体分析

②in和exists

示例表:

mysql> select * from tbl_emp;
+----+------+--------+
| id | NAME | deptId |
+----+------+--------+
|  1 | z3   |      1 |
|  2 | z4   |      1 |
|  3 | z5   |      1 |
|  4 | w5   |      2 |
|  5 | w6   |      2 |
|  6 | s7   |      3 |
|  7 | s8   |      4 |
|  8 | s9   |     51 |
+----+------+--------+
8 rows in set (0.02 sec)

mysql> select * from tbl_dept;
+----+----------+--------+
| id | deptName | locAdd |
+----+----------+--------+
|  1 | RD       | 11     |
|  2 | HR       | 12     |
|  3 | MK       | 13     |
|  4 | MIS      | 14     |
|  5 | FD       | 15     |
+----+----------+--------+
5 rows in set (0.01 sec)

in和exists用法

mysql> select * from tbl_emp e where e.deptId in (select id from tbl_dept d);
+----+------+--------+
| id | NAME | deptId |
+----+------+--------+
|  1 | z3   |      1 |
|  2 | z4   |      1 |
|  3 | z5   |      1 |
|  4 | w5   |      2 |
|  5 | w6   |      2 |
|  6 | s7   |      3 |
|  7 | s8   |      4 |
+----+------+--------+
7 rows in set (0.00 sec)

mysql> select * from tbl_emp e where exists (select 1 from tbl_dept d where d.id = e.deptId);
+----+------+--------+
| id | NAME | deptId |
+----+------+--------+
|  1 | z3   |      1 |
|  2 | z4   |      1 |
|  3 | z5   |      1 |
|  4 | w5   |      2 |
|  5 | w6   |      2 |
|  6 | s7   |      3 |
|  7 | s8   |      4 |
+----+------+--------+
7 rows in set (0.00 sec)
#'X'就是表示一个常量,是一个常量就行了,反正返回的就是true/false
mysql> select * from tbl_emp e where exists (select 'X' from tbl_dept d where d.id = e.deptId);
+----+------+--------+
| id | NAME | deptId |
+----+------+--------+
|  1 | z3   |      1 |
|  2 | z4   |      1 |
|  3 | z5   |      1 |
|  4 | w5   |      2 |
|  5 | w6   |      2 |
|  6 | s7   |      3 |
|  7 | s8   |      4 |
+----+------+--------+
7 rows in set (0.00 sec)

③为排序使用索引OrderBy优化

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

新建SQL

create table tblA(
    #id int primary key not null auto_increment,
    age int,
    birth timestamp not null
);

insert into tblA(age, birth) values(22, now());
insert into tblA(age, birth) values(23, now());
insert into tblA(age, birth) values(24, now());

create index idx_A_ageBirth on tblA(age, birth);

查看验证:

mysql> select * from tblA;
+------+---------------------+
| age  | birth               |
+------+---------------------+
|   22 | 2021-04-04 19:31:45 |
|   23 | 2021-04-04 19:31:45 |
|   24 | 2021-04-04 19:31:45 |
+------+---------------------+
3 rows in set (0.00 sec)

mysql> show index from tblA;

分析SQL 重点在于会不会产生UsingFileSort

mysql> EXPLAIN SELECT * FROM tblA where age > 20 order by age;

mysql> EXPLAIN SELECT * FROM tblA where age>20 order by age,birth;

mysql> EXPLAIN SELECT * FROM tblA where age>20 order by birth;

mysql> EXPLAIN SELECT * FROM tblA where age>20 order by birth,age;

mysql> EXPLAIN SELECT * FROM tblA order by birth;

mysql> EXPLAIN SELECT * FROM tblA WHERE birth > '2016-01-28 00:00:00' order by birth;

mysql> EXPLAIN SELECT * FROM tblA WHERE birth > '2016-01-28 00:00:00' order by age;

mysql> EXPLAIN SELECT * FROM tblA order by age, birth desc;

最后一种情况:尽管order by的顺序和创建索引的顺序保持一致,但是由于order by默认的是升序,而DESC是降序,MySQL建立索引的顺序默认的就是升序,现在需要降序,用不上了,那么MySQL只有产生一次内部排序了。

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

_结论:_ORDER BY满足两情况,会使用Index方式排序:

  1. ORDER BY语句使用索引最左前列。
  2. 使用where子句与Order BY子句条件列组合满足索引最左前列。

④MySQL的Filesort

如果Order by查询的列不在索引列上,MySQL的Filesort有两种算法:

1)双路排序

MySQL4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和OrderBy列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读对应的数据输出。

从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。

取一批数据,要对磁盘进行了两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。

2)单路排序

从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序压的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。

结论及引申出的问题

  • 由于单路是后出的,总体而言好过双路

  • 但是用单路有问题

在sort_buffer(在my.cnf中有配置缓冲区大小)中,方法B比方法A要多占用很多空间,因为方法B是把所有字段都取出,所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取取

sort_buffer容量大小,再排……从而多次I/O。(类比于缓冲区大小不够,那只有取多次了

本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。

⑤优化策略

  • 增大sort_buffer_size参数的设置
  • 增大max_length_for_sort_data参数的设置

为什么设置sort_buffer_size、max_length_for_sort_data参数能优化排序?

提高Order By的速度:

  1. Order by时select * 是一个大忌只Query需要的字段,这点非常重要(很容易把缓冲区占满)。在这里的影响是;

    • 当Query的字段大小总和小于max_length_for_sort_data而且排序字段不是TEXT|BLOB类型时,会用改进后的算法——单路排序,否则用老算法——多路排序。

    • 两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建临时tmp文件进行合并排序,导致多次IO,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer__size。

  2. 尝试提高sort_buffer_size,不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的。

  3. 尝试提高max_length_for_sort_data,提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率。

⑥索引和Order by小结

为排序使用索引

  • MySql两种排序方式∶文件排序 或 扫描有序索引排序
  • MySql能为 排序 与 查询 使用相同的索引

创建复合索引 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能使用索引(不产生FileSort

  • WHERE a = const ORDER BY b,c
  • WHERE a = const AND b = const ORDER BY c
  • WHERE a = const ORDER BY b, 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不是素引的一部分
  • WHERE a in (…) ORDER BY b, c //对于排序来说,多个相等条件也是范围查询 不是常量

2-GroupBy优化

GroupBy优化

  • group by_实质是先排序后进行分组_,遵照索引建的最佳左前缀。
  • 当无法使用索引列,增大max_length_for_sort_data参数的设置 + 增大sort_buffer_size参数的设置。
  • where高于having,能写在where限定的条件就不要去having限定了。

3-慢查询日志

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

①如何操作

默认情况下,MySQL数据库没有开启慢查询日速,需要我们手动来设置这个参数

当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。

1)查看是否开启及如何开启
  • 默认 - SHOW VARIABLES LIKE '%slow_query_log%';

  • 一次性开启 - set global slow_query_log=1;,只对当前数据库生效,如果MySQL重启后则会失效。只针对本次生效。

    mysql> SHOW VARIABLES LIKE '%slow_query_log%';
    +---------------------+--------------------------+
    | Variable_name | Value |
    +---------------------+--------------------------+
    | slow_query_log | OFF |
    | slow_query_log_file | localhost-slow.log |
    +---------------------+--------------------------+
    2 rows in set, 1 warning (0.00 sec)

    mysql> set global slow_query_log=1;
    Query OK, 0 rows affected (0.01 sec)

    mysql> SHOW VARIABLES LIKE '%slow_query_log%';
    +---------------------+--------------------------+
    | Variable_name | Value |
    +---------------------+--------------------------+
    | slow_query_log | ON |
    | slow_query_log_file | localhost-slow.log |
    +---------------------+--------------------------+
    2 rows in set, 1 warning (0.00 sec)

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

修改my.cnf文件,[mysqld]下增加或修改参数slow_query_log和slow_query_log_file后,然后重启MySQL服务器。也即将如下两行配置进my.cnf文件

slow_query_log =1slow_query_log_file=/var/lib/mycoderxz-slow.log

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

2)开启了慢查询日志

什么样的SQL才会记录到慢查询日志里面呢?

这个是由参数long_query_time控制,默认情况下long_query_time的值为10秒,命令:SHOW VARIABLES LIKE 'long_query_time%';

mysql> SHOW VARIABLES LIKE 'long_query_time%';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row in set, 1 warning (0.00 sec)

可以使用命令修改,也可以在my.cnf参数里面修改。

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

3)设置慢SQL阈值时间:

set global long_query_time=3;

mysql> SHOW VARIABLES LIKE 'long_query_time%';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row in set, 1 warning (0.00 sec)

mysql> set global long_query_time=3;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW VARIABLES LIKE 'long_query_time%';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row in set, 1 warning (0.00 sec)

为什么设置后看不出变化?

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

4)记录慢SQL并后续分析

假设我们成功设置慢SQL阈值时间为3秒(set global long_query_time=3;)。

模拟超时SQL:

mysql> SELECT sleep(4);
+----------+
| sleep(4) |
+----------+
|        0 |
+----------+
1 row in set (4.00 sec)

日志记录:

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

mysql> show global status like '%Slow_queries%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Slow_queries  | 1     |
+---------------+-------+
1 row in set (0.00 sec)

在配置文件中设置慢SQL阈值时间 永久生效!

#[mysqld]下配置:
slow_query_log=1;
slow_query_log_file=/var/lib/mysql/atguigu-slow.log
long_query_time=3;
log_output=FILE;

②日志分析工具mysqldumpslow

在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具mysqldumpslow。

查看mysqldumpslow的帮助信息,mysqldumpslow --help。

  • s:是表示按照何种方式排序
  • c:访问次数
  • l:锁定时间
  • r:返回记录
  • t:查询时间
  • al:平均锁定时间
  • ar:平均返回记录数
  • at:平均查询时间
  • t:即为返回前面多少条的数据
  • g:后边搭配一个正则匹配模式,大小写不敏感的

工作常用参考

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

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

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

    • mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu-slow.log
  • 另外建议在使用这些命令时结合│和more 使用,否则有可能出现爆屏情况 (也可以结合管道把结果写到一个文件里面

    • mysqldumpslow -s r-t 10 /ar/lib/mysql/atguigu-slow.log | more

3-批量插入数据脚本(存储过程)

创建SQL

create database bigData;
use bigData;

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;

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;

②设置参数log_bin_trust_function_creators

创建函数,假如报错:This function has none of DETERMINISTIC…

由于开启过慢查询日志,因为我们开启了bin-log,我们就必须为我们的function指定一个参数。

show variables like 'log_bin_trust_function_creators';
+---------------------------------+-------+
| Variable_name                   | Value |
+---------------------------------+-------+
| log_bin_trust_function_creators | OFF   |
+---------------------------------+-------+
1 row in set (0.02 sec)
set global log_bin_trust_function_creators=1;

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

  • windows下my.ini[mysqld]加上log_bin_trust_function_creators=1

  • linux下/etc/my.cnf 下my.cnf[mysqld]加上log_bin_trust_function_creators=1

③创建函数,保证每条数据都不同

1)随机产生字符串

delimiter $$ # 两个 $$ 表示结束
create function rand_string(n int) returns varchar(255)
begin
    declare chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyz';
    declare return_str varchar(255) default ''; #定义返回值变量
    declare i int default 0;
    while i < n do
        #concat函数连接字符串
        set return_str = concat(return_str,substring(chars_str,floor(1+rand()*52),1));
        set i=i+1;
    end while;
    return return_str;
end $$

2)随机产生部门编号

delimiter $$
create function rand_num() returns int(5)
begin
    declare i int default 0;
    set i=floor(100+rand()*10);
    return i;
end $$

运行结果

mysql> delimiter $$ # 声明两个 $$ 表示结束
mysql> create function rand_string(n int) returns varchar(255)
    -> begin
    ->     declare chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyz';
    ->     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));
    ->         set i=i+1;
    ->     end while;
    ->     return return_str;
    -> end $$
Query OK, 0 rows affected (0.00 sec)

mysql> select rand_string(2);
    -> ;
    -> $$
+----------------+
| rand_string(2) |
+----------------+
| af             |
+----------------+
1 row in set (0.00 sec)

mysql> delimiter $$
mysql> create function rand_num() returns int(5)
    -> begin
    ->     declare i int default 0;
    ->     set i=floor(100+rand()*10);
    ->     return i;
    -> end $$
Query OK, 0 rows affected (0.00 sec)

mysql> select rand_num()$$
+------------+
| rand_num() |
+------------+
|        105 |
+------------+
1 row in set (0.00 sec)

④创建存储过程

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

注意:设置autocommit设置为0;

delimiter $$
create procedure insert_emp(in start int(10),in max_num int(10))#没有返回值
begin
    declare i int default 0;
    set autocommit = 0;#把自动提交设置为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(),2000,400,rand_num());
        until i=max_num
        end repeat;
    commit;#注意要commit
end $$

创建往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 $$

⑤调用存储过程

往dept表中插入数据

mysql> DELIMITER ;#恢复为;作为结束符号,上面改为了$$
mysql> CALL insert_dept(100, 10);
Query OK, 0 rows affected (0.01 sec)

往emp表中插入50万数据

mysql> DELIMITER ;
mysql> CALL insert_emp(100001, 500000);
Query OK, 0 rows affected (27.00 sec)

运行结果

mysql> select * from dept;
+----+--------+---------+--------+
| id | deptno | dname   | loc    |
+----+--------+---------+--------+
|  1 |    101 | mqgfy   | ck     |
|  2 |    102 | wgighsr | kbq    |
|  3 |    103 | gjgdyj  | brb    |
|  4 |    104 | gzfug   | p      |
|  5 |    105 | keitu   | cib    |
|  6 |    106 | nndvuv  | csue   |
|  7 |    107 | cdudl   | tw     |
|  8 |    108 | aafyea  | aqq    |
|  9 |    109 | zuqezjx | dpqoyo |
| 10 |    110 | pam     | cses   |
+----+--------+---------+--------+
10 rows in set (0.00 sec)

mysql> select * from emp limit 20;
+----+--------+-------+----------+-----+------------+---------+--------+--------+
| id | empno  | ename | job      | mgr | hiredate   | sal     | comm   | deptno |
+----+--------+-------+----------+-----+------------+---------+--------+--------+
|  1 | 100002 | xmbva | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    108 |
|  2 | 100003 | aeq   | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    109 |
|  3 | 100004 | cnjfz | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    105 |
|  4 | 100005 | wwhd  | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    100 |
|  5 | 100006 | e     | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    107 |
|  6 | 100007 | yjfr  | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    108 |
|  7 | 100008 | xlp   | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    102 |
|  8 | 100009 | mp    | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    102 |
|  9 | 100010 | tcdl  | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    107 |
| 10 | 100011 | akw   | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    106 |
| 11 | 100012 | re    | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    108 |
| 12 | 100013 | qip   | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    105 |
| 13 | 100014 | bvaf  | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    108 |
| 14 | 100015 | g     | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    106 |
| 15 | 100016 | qt    | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    102 |
| 16 | 100017 | bzy   | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    109 |
| 17 | 100018 | gf    | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    102 |
| 18 | 100019 | r     | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    108 |
| 19 | 100020 | ydokg | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    100 |
| 20 | 100021 | ee    | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    107 |
+----+--------+-------+----------+-----+------------+---------+--------+--------+
20 rows in set (0.00 sec)

4-用Show Profile进行SQL分析

Show Profile是MySQL提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量。(比Explain更加详细)

官方文档

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

  • 慢查询日志抓取有查询较慢的SQL;
  • Explain
  • Show Profile
  • 服务器数据和脚本重要属性的调优(比如my.cnf的配置文件)

分析步骤

1.是否支持,看看当前的mysql版本是否支持。

mysql> show variables like 'profiling';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| profiling     | OFF   |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)

默认是关闭,使用前需要开启。

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

mysql> set profiling=on;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show variables like 'profiling';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| profiling     | ON    |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)

3.运行SQL

mysql> select * from emp group by id%10 limit 150000;
+----+--------+-------+----------+-----+------------+---------+--------+--------+
| id | empno  | ename | job      | mgr | hiredate   | sal     | comm   | deptno |
+----+--------+-------+----------+-----+------------+---------+--------+--------+
| 10 | 100011 | akw   | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    106 |
|  1 | 100002 | xmbva | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    108 |
|  2 | 100003 | aeq   | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    109 |
|  3 | 100004 | cnjfz | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    105 |
|  4 | 100005 | wwhd  | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    100 |
|  5 | 100006 | e     | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    107 |
|  6 | 100007 | yjfr  | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    108 |
|  7 | 100008 | xlp   | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    102 |
|  8 | 100009 | mp    | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    102 |
|  9 | 100010 | tcdl  | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    107 |
+----+--------+-------+----------+-----+------------+---------+--------+--------+
10 rows in set (0.55 sec)

mysql> select * from emp group by id%20 order by 5;
+----+--------+-------+----------+-----+------------+---------+--------+--------+
| id | empno  | ename | job      | mgr | hiredate   | sal     | comm   | deptno |
+----+--------+-------+----------+-----+------------+---------+--------+--------+
|  1 | 100002 | xmbva | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    108 |
|  3 | 100004 | cnjfz | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    105 |
|  5 | 100006 | e     | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    107 |
|  7 | 100008 | xlp   | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    102 |
|  9 | 100010 | tcdl  | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    107 |
| 11 | 100012 | re    | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    108 |
| 13 | 100014 | bvaf  | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    108 |
| 15 | 100016 | qt    | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    102 |
| 17 | 100018 | gf    | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    102 |
| 19 | 100020 | ydokg | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    100 |
|  2 | 100003 | aeq   | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    109 |
|  4 | 100005 | wwhd  | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    100 |
|  6 | 100007 | yjfr  | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    108 |
|  8 | 100009 | mp    | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    102 |
| 10 | 100011 | akw   | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    106 |
| 12 | 100013 | qip   | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    105 |
| 14 | 100015 | g     | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    106 |
| 16 | 100017 | bzy   | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    109 |
| 18 | 100019 | r     | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    108 |
| 20 | 100021 | ee    | salesman |   1 | 2021-04-05 | 2000.00 | 400.00 |    107 |
+----+--------+-------+----------+-----+------------+---------+--------+--------+
20 rows in set (0.57 sec)

4.查看结果,show profiles;

mysql> show profiles;
+----------+------------+-----------------------------------------------+
| Query_ID | Duration   | Query                                         |
+----------+------------+-----------------------------------------------+
|        1 | 0.00204000 | show variables like 'profiling'               |
|        2 | 0.55134250 | select * from emp group by id%10 limit 150000 |
|        3 | 0.56902000 | select * from emp group by id%20 order by 5   |
+----------+------------+-----------------------------------------------+
3 rows in set, 1 warning (0.00 sec)
  1. 化验单: 诊断SQL,show profile cpu,block io for query 上一步show profiles中的问题SQL数字号码;

    mysql> show profile cpu,block io for query 3;
    +--------------------------------+----------+----------+------------+--------------+---------------+
    | Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
    +--------------------------------+----------+----------+------------+--------------+---------------+
    | starting | 0.000008 | 0.000007 | 0.000000 | 0 | 0 |
    | Waiting for query cache lock | 0.000002 | 0.000002 | 0.000000 | 0 | 0 |
    | checking query cache for query | 0.000021 | 0.000021 | 0.000000 | 0 | 0 |
    | checking permissions | 0.000004 | 0.000003 | 0.000000 | 0 | 0 |
    | Opening tables | 0.000009 | 0.000009 | 0.000000 | 0 | 0 |
    | System lock | 0.000006 | 0.000006 | 0.000000 | 0 | 0 |
    | Waiting for query cache lock | 0.000009 | 0.000009 | 0.000000 | 0 | 0 |
    | init | 0.000019 | 0.000019 | 0.000000 | 0 | 0 |
    | optimizing | 0.000003 | 0.000002 | 0.000000 | 0 | 0 |
    | statistics | 0.000007 | 0.000008 | 0.000000 | 0 | 0 |
    | preparing | 0.000004 | 0.000004 | 0.000000 | 0 | 0 |
    | Creating tmp table | 0.000020 | 0.000020 | 0.000000 | 0 | 0 |
    | executing | 0.000002 | 0.000002 | 0.000000 | 0 | 0 |
    | Copying to tmp table | 0.329865 | 0.330004 | 0.000000 | 0 | 0 |
    | Sorting result | 0.000015 | 0.000011 | 0.000000 | 0 | 0 |
    | Sending data | 0.000023 | 0.000023 | 0.000000 | 0 | 0 |
    | end | 0.000003 | 0.000003 | 0.000000 | 0 | 0 |
    | removing tmp table | 0.000638 | 0.000638 | 0.000000 | 0 | 0 |
    | end | 0.000005 | 0.000004 | 0.000000 | 0 | 0 |
    | query end | 0.000016 | 0.000016 | 0.000000 | 0 | 0 |
    | closing tables | 0.000005 | 0.000005 | 0.000000 | 0 | 0 |
    | freeing items | 0.000006 | 0.000005 | 0.000000 | 0 | 0 |
    | Waiting for query cache lock | 0.000002 | 0.000002 | 0.000000 | 0 | 0 |
    | freeing items | 0.000056 | 0.000056 | 0.000000 | 0 | 0 |
    | Waiting for query cache lock | 0.000003 | 0.000002 | 0.000000 | 0 | 0 |
    | freeing items | 0.000001 | 0.000001 | 0.000000 | 0 | 0 |
    | storing result in query cache | 0.000003 | 0.000003 | 0.000000 | 0 | 0 |
    | logging slow query | 0.000001 | 0.000001 | 0.000000 | 0 | 0 |
    | cleaning up | 0.000002 | 0.000002 | 0.000000 | 0 | 0 |
    +--------------------------------+----------+----------+------------+--------------+---------------+
    29 rows in set (0.02 sec)

参数备注

  • ALL:显示所有的开销信息。
  • BLOCK IO:显示块lO相关开销。
  • CONTEXT SWITCHES :上下文切换相关开销。
  • CPU:显示CPU相关开销信息。
  • IPC:显示发送和接收相关开销信息。
  • MEMORY:显示内存相关开销信息。
  • PAGE FAULTS:显示页面错误相关开销信息。
  • SOURCE:显示和Source_function,Source_file,Source_line相关的开销信息。
  • SWAPS:显示交换次数相关开销的信息。

6.日常开发需要注意的结论 (有下面四种情况就很糟糕了)

  • converting HEAP to MyISAM 查询结果太大,内存都不够用了往磁盘上搬了。
  • Creating tmp table 创建临时表,拷贝数据到临时表,用完再删除
  • Copying to tmp table on disk 把内存中临时表复制到磁盘,危险!
  • locked

5-全局查询日志

永远不要在生产环境开启这个功能。

配置文件启用

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

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

编码启用:

  • set global general_log=1;

  • set global log_output='TABLE';

    mysql> set global general_log=1;
    Query OK, 0 rows affected (0.01 sec)

    mysql> set global log_output='TABLE';
    Query OK, 0 rows affected (0.00 sec)

此后,你所编写的sql语句,将会记录到mysql库里的geneial_log表,可以用下面的命令查看:

mysql> select * from mysql.general_log;

四、MySQL锁机制

1-数据库锁理论概述

锁是计算机协调多个进程或线程并发访问某一资源的机制。

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

类比:网上购物

打个比方,我们到淘宝上买一件商品,商品只有一件库存,这个时候如果还有另一个人买,那么如何解决是你买到还是另一个人买到的问题?

这里肯定要用到事务,我们先从库存表中取出物品数量,然后插入订单,付款后插入付款表信息,然后更新商品数量。在这个过程中,使用锁可以对有限的资源进行保护,解决隔离和并发的矛盾。

锁的分类

从对数据操作的类型(读\写)分

  • 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。
  • 写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。

从对数据操作的粒度分

  • 表锁
  • 行锁

2-读锁案例讲解

表锁(偏读) 下面使用的是MyISAM引擎。

特点:偏向MyISAM存储引擎,开销小,加锁快;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

建表SQL

create table mylock (
    id int not null primary key auto_increment,
    name varchar(20) default ''
) engine myisam; #注意这里使用了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;

运行结果

mysql> select * from mylock;
+----+------+
| id | name |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
|  5 | e    |
+----+------+
5 rows in set (0.00 sec)

手动增加表锁

lock table 表名字 read(write), 表名字2 read(write), 其他;

  • read(write)表示:对这张表是加读锁还是写锁;

    mysql> lock table mylock read;Query OK, 0 rows affected (0.00 sec)

查看表上加过的锁

mysql> show open tables;
+--------------------+------------------------------------------------------+--------+-------------+
| Database           | Table                                                | In_use | Name_locked |
+--------------------+------------------------------------------------------+--------+-------------+
| performance_schema | events_waits_summary_by_user_by_event_name           |      0 |           0 |
| performance_schema | events_waits_summary_global_by_event_name            |      0 |           0 |
| performance_schema | events_transactions_summary_global_by_event_name     |      0 |           0 |
| performance_schema | replication_connection_status                        |      0 |           0 |
| mysql              | time_zone_leap_second                                |      0 |           0 |
| mysql              | columns_priv                                         |      0 |           0 |
| my                 | test03                                               |      0 |           0 |
| bigdata            | mylock                                               |      1 |           0 |
...

In_use为1表示这个库的mylock表被加了一把锁。

释放锁

mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)

加读锁——为mylock表加read锁(读阻塞写例子)

多个session可模拟分布式的情况:当session1对A表进行加锁,其他的session只能对该表进行读,更新和插入都会被阻塞。

3-读锁案例讲解2

为mylock表加write锁(MylSAM存储引擎的写阻塞读例子)

session1加了写锁之后(自己爱怎么玩就怎么玩),其他的session不能读了!

案例结论

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。

MySQL的表级锁有两种模式:

  • 表共享读锁(Table Read Lock)

  • 表独占写锁(Table Write Lock)

锁类型

可否兼容

读锁

写锁

读锁

写锁

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

  1. 对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。

  2. 对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作。

简而言之,就是读锁会阻塞写,但是不会堵塞读。而写锁则会把读和写都堵塞。

4-表锁分析

如果有一种命令,可以让我们知道,锁了哪些表,锁了多长时间

  • 看看哪些表被加锁了

    mysql> show open tables;

  • 如何分析表锁定

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

mysql>  show status like 'table_locks%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Table_locks_immediate | 170   |
| Table_locks_waited    | 0     |  #越高锁门锁竞争的越激烈
+-----------------------+-------+
2 rows in set (0.00 sec)

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

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

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

5-行锁理论

偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁

由于行锁支持事务,复习老知识

  • 事务(Transaction)及其ACID属性
  • 并发事务处理带来的问题
  • 事务隔离级别

事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性:

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

并发事务处理带来的问题

  • 更新丢失(Lost Update)
  • 脏读(Dirty Reads)
  • 不可重复读(Non-Repeatable Reads)
  • 幻读(Phantom Reads)

更新丢失(Lost Update)

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题――最后的更新覆盖了由其他事务所做的更新。

例如,两个程序员修改同一java文件。每程序员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖前一个程序员所做的更改。

如果在一个程序员完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题。

脏读(Dirty Reads)

一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做”脏读”。

一句话:事务A读取到了事务B已修改但尚未提交的的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。(也许我改错了,而你拿到了错误的数据去干活)

不可重复读(Non-Repeatable Reads)

一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。

一句话:事务A读取到了事务B已经提交的修改数据,不符合隔离性。

幻读(Phantom Reads)

一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读“。

一句话:事务A读取到了事务B体提交的新增数据,不符合隔离性。

多说一句:幻读和脏读有点类似,

脏读是事务B里面修改了数据,

幻读是事务B里面新增了数据。

事务隔离级别

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

读数据—致性及允许的并发副作用(隔离级别)

读数据一致性

脏读

不可重复读

幻读

未提交读(Read Uncommitted)

最低级别,只能保证不读取物理上损坏的数据

已提交读(Read committed)

语句级

可重复读(Repeatable read)

事务级

可序列化(serializable)

最高级别,事务级

数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。

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

mysql> show variables like 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set, 1 warning (0.00 sec)

MySQL默认的是事务级,有可能出现幻读。

6-行锁案例讲解

新建SQL

#使用INNODB引擎
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);

运行结果

mysql> select * from test_innodb_lock;
+------+------+
| a    | b    |
+------+------+
|    1 | b2   |
|    3 | 3    |
|    4 | 4000 |
|    5 | 5000 |
|    6 | 6000 |
|    7 | 7000 |
|    8 | 8000 |
|    9 | 9000 |
|    1 | b1   |
+------+------+
9 rows in set (0.00 sec)

mysql> show index from test_innodb_lock;

行锁定基本演示(两个客户端更新同一行记录)

读自己之所写,因为session1中的数据还没有提交,所以在session2中是不能够读到最新的数据的;可类比:发朋友圈,第一时间自己肯定是可以看到的,但是其他的用户就不能保证在第一时间更新到了,应该是延迟几秒(分布式系统),无法满足强一致性;

当两个session修改同一行数据,后来的会被阻塞;当修改的不是同一行数据时,互不干扰(大路朝天各走一边)。

7-行锁演示答疑补充

session2还需要重新commit才可刷新最新的数据:原因是因为session2的commit也设置为0了,如果MySQL默认的commit=1的时候,读的时候是能够拿到最新的数据的。

8-索引失效导致行锁变表锁

无索引行锁升级为表锁

9-间隙锁危害

什么是间隙锁

当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁,对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”。

InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)宁可错杀,不可放过。上述案例中没有a=2的记录,但是MySQL依旧给这一条记录加锁了。。

【危害】

因为Query执行过程中通过过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。

间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。

10-面试题:如何锁定一行

面试:如何锁定一行?begin…commit

我希望在操作这条数据的时候,此数据不能被其他线程修改。

11-行锁总结与页锁

Innodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,Innodb的整体性能和MylISAM相比就会有比较明显的优势了。

但是,Innodb的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让Innodb的整体性能表现不仅不能比MyISAM高,甚至可能会更差。(使用不当,行锁变表锁)

①行锁分析

如何分析行锁定

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

mysql>  show status like 'innodb_row_lock%';
+-------------------------------+--------+
| Variable_name                 | Value  |
+-------------------------------+--------+
| Innodb_row_lock_current_waits | 0      |
| Innodb_row_lock_time          | 128380 |
| Innodb_row_lock_time_avg      | 32095  |
| Innodb_row_lock_time_max      | 50618  |
| Innodb_row_lock_waits         | 4      |
+-------------------------------+--------+
5 rows in set (0.01 sec)

对各个状态量的说明如下:

  • 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(等待平均时长)
  • lnnodb_row_lock_waits(等待总次数)
  • lnnodb_row_lock_time(等待总时长)这三项。

尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划(可以使用showProfile)。

②优化建议

  • 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁。
  • 合理设计索引,尽量缩小锁的范围
  • 尽可能较少检索条件,避免间隙锁
  • 尽量控制事务大小,减少锁定资源量和时间长度
  • 尽可能低级别事务隔离
  • varchar类型一定要加单引号!

页锁

开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。(了解一下即可)

五、主从复制

复制的基本原理

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

三步骤+原理图

MySQL复制过程分成三步

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

复制的基本原则

  • 每个slave只有一个master
  • 每个slave只能有一个唯一的服务器ID
  • 每个master可以有多个salve

复制的最大问题是延迟

一主一从常见配置

  • mysql版本一致且后台以服务运行

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

主机修改my.ini配置文件

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

  2. [必须]启用二进制日志

  3. [可选]启用错误日志

  4. [可选]根目录

  5. [可选]临时目录

  6. [可选]数据目录

  7. 主机,读写都可以

  8. [可选]设置不要复制的数据库

  9. [可选]设置需要复制的数据库

从机修改my.cnf配置文件

  • [必须]从服务器唯一ID
  • [可选]启用二进制日志

配置文件,请主机+从机都重启后台mysql服务

  • service mysql stop
  • service mysql start

主机从机都关闭防火墙

  • windows手动关闭

  • 关闭虚拟机linux防火墙

    查看防火墙状态

    firewall-cmd --state

    停止firewall

    systemctl stop firewalld.service

    禁止firewall开机启动

    systemctl disable firewalld.service

在Windows主机上建立帐户并授权slave

  • GRANT REPLICATION SLAVE ON *.* TO ‘zhangsan’@‘从机器数据库IP’ IDENTIFIED BY ‘123456’;
  • flush privileges;//刷新
  • 查询master的状态
    • show master status;
    • 记录下File和Position的值

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

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

  • CHANGE MASTER TO MASTER_HOST=’主机 IP’, MASTER_USER=‘zhangsan’, MASTER_PASSWORD=’123456’, MASTER_LOG_FILE='File名字’, MASTER_LOG_POS=Position数字;

  • 启动从服务器复制功能

    • start slave;
  • show slave status\G

    • 下面两个参数都是Yes,则说明主从配置成功!
    • Slave_IO_Running:Yes
    • Slave_SQL_Running:Yes

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

如何停止从服务复制功能

  • stop slave;