MySQL高级
MySQL高级
一,myslq 的架构介绍
1.1 mysql 简介
1.1.1 概述:
- MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司。
- Mysql 是开源的,可以定制的,采用了 GPL 协议,你可以修改源码来开发自己的 Mysql 系统。
- MySQL 使用标准的 SQL 数据语言形式。
- Mysql 可以允许于多个系统上,并且支持多种语言。这些编程语言包括 C、C++、Python、Java、Perl、PHP、 Eiffel、Ruby 和 Tcl 等。
- MySQL 支持大型数据库,支持 5000 万条记录的数据仓库,32 位系统表文件最大可支持 4GB,64 位系统支持最大的表文件为 8TB。
1.1.2 高级 mysql
- 完整的 myslq优化需要很深的功底,大公司甚至有专门的 DBA写上述
- myslq 内核
- sql 优化攻城狮
- mysql 服务器的优化
- 各种参数化常量设定
- 查询语句优化
- 主从复制
- 软硬件升级
- 容灾备份
- sql 编程
1.2 mysql Linux 版的安装
1.2.1 myslq 5.7 & centos7.6
1.2.1.1 下载地址
- https://dev.mysql.com/downloads/mysql/
- 点击 archives 找相应地版本
1.2.1.2 检查当前系统是否安装过 myslq
- rpm -qa|grep -i mysql
1.2.1.3 安装 myslq服务端(注意提示)
-
ps:centos7.6 自带的 mysql数据库是 mariadb,会跟 myslq冲突,要删除
-
先查看 mariadb相关的安装包
rpm -qa|grep mari
-
删除:rpm -e --nodeps 【mariadb-libs】
-
-
rpm -ivh MySQL-service-【table 键提示】
1.2.1.4 安装 myslq 客户端
-
解压到 /opt 下:tar -xvf mysql-5.7.26-1.el7.x86_64.rpm-bundle.tar
-
真正安装 mysql,一次运行一下几条
rpm -ivh mysql-community-common-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-server-5.7.26-1.el7.x86_64.rpm
-
查看 myslq安装是否成功
ps -ef | grep mysql
1.2.1.5 查看 myslq 安装时创建的 mysql用户和 mysql组
- cat /etc/passwd | grep mysql
- cat /etc/group | grep mysql
- 或者可以执行 mysqladmin --version 命令,类似 java -version如果打出消息,即为成功。
1.2.1.6 mysql服务的 启+ 停
- 开启: systemctl start mysqld.service
- 停止: systemctl stop mysqld.service
- 查看状态: systemctl status mysqld.service
1.2.1.7 mysql 服务启动后,给 root用户设置密码
-
mysql 自动给 root用户设置随机密码,运行
grep "password" /var/log/mysqld.log 看看到当前密码
-
运行 mysql -u root -p 用 root用户登陆,提示输入密码可用上述的,可以成功登陆进入 msyql命令行
-
设置密码: set password for 'root'@'localhost' =password('你的密码');
-
运行:flush privileges; 使密码生效。
1.2.1.8 自启动 mysql 服务
-
设置开机自启动 mysql:systemctl enable mysqld.service
-
查看 mysql是否开机自启:systemctl list-unit-files | grep mysql
左边是服务名称,右边是状态,enabled是开机启动,disabled是开机不启动
-
或者使用:ntsysv
找到 mysql的服务,找到之后 按下空格开启服务
退出,按 table选择取消,就退出该图形界面。
上下键:可以在中间的方框当中,在各个服务之间移动;
空格键:可以用来选择你所需要的服务,[*]表示开起启动;
tab键:可以在方框、OK、Cancel之间移动;
[F1]键:可以显示该服务的说明
1.2.1.9 修改配置文件位置
- 在 mysql 5.7中该 my.cnf在 /etc/my.cnf
- 如果不存在去 /usr/share/mysql 看一下(my-huge.cnf),拷贝一个
1.2.1.10 修改字符集和数据存储路径
-
查看字符集 :
show variables like 'character%';
show variables like '%char%';
默认的是客户端和服务器都用了 latin1,所以会乱码
-
修改
vi /etc/my.cnf
[mysqld] character_set_server=utf8 collation-server=utf8_general_ci
-
这时候登陆 myslq查看字符集,如果还有是 latin1的,这时候在 myslq中设置
set 【Variable_name】=utf8;
在进行查看就变成 utf8的编码了。
-
-
重启 mysql
systemctl restart mysqld;
-
重新连接后重新 create batabase 并使用新建库,然后在重新建表试试
1.2.1.11 mysql 的安装位置
-
在 linux下查看安装目录:ps -ef | grep mysql
路径 解释 备注 /var/lib/mysql mysql 数据库文件的存放路径 /var/lib/mysql/xxx.xx.pid /usr/share/mysql 配置文件目录 mysql.server 命令及配置文件 /usr/bin 相关命令目录 mysqladmin mysqldump 等命令 /etc/init.d/mysql 启停相关脚本 # -
在 linux下登陆 mysql 输入:show variables like '%dir%';
1.3 mysql 配置文件
-
二进制日志 log-bin
主从复制
-
错误日志:log-error
默认是关闭的,记录严重的警告和错误信息,每次启动和关闭的详细信息等。
-
查询日志 log
默认关闭,记录查询的sql语句,如果开启会减低 mysql的整体性能,因为记录日志也是需要消耗系统资源的
-
数据文件
-
两系统:
windows:D:\devSoft\MySQLServer5.5\data 目录下可以挑选很多库
Linux:看当前系统中的全部库后再进去(命令:ls -1F | grep ^d),默认路径:/var/lib/mysql
-
frm 文件:存放表结构
-
myd 文件:存放表数据
-
myi 文件:存放表索引
-
-
如何配置:
- windows:my.ini 文件
- Linux:/etc/my.cnf 文件
1.4 mysql 逻辑架构介绍
1.4.1 总体概览
-
和其它数据库相比,MySQL 有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
1.4.1.1 连接层
- 最上层是一些客户端和连接服务,包含本地 sock 通信和大多数基于客户端/服务端工具实现的类似于 tcp/ip 的
通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证
安全接入的客户端提供线程。同样在该层上可以实现基于 SSL 的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
1.4.1.2 服务层
-
第二层架构主要完成大多少的核心服务功能,如 slq接口,并完成缓存的查询,slq的分析和优化及部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如果过程,函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定查询表的顺序,是否利用索引等,最后生成相应的执行操作。如果是 select语句,服务器还会查询内部的缓存。 如果缓存空间组后大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
Management Serveices & Utilities 系统管理和控制工具 SQL Interface: SQL 接口。接受用户的 SQL 命令,并且返回用户需要查询的结果。比如 select from就是调用 SQL Interface Parser 解析器。 SQL 命令传递到解析器的时候会被解析器验证和解析 Optimizer 查询优化器。 SQL 语句在查询之前会使用查询优化器对查询进行优化,比如有where 条件时,优化器来决定先投影还是先过滤。 Cache 和 Buffer 查询缓存。如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取
数据。这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key 缓存,权限缓存等
1.4.1.3 引擎层
- 存储引擎层,存储引擎真正的负责了 MySQL 中数据的存储和提取,服务器通过 API 与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。
1.4.1.4 存储层
- 数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
1.4.2 查询说明
1.5 mysql 存储引擎
1.5.1 查看命令
-
如何用命令查看
#看你的 mysql现在提供什么存储引擎 mysql》show engines; #看你的 mysql当前默认的存储引擎 mysql》show variables like '%storage_engine%';
1.5.2 MyISAM 和 InnoDB
对比项 | MyISAM | InnoDB |
---|---|---|
主外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 表锁,即使操作一条记录也会所著整个表,不适合高并发的操作 | 行锁,操作时只锁某一行,部队其他行有影响,适合高并发的操作 |
缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 |
表空间 | 小 | 大 |
关注点 | 性能 | 事务 |
默认安装 | Y | Y |
1.5.3 阿里巴巴,淘宝用哪个
产品 | 价格 | 目标 | 主要功能 | 是否可投入生产 |
---|---|---|---|---|
Percona Server | 免费 | 提供 XtraDB存储引擎的包装器和其他分析工具 | XtraDB | 是 |
MariaDB | 免费 | 扩展 MySQl以包含 XtraDB和其他性能改进 | XtraDB | 是 |
Drizzle | 免费 | 提供比 MySQl更强大的可扩展性和性能改进 | 高可用性 | 是 |
- Percona 为 mysql数据库服务器进行了改进,在功能和性能上较 mysql有着很显著的提升。该版本提升了在 高负载情况下的 InnoDB性能,为 DBA提供一些非常有用的性能诊断工具:另外有更多的参数和命令来控制服务器行为。
- 该公司新键了一款存储引擎叫 xtradb完全可以提到 innodb,并且在性能和并发上做得很好
- 阿里巴巴大部分 mysql数据库其实使用的 percona的原型加以修改。
- AliSql + AliRedis
二,索引优化分析
2.1 性能下降 slq慢 执行事件长等待事件长
-
查询语句写的烂
-
索引失效
-
单值
id name email weixinNumber select * from user where name = 'Xxx'; create index '索引别名' on user(name); -- 在name上建索引
-
复合
id name email weixinNumber select * from user where name = 'Xxx' and email = 'Xxx'; create index '索引别名' on user(name,email); -- 在name上建索引
-
-
关联查询太多 join(设计缺陷或不得己的需求)
-
服务器调优及各个参数设置(缓冲线程数等)
2.2 常见通用的 join查询
2.2.1 sql 执行顺序
-
手写
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 <left table=""> ON <join condi="" tion=""> <join_ type=""> JOIN <right_ table=""> WHERE <where_ condition=""> GROUP BY <group_ by_="" _list=""> HAVING <having_ condition=""> SELECT DISTINCT <select list=""> ORDER BY LIMIT
-
总结
2.2.2 join 图
2..2.3 建表 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,
`age` INT(3) DEFAULT NULL,
`deptId` INT(11) DEFAULT NULL,
empno int not null,
PRIMARY KEY (`id`),
KEY `idx_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('华山','华山');
INSERT INTO t_dept(deptName,address) VALUES('丐帮','洛阳');
INSERT INTO t_dept(deptName,address) VALUES('峨眉','峨眉山');
INSERT INTO t_dept(deptName,address) VALUES('武当','武当山');
INSERT INTO t_dept(deptName,address) VALUES('明教','光明顶');
INSERT INTO t_dept(deptName,address) VALUES('少林','少林寺');
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('风清扬',90,1,100001);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('岳不群',50,1,100002);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('令狐冲',24,1,100003);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('洪七公',70,2,100004);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('乔峰',35,2,100005);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('灭绝师太',70,3,100006);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('周芷若',20,3,100007);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张三丰',100,4,100008);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('张无忌',25,5,100009);
INSERT INTO t_emp(NAME,age,deptId,empno) VALUES('韦小宝',18,null,100010);
2.2.4 7种 join
#1.所有有门派人员的信息(要求显示门派名称)
SELECT e.`name`,d.`deptName` FROM t_emp e INNER JOIN t_dept d ON e.`deptId`=d.`id`;
#2.列出所有人员及其门派信息
SELECT e.`name`,d.`deptName` FROM t_emp e LEFT JOIN t_dept d ON e.`deptId`=d.`id`;
#3.列出所有门派
SELECT * FROM t_dept;
#4. 所有无门派人士
SELECT * FROM t_emp WHERE deptId IS NULL;
#5. 所有无人门派
SELECT d.* FROM t_dept d LEFT JOIN t_emp e ON d.`id`=e.`deptId` WHERE e.`deptId` IS NULL;
#6. 所有人员和门派的对应关系
SELECT * FROM t_emp e LEFT JOIN t_dept d ON e.`deptId`=d.`id`
UNION
SELECT * FROM t_emp e RIGHT JOIN t_dept d ON e.`deptId`=d.`id`;
#7. 所有没有入门派的人员和没人入的门派
SELECT * FROM t_emp e LEFT JOIN t_dept d ON e.`deptId`=d.`id` WHERE e.deptId IS NULL
UNION
SELECT * FROM t_dept d LEFT JOIN t_emp e ON d.`id`=e.`deptId` WHERE e.`deptId` IS NULL;
#8. 添加 CEO 字段 ALTER TABLE `t_dept` add CEO INT(11) ;
update t_dept set CEO=2 where id=1;
update t_dept set CEO=4 where id=2;
update t_dept set CEO=6 where id=3;
update t_dept set CEO=8 where id=4;
update t_dept set CEO=9 where id=5;
#8.1 求各个门派对应的掌门人名称
SELECT d.deptName,e.name FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id
#8.2 求所有当上掌门人的平均年龄
SELECT AVG(e.age) FROM t_dept d LEFT JOIN t_emp e ON d.ceo=e.id
#8.3 求所有人物对应的掌门名称
SELECT ed.name '人物',c.name '掌门' FROM
(SELECT e.name,d.ceo
from t_emp e LEFT
JOIN t_dept d on e.deptid=d.id) ed
LEFT JOIN t_emp c
on ed.ceo= c.id;
SELECT e.name '人物',tmp.name '掌门'
FROM t_emp e
LEFT JOIN
(SELECT d.id did,e.name
FROM t_dept d
LEFT JOIN t_emp e
ON d.ceo=e.id) tmp
ON e.deptId=tmp.did;
SELECT e1.name '人物',e2.name '掌门'
FROM t_emp e1
LEFT JOIN t_dept d
on e1.deptid = d.id
LEFT JOIN t_emp e2
on d.ceo = e2.id ;
SELECT e2.name '人物'
,(SELECT e1.name
FROM t_emp e1
where e1.id= d.ceo) '掌门'
from t_emp e2
LEFT JOIN t_dept d on
e2.deptid=d.id;
2.3 索引简介
2.3.1 是什么
-
MySQL 官方对索引的 定义为:索引(Index)是帮助 MySQL 高效获取数据的数据结构。可以得到索引的本质:索引是数据结构。可以简单理解为排好序的快速查找数据结构。
-
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,
这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图就是一种可能的索引方式示例:
-
左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址。为了加快 Col2 的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指 针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
-
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。
-
我们平常所说的索引,如果没有特别指明,都是指B树(所录搜索树,并不一定是二叉树的)结构组织的索引。其种聚集索引,此要索引,覆盖索引,复合索引,前缀索引,唯一索引,默认都是使用 B+树索引,统称索引。当然,除了 B+树这种类型的索引之外,还有哈希索引(hashindex)等。
2.3.2 优势
- 类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的 IO成本。
- 通过索引列对数据进行排序,降低数据排序的成本,降低了 CPU的消耗。
2.3.3 劣势
-
实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。
-
虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。
-
索引只是提高效率的一个因素,如果你的 MySQL有大量数据的表,就需要花时间研究建立最优秀的索引,或优化查询
2.3.4 mysql 索引分类
2.3.4.1 单值索引
-
概念:即一个索引只包含单个列,一个表可以有多个单列索引
-
语法:
#所表一起创建: CREATE TABLE customer ( id INT(10) UNSIGNED AUTO_INCREMENT , customer_no VARCHAR(200),customer_name VARCHAR(200) ,PRIMARY KEY(id) ,KEY (customer_name) ); #单独建单值索引: CREATE INDEX idx_customer_name ON customer(customer_name);
2.3.4.2 唯一索引
-
概念:索引列的值必须唯一,但允许有空值
-
随表一起创建:
CREATE TABLE customer ( id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200) ,PRIMARY KEY(id) ,KEY (customer_name), UNIQUE (customer_no) ); 单独建唯一索引: CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no);
2.3.4.3 主键索引
-
概念:设定为主键后数据库会自动建立索引,innodb为聚簇索引
-
随表一起建索引
CREATE TABLE customer ( id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200) ,PRIMARY KEY(id) ); 单独建主键索引: ALTER TABLE customer add PRIMARY KEY customer(customer_no); 删除建主键索引: ALTER TABLE customer drop PRIMARY KEY ; 修改建主键索引: 必须先删除掉(drop)原索引,再新建(add)索引
2.3.4.4 复合索引
-
概念:即一个索引包含多个列
-
随表一起建索引:
CREATE TABLE customer ( id INT(10) UNSIGNED AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200) ,PRIMARY KEY(id) ,KEY (customer_name), UNIQUE (customer_name) ,KEY (customer_no,customer_name) ); 单独建索引: CREATE INDEX idx_no_name ON customer(customer_no,customer_name);
2.3.4.5 基本语法
操作 | 命令 |
---|---|
创建 | CREATE UNIQUE | INDEX [indexName] ON table_name(column)) |
删除 | DROP INDEX [indexName] ON table_name; |
查看 | SHOW INDEX FROM table_name\G |
使用 Alter命令 | ALTER TABLE tbl_name ADD PRIMARY KEY (column); 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为 NULL。 |
+ | ALTER TABLE tbl_name ADD UNIQUE index_name (column); 这条语句创建索引的值必须是唯一的(除了 null外,null可能出现多次) |
+ | ALTER TABLE tbl_name ADD INDEX index_name (column); 添加普通索引,索引值可出现多次。 |
+ | ALTER TABLE tbl_name ADD FULLTEXT index_name (column); 该语句指定了索引为 FULLTEXT ,用于全文索引。 |
+ | ALTER TABLE tbl_name ADD INDEX index_name (column,column,...); 多列索引。 |
- | alter table table_name drop index index_name; 删除索引。 |
- | alter table table_name drop primary key; 删除主键索引。 |
- alter table table_name add primary key | unique | fulltext index_name (collumn_list);
2.3.5 mysql 索引
2.3.5.1 Btree 索引
-
MySQL 使用的是 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,显然成本非常非常高。
2.3.5.2 R-Tree 索引
-
B+Tree 与 B-Tree 的区别
- B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;B+树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
- 在 B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而 B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看 B-树的性能好像要比 B+树好,而在实际应用中却是 B+树的性能要好些。因为 B+树的非叶子节点不存放实际的数据,这样每个节点可容纳的元素个数比 B-树多,树高比 B-树小,这样带来的好处是减少磁盘访问次数。尽管 B+树找到一个记录所需的比较次数要比 B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+树的性能可能还会好些,而且 B+树的叶子节点使用指针连接在一起,方便顺序遍历(例如查看一个目录下的所有文件,一个表中的所有记录等),这也是很多数据库和文件系统使用 B+树的缘故。
-
思考:为什么说 B+树比 B-树更适合实际应用中操作系统的文件索引和数据库索引?
- B+树的磁盘读写代价更低
B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对 B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说 IO 读写次数也就降低了。
- B+树的查询效率更加稳定
由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
2.3.5.3 聚簇索引和非聚簇索引
-
聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。术语‘聚簇’表示数据行和相邻的键值聚簇的存储在一起。如下图,左侧的索引就是聚簇索引,因为数据行在磁盘的排列和索引排序保持一致。
-
聚簇索引的好处:
按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不不用从多
个数据块中提取数据,所以节省了大量的io 操作。 -
聚簇索引的限制:
对于mysql 数据库目前只有innodb 数据引擎支持聚簇索引,而Myisam 并不支持聚簇索引。
由于数据物理存储排序方式只能有一种,所以每个 Mysql 的表只能有一个聚簇索引。一般情况下就是该表的主键。 -
为了充分利用聚簇索引的聚簇的特性,所以 innodb 表的主键列尽量选用有序的顺序 id,而不建议用无序的id,比如uuid 这种。
2.3.5.4 时间复杂度(扩展)
-
同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法分析的目的在于选择合适算法和改进算法。
-
时间复杂度是指执行算法所需要的计算工作量,用大O 表示记为:O(…)
2.3.6 那些情况需要创建索引
- 主键自动建立唯一索引;
- 频繁作为查询条件的字段应该创建索引
- 查询中与其它表关联的字段,外键关系建立索引
- 频繁更新的字段不适合创建索引,因为每次更新不单单是更新了记录还会更新索引
- where 条件里用不到的字段不创建索引
- 单键/组合索引的选择问题(在高并发倾向组合索引)
- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
- 查询中统计或者分组字段
2.3.7 哪些情况不需要创建索引
-
表记录太少
-
经常增删改的表或者字段
提高查询速度,同时却会降低更新表的速度,如对表进行 insert,update和delete。因为更新表新表是,mysql不仅要保存数据,还要保存一下索引文件。
-
Where 条件里用不到的字段不创建索引
-
过滤性不好的不适合建索引
-
数据重复且分布平均的表字段,一次应该只为最经常查询和经常怕继续的数据列建立索引。
注意,如果某个数据里包含许多重复的内容,为他建立索引就没有太大的实际效果。
-
假如一个表有10万行记录,有一个字段A只有T和F两种值,且每个值得分布概率大约为50%,那么对这种表A字段键索引一般不会提高数据库的查询速度。
-
索引的选择性是指索引列中不同值得数目与表中记录数得比。如果一个表中有2000条记录,表索引列有1980个不同的值,那么这个索引的选择性就是 1980/2000=0.99。一个索引的选择性越接近1,这个索引的效率就越高。
-
2.4 性能分析
2.4.1 MySQL Query Optimizer
- Mysq|中有专门负责优化 SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为
客户端请求的 Query提供他认为最优的执行计划( 他认为最优的数据检索方式,但不见得是 DBA认为是最优的,
这部分最耗费时间) - 当客户端向 MySQL请求一条 Query,命令解析器模块完成请求分类,区别出是SELECT并转发给 MySQL
Query Optimizer时,MySQL Query Optimizer首先会对整条Query进行优化,处理掉- -些常量表达式的预算,
直接换算成常量值。并对 Query中的查询条件进行简化和转换,如去掉--些无用或显而易见的条件、结构调整
等。然后分析 Query中的Hint信息(如果有),看显示Hint信息是否可以完全确定该 Query的执行计划。如果
没有 Hint或 Hint信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据 Query进行写相应的
计算分析,然后再得出最后的执行计划。
2.4.2 MySQL 常见瓶颈
- CPU:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候
- IO:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候
- 服务器硬件的性能瓶颈: top,free, iostat和vmstat来查看系统的性能状态
2.4.3 Explain
2.4.3.1 是什么(查看执行计划)
-
使用 EXPLAIN关键字可以模拟优化器执行 SQL查询语句,从而知道 MySQL是
如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈 -
官方文档: https://dev.mysql.com/doc/refman/5.7/en/execution-plan-information.html
Optimization==》Understanding the Query Execution Plan
2.4.3.2 能干吗
- 表的读取顺序 ==>id
- 数据读取操作的类型 ==>select_type
- 那些索引可以使用 ==>
- 那些索引被实际使用 ===>
- 表之间的引用 ===>
- 每张表有多少行被优化器查询 ==>
2.4.3.3 怎么玩
-
Explain + sql语句
-
Explain 执行返回的信息
Column JSON name Meaning id select_id The SELECT identifier select_type None The SELECT type table table_name The table for the output row partitions partitions The matching partitions type access_type The join type possible_keys possible_keys The possible indexes to choose key key The index actually chosen key_len key_length The length of the chosen key ref ref The columns compared to the index rows rows Estimate of rows to be examined filtered filtered Percentage of rows filtered by table condition Extra None Additional information
2.4.3.4 个字段解释
CREATE TABLE t1(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t2(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t3(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
CREATE TABLE t4(id INT(10) AUTO_INCREMENT,content VARCHAR(100) NULL , PRIMARY KEY (id));
INSERT INTO t1(content) VALUES(CONCAT('t1_',FLOOR(1+RAND()*1000))); #给content 随机插入一个值
INSERT INTO t2(content) VALUES(CONCAT('t2_',FLOOR(1+RAND()*1000)));
INSERT INTO t3(content) VALUES(CONCAT('t3_',FLOOR(1+RAND()*1000)));
INSERT INTO t4(content) VALUES(CONCAT('t4_',FLOOR(1+RAND()*1000)));
id
-
select 查询的序列号,包含一组数字,表示查询中执行 select 子句或操作表的顺序。
-
三种情况
-
id 相同,执行顺序由上至下
-
id 不同,id 不同,如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行
-
有相同也有不同
-
-
总结:id 如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id 值越大,优先级越高,越先执行,衍生 = derived
关注点:id 号每个号码,表示一趟独立的查询。一个 sql 的查询趟数越少越好。
select_type
-
select_type 代表查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。
select_type value JSON Name message simple None 简单的 select 查询,查询中不包含子查询或者 UNION primary None 查询中若包含任何复杂的子部分,最外层查询则被标记为 Primary(相当于鸡蛋壳) union None 若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED depedent union dependent (true) 第二个或更晚的SELECT语句,取决于外部查询 union result union_result 从UNION表获取结果的SELECT subquery None 在SELECT或WHERE列表中包含了子查询 dependent subquery dependent (true) 在SELECT或WHERE列表中包含了子查询,子查询基于外层 derived None 在 FROM 列表中包含的子查询被标记为 DERIVED(衍生) MySQL 会递归执行这些子查询, 把结果放在临时表里。 materiallized materialized_from_subquery 实体化子查询 uncacheable subquery cacheable (false) 无法缓存其结果的子查询,必须对外部查询的每一行重新求值 uncacheable union cacheable (false) UNION中属于不可缓存子查询的第二次或以后的选择(参见不可缓存子查询)
table
- 显示这一行的数据是关于那张表的
type
-
type 是查询的访问类型。是较为重要的一个指标,结果值从最好到最坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >ALL //一般来说,得保证查询至少达到 range 级别,最好能达到 ref。 system > const > eq_ref > ref > range > index > ALL
-
system:表只有一行记录(等于系统表),这是 const 类型的特列,平时不会出现,这个也可以忽略不计
-
const:表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引。因为只匹配一行数据,所以很快如将主键置于 where 列表中,MySQL 就能将该查询转换为一个常量。 ![]
备注:MySQL 5.7 这里不显示 derived (衍生)
-
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。
-
ref:非唯一性索引扫描,返回匹配某个单独值的所有行.本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。
-
没有用索引前
-
建立索引后
-
-
ref_or_null:对于某个字段既需要关联条件,也需要 null 值得情况下。查询优化器会选择用 ref_or_null 连接查询。
-
index_merge:在查询过程中需要多个索引组合使用,通常出现在有 or 的关键字的 sql 中。
备注:MySQL 5.7 中,这里 type不再是 index_merge
-
unique_subquery:该联接类型类似于 index_subquery。 子查询中的唯一索引。
备注:MySQL 5.7 中,这里 type不再是 unique_subquery
备注:一般来说,得保证查询至少达到range级别,最好能达到ref。
-
index_subquery:利用索引来关联子查询,不再全表扫描。
-
range:只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引一般就是在你的 where 语句中出现了 between、<、>、in 等的查询这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。
备注:mysql 5.7查询 where用了in type这里不是 range
-
index:Full Index Scan,出现 index是 sql使用了索引但是没用通过索引进行过滤,一般是使用了覆盖索引或者是利用索引进行了排序分组。(index 与 all区别为 index类型只遍历索引树。这通常比 all快,因为索引文件通常比数据文件小。也就是说虽然 all和 index都是读全表,但 index是从索引中读取的,而 all是从硬盘中读取的)
-
all:Full Table Scan,将遍历全表以找到匹配的行。
possible_keys
- 显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
key
-
实际使用的索引。如果为NULL,则没有使用索引。
查询中若使用了覆盖索引,则该索引仅出现在 key列表中。(Extra_use index)
key_len
-
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。 在不损失精确性的情况下,长度越短越好。key_len 显示的只为索引字段的最大可能长度,并非实际使用的长度,即 ken_len 是根据表定义计算而得,不是通过表内检索出。
-
如何计算:
- 先看索引上字段的类型+长度比如 int=4 ; varchar(20) =20 ; char(20) =20
- 如果是 varchar 或者 char 这种字符串字段,视字符集要乘不同的值,比如 utf-8 要乘 3,GBK 要乘 2,
- varchar 这种动态字符串要加 2 个字节
- 允许为空的字段要加 1 个字节
第一组:key_len=age 的字节长度+name 的字节长度=4+1 + ( 20*3+2)=5+62=67
第二组:key_len=age 的字节长度=4+1=5
列类型 KEY LEN 备注 id int key_ len=4+1=5 允许NUL,加1-byte id int not null key_len=4 不允许NULL user char(30) utf8 key_ _len =30*3+1 允许NULL user varchar(30) not null utf8 key_ _len=30*3+2 动态列类型,加2 -bytes user varchar(30) utf8 key_ len =30*3+2+1 动态列类型,加2-bytes;允许NULL,再加1-byte detail text(10) utf8 key_ len =30*3+2+1 TEXT列截取部分,被视为动态列类型,加2-bytes;且允许NULL
ref
-
显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
rows
-
根据表统计信息及索引选用情况,大致算出找到所需的记录所需要读取的行数
filtered
- 筛选的列指示表条件筛选的表行的估计百分比。最大值是100,这意味着没有对行进行过滤。值从100下降表示过滤量增加。行显示已检查的估计行数,rows
×
filtered 显示与下表连接的行数。例如,如果rows
数为1000,并且 filtered 的行数为50.00(50%),则要与下表合并的行数为1000 × 50% = 500。
Extra
-
其他的额外重要的信息。
-
Using filesort
说明 mysql 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL 中无法利用索引完成的排序操作称为“文件排序”。
出现 filesort 的情况:优化后,不再出现 filesort 的情况:
查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度。
-
Using temporary
使了用临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。
-
Using index
Using index 代表表示相应的 select 操作中使用了 覆盖索引(Covering Index),避免访问了表的数据行,效率不错!如果同时出现 using where,表明索引被用来执行索引键值的查找;如果没有同时出现 using where,表明索引只是用来读取数据而非利用索引执行查找。
利用索引进行了排序或分组。
-
覆盖索引(Covering index),一说为索引覆盖。
理解方式一:就是 select的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回 select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。
理解方式二:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了(或覆盖了)满足查询结果的数据就叫覆盖索引。
注意:如果要使用覆盖索引,一定要注意 select列表中取出需要的列,不可 select*,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。
-
-
-
Using where
表明使用了 where 过滤。
-
Using join buffer
使用了连接缓存。
-
impossible where
where 子句的值总是 false,不能用来获取任何元组。
-
select tables optimized away
在没有 GROUPBY 子句的情况下,基于索引优化 MIN/MAX 操作或者对于 MyISAM 存储引擎优化 COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
在 innodb 中:
在 Myisam 中:
-
distinct
优化 distinct操作,在找到第一匹配的元组后即可停止找同样值得动作
2.4.3.5 热身 Case
请写出第一条执行了那条 sql,第二条
explain select d1.name,(select id from t3) d2
from (select id,name from t1 where other_column='') d1
union
(select name,id from t2);
id select_type table type possible_keys key key_len ref rows extra
1 primary <derived3> system null null null null 1
3 deriued t1 all null null null null 1 using where
2 subquery t3 index null primary 4 null 1 using index
4 union t2 add null null null null 1
null union result <union1,4> all null null null null null
- 第一行(执行顺序4):id列为1,表示 union里的第一个 select,select_type列的 primary表示查询为外层查询,table列被标记为 <derived3>,表示查询结果来自一个衍生表,其中 derived3 中 3代表该擦汗寻衍生自第三个 select查询,即 id为3的 select。【select d1.name ...】
- 第二行(执行顺序2):id为3,是整个查询中第三个 select的一部分。因查询包含在 from中,所以 derived。【select id,name from t1 whree 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操作】
2.5 索引优化
2.5.1 索引分析
2.5.1.1 单表
建表 sql
create table if not exists article( #文章
id int(10) unsigned not null primary key auto_increment, #主键
author_id int(10) unsigned not null, #作者id
category_id int(10) unsigned not null, #分类id
views int(10) unsigned not null, #被查看的子组
comments int(10) unsigned not null, #回帖的备注
title varbinary(255) not null, #表文主题
content text not null #文章主题
);
insert into article values(null,1,1,1,1,'1','1'),(null,2,2,2,2,'2','2'),(null,1,1,3,3,'3','3');
select * from article;
案例
#查询category_ _id 为1且comments大于1的情况下,views最多的article_ jid。
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
#结论:很显然,type是ALL,即最坏的情况。Extra 里还出现了Using filesort, 也是最坏的情况。优化是必须的。
#开始优化:
#1.1新建索引+删除索引
#ALTER TABLE article ADD INDEX idx_article_ccv (`category_id`,`comments`, `views`);
create index idx_article_ccv on article(category_id,comments,views);
# DROP INDEX idx_article_ccv ON article; #删除索引
show index from article; #查看表总的索引
#1.2第2次EXPL AIN
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments >1 ORDER BY views DESC LIMIT 1;
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments =3 ORDER BY views DESC LIMIT 1;
#结论:
#type变成了range,这是可以忍受的。但是extra里使用Using filesort仍是无法接受的。
#但是我们已经建立了索引,为啥没用呢?
#这是因为按照BTree索引的工作原理,
#先排序category_id,
#如果遇到相同的category_id 则再排序comments,如果遇到相同的comments则再排序views.
#当comments字段在联合索引里处于中间位置时,
#因comments> 1条件是-一个范围值(所谓range),
#MySQL无法利用索引再对后面的views部分进行检索,即range类型查询字段后面的索引无效。
#1.3删除第一次建立的索引
DROP INDEX idx_article_cvv ON article;
#1.4第2次新建索引
#ALTER TABLE article ADD INDEX idx_article_cv ( category_id ,views);
create index idx_article_cv on article(category_id,views);
# 1.5第3次EXPLAIN
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 也消失了,结果非常理想。
DROP INDEX idx_article_cv ON article;
2.5.1.2 两表
建表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)
);
#插入 20个
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
#插入 20个
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
SELECT * FROM class;
SELECT * FROM book;
案例
#下面开始explain分析
explain select * from class left join book on class.card=book.card;
#结论:type 有all
#添加索引优化
alter table book add index Y (card);
#第2次explain
explain select * from class left join book on class.card=book.card;
#可以看到第二行的type变成ref,rows也变成优化比较明显。
#这是由左连接特性决定的。left join条件用于确定如何从右表搜索行,左边一定都有
#所以右边是我们的关键点,一定需要创立索引
#删除旧索引+新建+第三次 explain
drop index Y on book;
alter table class add index X (card);
explain select * from class left join book on class.card=book.card;
#然后来看一个右连接查询
#优化较明显。这是因为 right join条件用于确定如何从左表搜索行,右表一定都有,所以左边是我们的关键点,一定要建立索引。
explain select * from class right join book on class.card=book.card;
drop index X on class;
alter table book add index Y (card);
#右连接,基本 无变化
explain select * from class right join book on class.card=book.card;
总结:左连接 left join,建立在右表上;右连接 right join,建立在 左表上。
2.5.1.3 三表
建表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;
//插入20个
insert into phone(card) values(floor(1+rand()*20));
案例
alter table phone add index z (card);
alter table book add index Y (card); #上一个 case建过同样的
explain select * from calss left join book on class.card=book.card left join phone on book.card=phone.card;
#后2行的 type都是 ref且总 rows优化很好,效果不错。因此索引最好设置在经常需要查询的字段中。
#class (种类)小的结果,book (书籍)大的
【结论】
- join语句的优化
尽可能减少 join语句中的 nestedloop的循环总次数,“永远用小的结果驱动大的结果集”。(上面案例就是)
优先优化 nestedloop的内存循环
保证 join语句中被驱动表上 join条件字段已经被索引;
当无法保证被驱动表的 join条件字段被索引且内存资源充足的前提下,不要太吝(lin)惜 joinbuffer的设置。joinbuffer的设置。
2.5.2 索引失效(应该避免)
2.5.2.1 建表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());
SELECT * FROM staffs;
ALTER TABLE staffs ADD INDEX idx_staffs_nameAgePos(NAME, age, pos); I
show index from staffs;
2.5.2.2 案例(索引失效)
-
全值匹配我最爱
EXPLAIN SELECT * FROM staffs WHERE NAME=''; EXPLAIN SELECT * FROM staffs WHERE NAME='' AND age=''; EXPLAIN SELECT * FROM staffs WHERE NAME='' AND age='' AND pos=''; #结论:全职匹配我最爱指的是:查询的字段按照顺序在索引中都可以匹配得到! #sql中查询字段的顺序,根使用索引中字段的顺序,没有关系。优化器会在不影响sql 执行结果的前提下给你自动的优化
-
最佳左前前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列
EXPLAIN SELECT * FROM staffs WHERE age=23 AND pos='dev'; EXPLAIN SELECT * FROM staffs WHERE pos='dev'; explain select * from staffs where name =july; #查询字段与索引顺序的不同会导致索引无法充分使用,甚至索引失效! #原因:使用复合索引,需要遵守最佳前缀法则,即如果索引了多列,要遵守最左前缀法则。指的是查询从索引的'最左前列开始并且不跳过索引中的列。' #结论:过滤条件要使用索引必须按照索引建立时的顺序,依此满足,一旦跳过某个字段,索引后面的字段都无法被使用。
-
不在索引列上做任何操作(计算,函数,(自动 or手动)类型转换),会导致索引失效而转向全表扫描
EXPLAIN SELECT * FROM staffs WHERE NAME='July'; EXPLAIN SELECT * FROM staffs WHERE LEFT(NAME,4)='July';
-
存储引擎不能使用索引中范围条件右边的列
EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND age>25 AND pos='manager'; #建议:尽可能做范围查询的字段的索引顺序放在最后
-
尽量使用覆盖索引(只能访问索引的查询(索引列和查询列一致)),减少 select*
EXPLAIN SELECT * FROM staffs WHERE NAME='July' AND age>25 AND pos='dev'; EXPLAIN SELECT NAME,age,pos FROM staffs WHERE NAME='July' AND age>25 AND pos='dev'; EXPLAIN SELECT NAME,age,pos FROM staffs WHERE NAME='July' AND age>25; EXPLAIN SELECT NAME,age,pos FROM staffs WHERE NAME='July';
-
MySQL在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
explain select * from staffs where name !='July'; explain select * frm staffs where name <> 'July';
-
is null,is not null 也无法使用索引
explain select * from staffs where name is null; explain select * from staffs where name is not null; #is not null用不到索引 ,is null可以用到索引
-
like 以通配符开头 ('%abc...') mysql索引失效会变成全表扫描的操作
EXPLAIN SELECT * FROM staffs WHERE NAME LIKE '%July'; EXPLAIN SELECT * FROM staffs WHERE NAME LIKE '%July%'; EXPLAIN SELECT * FROM staffs WHERE NAME LIKE 'July%'; #发现 百分号写在右边,可以优化,左边索引会失效
问题:解决 like '%字符串%' 时索引不被使用的方法??
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; #drop table tbl_user INSERT INTO tbl_user(NAME,age,email) VALUES('1aa1' ,21,'b@163.com'); INSERT INTO tbl_user(NAME,age,email) VALUES('2aa2' ,222,'a@63.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'); SELECT * FROM tbl_user; # before index 没有创建索引的时候 type:all key:null extra:using where 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%'; #create index 创建联合索引 name,age CREATE INDEX idx_user_nameAge ON tbl_user(NAME,age); #drop index idx_user_nameAge ON tbl_user ; #删除索引 #alter table tbl_user drop index idx_user_nameAge; #删除索引 #show index from tbl_user; #查看索引 EXPLAIN SELECT NAME,age FROM tbl_user WHERE NAME LIKE '%aa%'; #创建索引之后,这时候是:type:index key:idx_user_nameAge extra:using where;using index (必须字段是 id,name,age 这些某个或者组合) #如果查询的字段加上了 email就没有使用到索引,或者 * 也是 type:all key:null extra:using where
-
字符串不加单引号索引失效
EXPLAIN SELECT * FROM staffs WHERE NAME =200; EXPLAIN SELECT * FROM staffs WHERE NAME ='200';
-
少用 or,用它来连接时会索引失效
EXPLAIN SELECT * FROM staffs WHERE NAME ='July' OR age=23; #使用 union all或者 union来替代
-
练习
假设 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 a = 3 or where b = 3 and c=4 or where c=4 N where a = 3 and c= 5 使用到a,但是c不可以,b中间断了 wehre a=3 and b >4 and c = 5 使用到a和b,不能用在范围之后,b断了 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 优化有诀窍。
2.5.2.3 面试题讲解
-
题目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','a1','a3','a1','a1'); INSERT INTO test03(c1,c2,c3,c4,c5) VALUES('a2','a2','a2','a2','a2'); INSERT INTO test03(c1,c2,c3,c4,c5) VALUES('a3','a3','a3','a3','a3'); INSERT INTO test03(c1,c2,c3,c4,c5) VALUES('a4','a4','a4','a4','a4'); INSERT INTO test03(c1,c2,c3,c4,c5) VALUES('a1','a5','a3','a4','a5'); SELECT * FROM test03; #【建索引】 create index idx_test03_c1234 on test03(c1,c2,c3,c4); show index from test03; #【问题:我们创建了复合索引 ,根据一下slq分析下索引使用情况?】 explain select * from test03 where c1='a1'; #type:ref key:idx_test03_c1234 key_len=31 ref:const explain select * from test03 where c1='a1' and c2='a2'; #type:ref key:idx_test03_c1234 key_len:62 ref:const,const explain select * from test03 where c1='a1' and c2='a2' and c3 ='a3'; #type:ref key:idx_test03_c1234 key_len:63 ref:const,const,const explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4 ='a4'; #type:ref key:idx_test03_c1234 key_len:124 ref:const,const,const,const #1 全职匹配我最爱) explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4 ='a4'; #type:ref key:idx_test03_c1234 key_len:124 ref:const,const,const,const extra:using where #2 optimizer 会开启自我优化分析) explain select * from test03 where c1='a1' and c2='a2' and c4 ='a4' and c3='a3'; explain select * from test03 where c4 ='a4' and c3='a3' and c2='a2' and c1='a1' ; #type:ref key:idx_test03_c1234 key_len:124 ref:const,const,const,const extra:using where #3 范围之后全失效) explain select * from test03 where c1 ='a1' and c2='a2' and c3>'a3' and c4='a4'; # type:range key:index_test03_c1234 key_len=93 ref=null extra:using where #4) explain select * from test03 where c1 ='a1' and c2='a2' and c4>'a4' and c3='a3' ; # type:range key:index_test03_c1234 key_len=124 ref=null extra:using index condition #5 索引两大功能:查找,排序) explain select * from test03 wehere c1 ='a1' and c2='a2' and c4='a4' order by c3 ; # type:range key:index_test03_c1234 key_len=62 ref:const,const extra:using where #c3作用在排序而不是查找 #6) explain select * from test03 wehere c1 ='a1' and c2='a2' order by c3 ; # type:ref key:index_test03_c1234 key_len=62 ref:const,const extra:using where #7 索引顺序和排序顺序不一致,中间兄弟断了) explain select * from test03 wehere c1 ='a1' and c2='a2' order by c4 ; # type:ref key:index_test03_c1234 key_len=62 ref:const,const extra:using index condition;using filesort #出现了 filesort #8) #8.1) EXPLAIN SELECT * FROM test03 WHERE c1 ='a1' AND c5='a5' ORDER BY c2,c3 ; # type:ref key:index_test03_c1234 key_len=31 ref:const extra:using index condition;using where #只用 c1一个字段索引,但是c2,c3用于排序,无 filesort #8.2) EXPLAIN SELECT * FROM test03 WHERE c1 ='a1' AND c5='a5' ORDER BY c3,c2 ; # type:ref key:index_test03_c1234 key_len=31 ref:const extra:using index condition;using where;using filesort #出现了 filesort,我们建的索引是 1234,它没有按顺序来,3 2颠倒了 #9) EXPLAIN SELECT * FROM test03 WHERE c1 ='a1' AND c2='a2' ORDER BY c2,c3 ; # type:ref key:index_test03_c1234 key_len=31 ref:const extra:using index condition;using where #10) EXPLAIN SELECT * FROM test03 WHERE c1 ='a1' AND c2='a2' AND c5='a5' ORDER BY c2,c3 ; #type:ref key:index_test03_c1234 key_len=62 ref:const,const extra:using index condition;using where #用 c1,c2两个字段索引,但是 c2,c3用于排序,无 filesort EXPLAIN SELECT * FROM test03 WHERE c1 ='a1' AND c2='a2' AND c5='a5' ORDER BY c3,c2 ; #type:ref key:index_test03_c1234 key_len=62 ref:const,const extra:using index condition;using where #本列有常量 c2(这里c2是一个单独值,常量)的情况 和 8.2对比: EXPLAIN SELECT * FROM test03 WHERE c1 ='a1' AND c5='a5' ORDER BY c3,c2 ; #type:ref key:index_test03_c1234 key_len=31 ref:const extra:using index condition;using filesort #11) EXPLAIN SELECT * FROM test03 WHERE c1 ='a1' AND c4='a4' group by c2,c3 ; #type:ref key:index_test03_c1234 key_len=31 ref:const extra:using where #12 不符合索引顺序) explain select * from test03 where c1='a1' and c4='a4' group by c3,c2; #type:ref key:index_test03_c1234 key_len=31 ref:const extra:using index condition;using temporary;using filesort
-
定值(常量),范围(之后是失效)还是排序,一般 order by是给个范围,group by(分组)基本上都需要进行排序,会有临时表产生。
2.5.3 一般性建议
- 对于单键索引,尽量选择对当前 query过滤性更好的索引
- 在选择组合索引的时候,当前 query中过滤性最好的字段在索引字段顺序中,位置越靠前(左)越好。
- 在选择组合索引的时候,尽量选择可以能够包含当前 query中的 where子句中更多字段的索引
- 尽可以能通过分析统计信息和调整 query的写法来达到选择合适索引的目的
三,查询截取分析
3.1 查询优化
3.1.1 永远小表驱动大表
-
类似嵌套循环 nested loop
-
优化原则:小表驱动大表,即小的数据集驱动大的数据集
#######################原理(RBO)######################## 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。 select * from A where exists (select 1 from B where B.id = A.id) # 这里的1只要是个常量就行 等价于 from select * from A from select * from B where B.id = A.id 当A表数据集系小于B表的数据集时,用 exists由于 in 注意:A表与B表的 id字段应建立索引。
-
exists
select ... from table where exists (subquery)
改语法可以理解为:将主查询的数据,放到子查询中做条件验证,根据验证结果(true或false)来决定主查询的数据结果是否得以保留。
- exists(subquery) 只返回 true或 false,因此子查询中的 select* 也可以是 select 1或者 select 'X',官方说法是实际执行时会忽略 select清单,依此没有区别。
- exists 子查询的实际执行过程可能经过了优化而不是我们理解上的逐条对比,如果担忧效率问题,可进行实际检验以确定是否有效率问题。
- exists 子查询往往也可以用条件表达式,其他子查询或 join来代替,何种最有需求要具体问题分析。
3.1.2 order by关键字优化
-
order by之前有没有用到索引,之后 会不会产生 filesort
-
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); SELECT * FROM tblA;
-
case
EXPLAIN SELECT * FROM tblA WHERE age>20 ORDER BY age; EXPLAIN SELECT * FROM tblA WHERE age>20 ORDER BY age,birth; EXPLAIN SELECT * FROM tblA WHERE age>20 ORDER BY birth; EXPLAIN SELECT * FROM tblA WHERE age>20 ORDER BY birth,age;
EXPLAIN SELECT * FROM tblA ORDER BY birth; EXPLAIN SELECT * FROM tblA WHERE birth > '2016-01-28 00:00:00' ORDER BY birth; EXPLAIN SELECT * FROM tblA WHERE birth > '2016-01-28 00:00:00' ORDER BY age; EXPLAIN SELECT * FROM tblA ORDER BY age ASC,birth DESC; # order by默认升序,
-
MySQL支持二中方式的排序,filesort和 index,index效率高,它指 MySQL扫描索引本身完成排序。filesort方式效率低。
-
order by满足两种情况,会使用 index方式排序
- order by 语句使用索引最左前列
- 使用 where子句与 order by子句条件列组合满足索引最左前列
-
-
尽可能在索引列上完成排序,遵照索引的最佳左前缀
-
如果不在索引列上,filesort有两种算法:mysql就要启动双路排序和单路排序
-
双路排序:
MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和 order by列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中的值重新从列表中读取对应的数据输出。
从磁盘取排序字段,在 buffer进行排序,再从磁盘取其他字段。
取一批数据,要对磁盘进行两次扫描,众所周知,i/o 是很耗时的,所以 myslq4.1之后,出现了第二种改进的算法,就是单路排序。
-
单路排序:
从磁盘读取查询需要的所有列,按照 order by列在 buffer对他们进行排序,然后扫描排序后的列表进行输出,他的效率更快一些,避免了第二次读取数据。并且把随机 i\o 变成了顺序 i\o,但是他会使用更多空间,因为他把每一行都保存在内存中了。
-
结论及引申的问题:
由于单路是后出的,总体而言好过双路,但是用单路有问题。
在 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 参数的设置
-
why:提高 order by的速度
- Order by时select *是一个大忌只 Query需要的字段,这点非常重要。在这里的影响是:
1.1当Query的字段大小总和小于max_ ength_for_sort_data 而且排序字段不是 TEXT|BLOB类型时,会用改进后的算法一单
路排序,否则用老算法一多路排序。
1.2两种算法的数据都有可能超出sort_buffer的容量, 超出之后,会创建tmp文件进行合并排序,导致多次I/O, 但是用单路排序
算法的风险会更大一些, 所以要提高sort_buffer_size 。 - 尝试提高sort_buffer_size
不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的 - 尝试提高max_ length_for_sort_data
提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_ buffer size的概率就增大,明显症状是高
的磁盘I/O活动和低的处理器使用率.
- Order by时select *是一个大忌只 Query需要的字段,这点非常重要。在这里的影响是:
-
-
小总结:
为排序使用索引
- 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 #排序不一致 where a = const order by c #丢失a索引 where a = const order by a,b #丢失b索引 where a in(...) order by b,c #对排序来说,多个相等条件也是范围查询
3.1.3 group by关键字优化
- group by 实质是先排序后进行分组,遵照索引建的最佳左前缀,当无法使用索引列,增大 max_length_for_sort_data 参数的设置 + 增大 sort_buffer_size 参数的设置,where 高于 having,能写在 where 限定的条件就不要去 having限定了。
3.2 慢查询日志
3.2.1 是什么
- MySQL的慢查询日志是 MySQL提供的一种日志记录,它用来记录在 MySQL中响应事件超过的阈值的语句,具体值运行事件超过 long_query_time值得 sql,则会被记录到慢查询日志中。
- 具体指运行事件超过 long_query_time 值的 sql,则会被记录到慢查询日志中。long_query_time的默认值为 10,意思是运行10秒以上的语句。
- 由他来查看那些 sql超出了我们的最大忍耐时间值,比如一条 sql执行超过 5秒钟,我们就算慢 sql,希望能收集超过5秒的 sql,结合之前 explain进行全面分析。
3.2.2 怎么玩
说明
- 默认情况下,MySQL 数据库没有开启慢查询日志,需要我们手动来设置这个参数。
- 当然,如果不是调优需要的话,一般不建议启动参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。
查看是否开启及如何开启
-
默认:show variables like '%slow_query_log%';
默认情况下:show_query_log 的值为 off,表示慢查询日志是禁用的,可以通过设置 slow_query_log的值来开启
-
开启:set global show_query_long=1;
如果使用 set global show_query_long = 1 开启了慢查询日志支队当前数据库有效,如果 MySQL重启则会失效。
如果要永久生效,就必须修改配置文件 my.cnf (其他系统变量也是如此)
修改 my.cnf 文件,【mysqld】下增加或修改参数
show_query_log 和 slow_query_log_file 后,然后重启 MySQL服务器。也即将如下两行配置进 my.cnf 文件
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/系统名称-slow.log
关于慢查询的参数 slow_query_log_file,它指定慢查询日志文件的存放路径,系统默认会给一个 缺省的文件 host_name-slow.log(如果没有指定参数 slow_query_log_file 的话)
开启慢查询日志,什么样的 sql才会记录到慢查询日志里面?
-
这个是由参数 long_query_time 控制,默认情况下 long_query_time 的值为10秒,命令:
show variables like '%long_query_time%';
可以使用命令修改,也可以在 my.cnf 参数里面修改。
假如运行时间正好等于 long_query_time 的情况,并不会被记录下来。也就是说:在 MySQL源码里是判断大于 long_query_time,而非大于等于.
case
-
查看当前多少秒算慢
show variables like 'long_query_time%'
-
设置慢的阈值时间
set global long_query_time = 3;
-
为什么设置后看不出来变化?
需要重新连接或新开一个会话才能看到修改值。
show variables like 'long_query_time%';
show global varibles like 'long_query_time';
-
记录慢 sql并后续分析
模仿 sql执行4秒:select sleep(4);
-
查询当前系统中由多少条慢查询记录
show global status like '%Slow_queries%';
配置版
[mysqld]
slow_query_log=1;
slow_query_log_file=/var/lib/mysql/系统名称-slow.log
long_query_time=3;
long_output=FILE
3.2.3 日志分析工具 mysqldumpslow
- 在生产环境中,如果要手工分析日志,查找,分析sql,显然是个体力活,MySQL提供了日志分析工具 mysqlumpslow。
查看 mysqldumpslow的帮助信息
mysqldumpslow --help
s:是表示按照何种方式排序
c:访问次数
l:锁定时间
r:返回记录
t:查询时间
al:平均锁定时间
ar:平均返回记录数
at:平均查询时间
t:即为返回前面多少条的数据
g:后边搭配一个正则匹配模式,大小写不敏感的
工作常用参数
得到返回记录最多的 10个 sql
mysqldumpslow -s r -t 10 /var/lib/mysql/系统名称-slow.log
得到访问次数最多的10个 sql
mysqldumpslow -s c -t 10 /var/lib/mysql/系统名称-slow.log
得到按照时间排序的前10条里面含有左连接的查询语句
mysqldumpslow -s t -t 10 -g 'left join' /var/lib/mysql/系统名称-slow.log
另外建议在使用这些命令时结合 | 和 more 使用,否则有可能出现爆屏情况
mysqldumpslow -s r -t 10 /var/lib/mysql/系统名称-slow.log | more
3.3 批量数据脚本
往表里插入 1000w数据
-
建表
#新建库 CREATE DATABASE bigData; USE bigData; #建表 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; #建表 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;
-
设置参数 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;
-
这样添加了参数以后,如果 mysqld重启,上述参数又会消失,永久方法:
windows 下:myini [mysqld] 加上 log_bin_trust_function_creators=1
Linux下:/etc/my.cnf 下 my.cnf [mysqld] 加上 log_bin_trust_function_creators=1
-
-
创建函数,保证每条数据都不同
-
随机产生字符串
DELIMITER $$ CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255) BEGIN DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ'; 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 $$ #如果要删除函数,则执行:drop function rand_string;
-
随机产生部门编号
#用于随机产生多少到多少的编号 DELIMITER $$ CREATE FUNCTION rand_num () RETURNS INT(11) BEGIN DECLARE i INT DEFAULT 0; SET i = FLOOR(100+rand()*10) ; RETURN i; END$$ #如果要删除函数:drop function rand_num;
-
-
创建存储过程
-
创建 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',001,CURDATE(),2000,400,rand_num()); UNTIL i = max_num END REPEAT; COMMIT; END$$ #删除 # DELIMITER ; # drop PROCEDURE insert_emp;
-
创建 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;
-
-
调用存储过程
-
dept
#执行存储过程,往dept 表添加 10条数据 DELIMITER ; CALL insert_dept(100,10);
-
emp
#执行存储过程,往emp 表添加50 万条数据 DELIMITER ; CALL insert_emp(100001,500000);
-
3.4 show profile
- 是什么:是 mysql提供可以用来分析当前会话中语句执行的资源消耗情况。用于 slq的调优的测量
- 官网:https://dev.mysql.com/doc/refman/5.7/en/show-profile.html
- 默认情况下,参数处于关闭状态,并保存最近 15次的运行结果
分析步骤
-
是否支持,看看当前的 MySQL版本是否支持
show variables like 'profiling';
默认是关闭,使用前需要开启
-
开启功能,默认是关闭,使用前需要开启
set profiling = on;
-
运行 sql
select * from emp gruop by id%10 limit 150000;
select * from emp group by id %20 order by 5;
-
查看结果,show profiles;
-
诊断 sql,show profile cpu,block io for query 上一步前面的问题 sql数字号码;
参数备注:
type
all = 显示所有开销信息
block io = 显示快 io相关开销
context switches = 上下文切换相关开销
cpu = 显示 cpu相关开销
ipc = 显示发送和接收相关开销信息
memory = 显示页面错误相关开销
page faults = 显示页面错误相关开销信息
source = 显示和Source_function,Source_file,Source_line相关的开销信息
swaps = 显示交换次数相关开销
-
日常开发需求注意的结论
- converting HEAP to MyISAM 查询结果太大,内存都不够用了往磁盘上搬了。
- creating tmp table 创建临时表,拷贝数据到临时表用完再删除
- copying to tmp table on disk 把内存中临时表复制到磁盘,危险!!!
- locked
3.5 全局查询日志
3.5.1 配置启用
-
再 mysql的 my.cnf中,设置如下:
#开启 general_log=1 #记录日志文件的路径 general_log_file=/path/logfile #输出格式 log_output=FILE
3.5.2 编码启用
-
命令
set global general_log = 1; set global log_output = 'table'; #此后,你所编写的 sql语句,将会记录到 MySQL库里的 general_log表,可以用下面的命令查看 select * from mysql.generalo_log;
永远不要再生产环境开启这个功能
四,myslq 锁机制
4.1 概述
4.1.1 定义
- 锁是计算机协调多个进程或线程并发访问某一资源的机制。
- 在数据库中,除传统的计算资源(如 cpu,ram,i/o等)的争用以外,数据也是一种供许多用户共享的资源。如何保证并发访问的一致性,有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个酵素来说,锁对数据库而言显得尤其重要,也是更加复杂。
4.1.2 生活购物+锁的分裂
- 打个比方,我们淘宝上买一件商品,商品只有一件库存,这时候如果还有另外一个人买,那么如何解决是你买到还是另外一个人买到的问题?
- 这里肯定要用到事务,我们先从库存表中取出物品数量,然后插入订单,付款插入付款表信息,然后更新商品数量。在这个过程中,使用锁可以对有限的资源进行保护,解决隔离和并发的矛盾。
4.1.3 锁的分类
-
从对数据库操作的类型(读/写)分,
读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会相互影响。
写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。
-
从对数据操作的粒度分:表锁,行锁
4.2 三锁
开销,加锁速度,死锁,粒度,并发性能只能就具体应用的特点说哪种锁更适合
4.2.1 表锁(偏读)
-
特点:偏向 myisam存储引擎,开销小,加锁快,无死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低。
-
案例分析:
-
建表sql
#[表级锁分析-建表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; #[手动增加表锁] lock table 表1名字 read(write),表2名字 read(write),其它; #[查看表上加过的锁] show open tables; #[释放表锁] unlock tables;
-
加读锁
session_1 session_2 获得表 mylock的 read锁定 连接终端 当前 session可以查询该表记录 其他 session也可以查询该表的记录 当前 session不能查询其他没有锁定的表 其他 session可以查询或更新未锁定的表 当前 session中插入或者更新锁定的表都会提示错误 其他 session插入或者更新锁定表会一直等待获得锁 释放锁 session_2获得锁,插入操作完成 -
加写锁
session_1 session_2 获得表 mylock的 write锁定 待 session1开启后,session2在连接终端 当前 session对锁定表的查询,更新,插入操作都可以执行 其他 session对锁定的表的查询被阻塞,需要等待锁被释放:备注如果可以,亲换成不同id来进行测试,因为 mysql聪明有缓存,第2次的条件会从缓存取得,影响锁效果演示 释放锁 sessio_2 获得锁,查询返回
-
-
案例结论:
-
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
-
MySQL的表级锁有两种模式:
表共享读锁(Table Read Lock)
表独占写锁(Table Write Lock)锁类型 可否兼容 读锁 写锁 读锁 √ √ × 写锁 √ × × -
结合,上表,所以对MyISAM表进行操作,会有以下情况:
- 对MyISAM表的读操作(加读锁),不会阻塞其他进程对同- -表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,
才会执行其它进程的写操作。 - 对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作
- 对MyISAM表的读操作(加读锁),不会阻塞其他进程对同- -表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,
-
简而言之,就是读锁会阻塞写,但是不会阻塞读。而写锁会把读和写都阻塞。
-
-
表锁分析:
#看看那些锁被加锁了 show open tables; #如何分析表锁定 #可以通过检查 table_locks_waited 和 table_locks_immediate 状态变量来分析系统上的表锁定 show status like 'table%';
-
两个状态变量记录 MySQL内部表级锁定的情况,说明如下:
Table_ locks_ immediate: 产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁值加1 ;
Table_locks_waited:出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次锁值加1),此值高则说明存在着较严重的表级锁争用情况;
此外,Myisam的读写锁调度是写优先,这也是myisam不适合做写为主表的引擎。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞
-
4.2.2 行锁(偏写)
-
特点:
-
偏向 innodb存储引擎,开销大,加锁慢,会出现死锁,锁定粒度最小,发生所冲突的概率最低,并发度也最高。
-
innodb 与 myisam的最大不同有两点:
支持事务、采用行级锁
-
由于行锁支持事务
-
事务(transaction)及其 acid属性并
事务是由- -组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。
- 原子性(Atomicity) :事务是-一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。.
- 一致性(Consistent): 在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改
,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。 - 隔离性(Isolation) :数据库系统提供- -定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处
理过程中的中间状态对外部是不可见的,反之亦然。 - 持久性(Durable):事务完成之后,它对于数据的修改是永久性的,I即使出现系统故障也能够保持。
-
发事务处理带来的问题
- 更新丢失(lost update
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问
题一一最后的更新覆盖了由其他事务所做的更新。
例如,两个程序员修改同 - java文件。每程序员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其
更改副本的编辑人员覆盖前一个程序员所做的更改。
如果在一个程序员完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题。- 脏读(dirty reads
一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一
条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进-步的处理,就会产生未提交的数据依赖关系。这种现象
被形象地叫做”脏读”。
一句话:事务A读取到了事务B已修改但尚未提交的的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取
的数据无效,不符合-致性要求。- 不可重复读(non-repeatable reads
一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除
了!这种现象就叫做“不可重复读”。
一句话:事务A读取到了事务B已经提交的修改数据,不符合隔离性- 幻读(phantom reads
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读
一句话:事务A读取到了事务B体提交的新增数据,不符合隔离性。
多说-一句:幻读和脏读有点类似,
脏读是事务B里面修改了数据,
幻读是事务B里面新增了数据。 -
事务隔离级别
脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上 “串行化” 进
行,这显然与“并发”是矛盾的。同时,不同的应用对读- -致性 和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读
”和“幻读”并不敏感,可能更关心数据并发访问的能力。
常看当前数据库的事务隔离级别: 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;
-
行锁定基本演示
session_1 session_2 set autocommit=0; 取消自动提交 set autocommit=0; update test_innodb_lock set Xxx where a=1;更新但是不提交,没有手写 commit update test_innodb_lock set Xxx where a=1;update session_2被阻塞,只能等待 commit;提交更新 解除阻塞,更新正常进行 commint; 下面试试 session_1 更新 a=1 下面试试 session_2 更新 a=9 update test_innodb_lock set Xxx where a=4; 修改成功 update test_innodb_lock set Xxx where a=9; 修改成功 commit; commit -
无锁引行锁升级为表锁
session_1 session_2 set autocommit=0; 取消自动提交 set autocommit=0; update test_innodb_lock set a=41 where b=4001; varchar类型不写单双引号,不提交 update test_innodb_lock set b='9003' where a=9; session_2被阻塞,只能等待 commit;提交更新 解除阻塞,更新正常,commit; -
间隙锁危害
session_1 session_2 update test_innodb_lock set b='0629' where a>1 and a<6; insert into test_innodb_lock value(2,'2000'); 阻塞产生暂时不能插入 commit; 阻塞解除,完成插入 commit; - 什么是间隙锁?
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键
值在条件范围内但并不存在的记录,叫做“间隙(GAP)” ,
InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁) 。 - 危害
因为Query执行过程中通过过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。
间隙锁有一个比较致命的弱点,就是当锁定一一个范围键值之 后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无
法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害
- 什么是间隙锁?
-
面试题:常考如何锁定一行
begin; select * from test_innodb_lock where a = 8 for update; #select Xxx for update锁定某一行后,其他的操作会被阻塞,直到锁定行的话提交commit; commit;
-
-
案例结论
Innodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些, 但是在整体并
发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,Innodb的整 体性能和MyISAM相比就会有比较明显的
优势了。但是,Innodb的行 级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让Innodb的整体性能表现不仅不能比MyISAM高
,甚至可能会更差。 -
行锁分析,优化建议
-
如何分析行锁定
通过检查 innodb_row_lock状态变量来分析系统上的行锁的争夺情况
show status like 'innodb_row_lock%';
-
参数说明
Innodb_row_lock_current_waits:当前正在等待锁定的数量
Innodb_row_lock_time :从系统启动到现在锁定总时间长度
Innodb_row_lock_time_avg :每次等待所花平均时间
Innodb_row_lock_time_max :从系统启动到现在等待最长的一次所花的时间
Innodb_row_lock_time_waits :系统启动后到现在总共等待的次数
-
对于这5个状态变量,比较重要的主要是
Innodb_row_lock_time_ avg (等待平均时长),
Innodb_row_lock_waits ( 等待总次数)
Innodb_row_ock_time (等待总时长)这三项。
尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着
手指定优化计划。
-
-
优化建议
- 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁。((varchar 不加 单双引号是重罪)
- 合理设计索引,尽量缩小锁的范围
- 尽可能较少检索条件,避免间隙锁
- 尽量控制事务大小,减少锁定资源量和时间长度
- 尽可能低级别事务隔离
4.2.3 页锁
- 开销和加锁时间界于表锁和行锁之间;会出现死锁,锁定粒度界于表锁和行锁之间,并发度一般。【了解即可】
五,主从复制
5.1 复制的基本原理
-
slave 会从master 读取binlog 来进行数据同步
-
三步骤+原理图
MySQL 复制过程分成三步:
master 将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events;
slave 将master 的binary log events 拷贝到它的中继日志(relay log);
slave 重做中继日志中的事件,将改变应用到自己的数据库中。MySQL 复制是异步的且串行化的
5.2 复制的基本原则
- 每个slave 只有一个master
- 每个slave 只能有一个唯一的服务器ID
- 每个master 可以有多个salve
5.3 复制的最大问题
因为发生多次IO,存在延时问题
5.4 一主一从常见配置
-
mysql 版本一致且后台以服务运行
-
主从都配置在 [mysqld]结点下,都是小写
主机修改my.ini 配置文件#[必须]主服务器唯一ID server-id=1 #[必须]启用二进制日志 log-bin=自己本地的路径/data/mysqlbin log-bin=D:/devSoft/MySQLServer5.5/data/mysqlbin #启用错误日志 log-err=自己本地的路径/mysqlerr log-err=D:/devSoft/MySQLServer5.5/data/mysqlerr #根目录 basedir="自己本地路径" basedir="D:/devSoftMySQLSserver5.5/" #临时目录 tmpdir="自己本地路径" tmpdir="D:/devSoftMySQLSserver5.5/" #数据目录 datadir="自己本地路径/data" datadir="D:/devSoftMySQLSserver5.5/" #主机,读写都可以 read-only=0 #设置不要复制的数据库 binlog-ignore-db=mysql #设置需要复制的数据库 binlog-do-db=需要复制的主数据库名字 设置logbin 格式 binlog_format=STATEMENT(默认)
-
从机配置文件修改my.cnf 的[mysqld]栏位下
#从机服务id server-id = 2 #注意my.cnf 中有server-id = 1 #设置中继日志 relay-log=mysql-relay
-
因修改过配置文件,请主机+从机都重启后台mysql 服务
主机从机都关闭防火墙、安全工具(腾讯管家等) -
在 windows 主机上建立账户并授权 slave
#创建用户,并授权 GRANT REPLICATION SLAVE ON *.* TO '备份账号'@'从机器数据库IP' IDENTIFIED BY '123456'; flush privileges; #查询 master的状态 show master status; #记录下 file和 position的值 #执行完此步骤不要再操作主服务器 MySQL,防止主服务器状态值变化
-
在 Linux 从机上配置需要复制的主机
#查询master 的状态 CHANGE MASTER TO MASTER_HOST='主机IP',MASTER_USER='创建用户名',MASTER_PASSWORD='创建的密码' ,MASTER_LOG_FILE='File 名字',MASTER_LOG_POS=Position 数字; #(如果之前做过同步,请先停止 sotp slave;) #启动从服务器复制功能 start slave; show slave status\G; /* 下面两个参数都是Yes,则说明主从配置成功! Slave_IO_Running: Yes Slave_SQL_Running: Yes */
-
主机新建库,新建表,insert记录,从机复制
-
如何停止服务复制功能?
stop slave;