一、MySQL的架构介绍
1.1 MySQL简介
概述
- MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司。
- MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活
性。 - Mysq|是开源的,所以你不需要支付额外的费用。
- MysqI支持大型的数据库。可以处理拥有上千万条记录的大型数据库。
- MySQL使用标准的SQL数据语言形式。
- Mysq|可以允许于多个系统上,并且支持多种语言。这些编程语言包括C、C++、 Python、 Java、 Perl、 PHP、Eiffel、 Ruby和Tc|等 。
- Mysq|对PHP有很好的支持,PHP是 目前最流行的Web开发语言。
- MySQL支持大型数据库,支持5000万条记录的数据仓库,32位 系统表文件最大可支持4GB, 64位系统支持最大的表文件为8TB。
- Mysq|是可以定制的,采用了GPL协议,你可以修改源码来开发自己的Mysq|系统。
高级MySQL
高级MySQL包括:MySQL内核、sql优化攻城狮、mysql服务器的优化、各种参数常量设定、查询语句优化、主从复制、软硬件升级、容灾备份、sql编程
1.2 MySQL Linux版的安装
参考此篇文章
1.3 MySQL 配置文件
二进制日志log-bin
# Replication Master Server (default)
# binary logging is required for replication
log-bin=mysql-bin
错误日志log-error
默认是关闭的。记录严重的警告和错误信息,每次启动和关闭的详细信息等
查询日志log
默认关闭,记录查询的sql语句,如果开启会减低mysqI的整体性能,因为记录日志也是需要消耗系统资源的
数据文件
- frm文件 —— 存放表结构的
- myd文件 —— 存放表数据
- myi文件 —— 存放表索引
1.4 MySQL的逻辑架构介绍
总体概览
- 和其它数据库相比,MySQL有 点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
-
连接层
最上层是一些客户端和连接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于tcp/ip的通信。主要完成-些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
-
服务层
第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化及部分内置函数的执行。所有跨存储引擎的功能也在这一层实现, 如过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定查询表的顺序,是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存。如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
-
引擎层
存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这 样我们可以根据自己的实际需要进行选取。后面介绍MyISAM和InnoDB
-
存储层
数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
1.5 MySQL存储引擎
查看存储引擎命令
mysql> show engines;
mysql> show variables like '%storage_engine%';
MyISAM和InnoDB
阿里、淘宝用的存储引擎
- Percona为MySQL数据库服务器进行了改进,在功能和性能上较MySQL有着很显著的提升。该版本提升了在高负载情况下的InnoDB的性能、为DBA提供一些非常有用的性能诊断工具;另外有更多的参数和命令来控制服务器行为。
- 该公司新建了一款存储引擎叫xtradb完全可以替代innodb,并且在性能和并发上做得更好,
- 阿里巴巴大部分mysq|数据库其实使用的percona的原型加以修改。
- AliSql+AliRedis
二、索引优化分析
2.1 SQL性能下降的原因
性能下降SQL慢、执行时间长、等待时间长
原因:
-
查询语句写的烂 -
索引失效
-
单值索引 -
复合索引 -
关联查询太多join(设计缺陷或不得已的需求) -
服务器调优及各个参数设置(缓冲、线程数等)
2.2 常见通用的Join查询
SQL的执行顺序
-
手写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>
-
机器读取的SQL FROM
<left_table> <join_type>
JOIN <right_table> ON <join_condition>
WHERE
<where_condition>
GROUP BY
<group_by_list>
HAVING
<having_condition>
SELECT
DISTINCT <select_list>
ORDER BY
<order_by_condition>
LIMIT <limit_number>
-
总结
七种Join图
测试7种Join
-
建表sql USE db01;
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)
) 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);
-
inner join 只查询两个共有的,其余的不显示 -
left join 左表都查询,右表没有对应的就置空 -
right join 右表都查询,左表没有对应的就置空 -
right join where left.id is null 查询右表独有的数据 -
left join where right.id is null 查询左表独有的数据 -
full outer join 说明,此连接对mysql无效,对Oracle有用,但是为了达到效果,还是要编写sql
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;
-
只显示两个独有的 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;
2.3 索引简介
2.3.1 索引是什么
-
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构就是索引。 -
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上 -
一种索引方式示例: -
我们平常所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一 索引默认都是使用B+树索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈稀索引(hash index)等。
2.3.2 索引的优势与劣势
优势
- 类似于大学图书馆建书目索引,提高数据检索效率,降低数据库的IO成本
- 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗
劣势
- 实际上索引也是一张表,该表保存了主键和索引的字段,并指向实体表的记录,索引索引列也是要占用空间的
- 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE 和DELETE.因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息
- 索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句
2.3.3 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 table_name;
-
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,用于全文索引。
2.3.4 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间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的I0)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内有,发生第二次IO,29在26和30之间, 锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。
真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
【其他类型索引】
- Hash索引
- full-text全文索引
- R-Tree索引
2.3.5 MySQL创建索引的情景
哪些情况需要创建索引
- 主键自动建立唯一索引
- 频繁作为查询条件的字段应该创建索引
- 查询中与其它表关联的字段,外键关系建立索引
- 频繁更新的字段不适合创建索引,因为每次更新不单单是更新了记录,还会更新索引,加重IO负担
- where条件里用不到的字段不创建索引
- 单键/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
- 查询中统计或者分组字段
哪些情况不要创建索引
- 表记录太少
- 经常增删改的表
- Why:提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。
- 数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
2.4 性能分析
MySQL Query Optimizer
- MysqI中有专门负责优化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来查看系统的性能状态
2.4.1 Explain
Explain是什么(查看执行计划)
- 使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。
Explain的作用
- 表的读取顺序
- 数据读取操作的操作类型
- 哪些索引可以使用
- 哪些索引被实际使用
- 表之间的应用
- 每张表有多少行被优化器查询
Explain的用法
用法:Explain + SQL语句
执行计划包含的信息
执行计划包含的信息的字段解释
-
id:select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序。分三种情况:
-
select_type:查询的类型,主要用于区别普通查询、联合查询、子查询等的复杂查询
- SIMPLE:简单的select查询,查询中不包含子查询或者UNION
- PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为
- SUBQUERY:在SELECT或WHERE列表中包含子查询
- DERIVED:在FROM列表中包含的子查询被标记为DERIVED(衍生)。MySQL会递归执行这些子查询,把结果放在临时表里
- UNION:若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED
- UNION RESULT:从UNION表中获取结果的SELECT
-
table:显示这一行的数据是关于哪些表的 -
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
-
system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,这个也可以忽略不计 -
const:表示通过索引一次就找到了,const用于比较primary key或则unique索引。因为只匹配一行数据,所以很快。如将主键置于where列表中,MySQL就能将该查询转换为一个常量 -
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描 -
ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体 -
range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引。一般就是在你的where语句中出现了between、<、>、in等的查询。这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不会扫描全部索引 -
index:Full Index Scan,index与All区别为index类型只遍历索引树。这通常比All快,因为索引文件通常比数据文件小。(也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读的) -
all:Full Table Scan,将遍历全表以找到匹配的行。 -
一般来说,得保证查询至少达到range级别,最好能达到ref -
possible_keys:显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出。但不一定被查询实际使用 -
key:实际使用的索引。如果为NULL,则没有使用索引。查询中若使用了覆盖索引,则该索引仅出现在key列表中,不会出现在possible_keys列表中。(覆盖索引:查询的字段与建立的复合索引的个数一一吻合) -
key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好。 key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的 -
ref:显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。查询中与其它表关联的字段,外键关系建立索引 -
rows:根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数 -
Extra:包含不适合在其他列中显示但十分重要的额外信息
-
Using filesort:说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作成为“文件排序” -
Using temporary:使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序order by和分组查询group by -
Using index:表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!如果同时出现using where,表明索引被用来执行索引键值的查找;如果没有同时出现using where,表明索引用来读取数据而非执行查找动作
-
覆盖索引 -
Using where:表明使用了where过滤 -
Using join buffer:使用了连接缓存 -
impossible where:where子句的值总是false,不能用来获取任何元组。(查询语句中where的条件不可能被满足,恒为False) -
select tables optimized away:在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化 -
distinct:优化distinct操作,在找到第一匹配的元组后即停止找相同值的动作
热身Case
写出下列sql的执行顺序
结果:
2.5 索引优化
2.5.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,`category_id` INT(10) UNSIGNED NOT NULL , `views` INT(10) UNSIGNED NOT NULL , `comments` INT(10) UNSIGNED NOT NULL,`title` VARBINARY(255) NOT NULL,`content` TEXT NOT NULL);INSERT INTO `article`(`author_id`,`category_id` ,`views` ,`comments` ,`title` ,`content` )VALUES(1,1,1,1,'1','1'),(2,2,2,2,'2','2'),(3,3,3,3,'3','3'); SELECT * FROM ARTICLE;
案例
-
查询category_ id 为1且comments大于1的情况下,views最多的article_ id。 -- 查询sqlSELECT 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>1 ORDER BY views DESC LIMIT 1;
结论:很显然,type是ALL,即最坏的情况。Extra里还出现了Using filesort,也是最坏的情况。优化是必须的。 -
开始优化
-
添加索引
结论:type变成了range,这是可以忍受的。但是extra里使用Using filesort 仍是无法接受的。 疑问:我们已经建立了索引,为啥没用呢? 解答:这是因为按照BTree索引的工作原理,先排序category_id,如果遇到相同的category_ id则再排序comments,如果遇到相同的comments则再排序views。当comments字段在联合索引里处于中间位置时,因comments> 1条件是-一个范围值(所谓range),MySQL无法利用索引再对后面的views部分进行检索,即range类型查询字段后面的索引无效。 -
删除第一次的索引,第二次新建索引,这次的索引不包含range类型查询所涉及的字段
结论:可以看到,type变成了ref,Extra中的Using filesort也消失了,结果还是非常理想的。 -
删除测试的索引
两表
建表sql
CREATE TABLE IF NOT EXISTS `class`(`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,`card` INT (10) UNSIGNED NOT NULL);CREATE TABLE IF NOT EXISTS `book`(`bookid` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,`card` INT (10) UNSIGNED NOT NULL);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)));
案例
结论:type有ALL,不是很理想
-
添加索引优化 alter table class add index Y(card);
-
explain性能分析 结论:可以看到第二行的type变为ref,rows也变成了优化比较明显。 原因:这是由于左连接特性决定的,LEFT JOIN条件用于确定如何从右表搜索行,左边一定都有,所有右边是我们的关键点,所以所有要在右表建立。同理,右连接的关键在左边,所以索引要在左边建立。 总结:左连接建右表,右连接建左表。理由:以左连接为例,左表的信息全都有,所以右表需要查找,所以建立右表index
三表
建表SQL(在二表的基础上,清除所有索引)
CREATE TABLE IF NOT EXISTS `phone`(`phoneid` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,`card` INT (10) UNSIGNED NOT NULL)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)));
案例
explain select * from class left join book on book.card = class.card left join phone on book.card = phone.card;
分析:sql都是左连接,class是全部加载的,索引book需要一个索引,继续左连接phone,phone是右表也需要一个索引
实施:
create index Y on phone(card); create index Y on book(card); explain select * from class left join book on book.card = class.card left join phone on book.card = phone.card;
结论:后两行的type都是ref且总rows优化很好,效果不错。因此所有最好设置在需要经常查询的字段中。
Join语句的优化
- 尽可能减少Join语句中的NestedL oop的循环总次数; “永远用小结果集驱动大的结果集”
- 优先优化嵌套循环的内层循环
- 保证Join语句中被驱动表上join条件字段已经被索引
- 当无法保证被驱动表的Join条件字段被索引且内存资源充足的前提下,不要太吝惜Join Buffer的设置
2.5.2 索引失效(应该避免)
建表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`);
索引失效案例
全值匹配我最爱
建立几个复合索引字段,最好就用上几个字段。且按照顺序来用
最佳左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。最前缀法则,必须有车头,中间车厢不能断
不在索引列上作任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
存储引擎不能使用索引中范围条件右边的列
尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select
mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
is null,is not null 也无法使用索引
like以通配符开头(‘%abc…’)mysql索引失效会变成全表扫描的操作
问题:解决like ‘%字符串%’时索引不被使用的方法
案例
建表SQL
CREATE TABLE tbl_user(`id` INT(11) NOT NULL AUTO_INCREMENT,`name` VARCHAR(20) DEFAULT NULL,`age`INT(11) DEFAULT NULL,`email` VARCHAR(20) DEFAULT NULL,PRIMARY KEY(`id`))ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;insert into tbl_user(NAME,age,email) values('1aa1',21,'b@163.com');insert into tbl_user(NAME,age,email) values('2aa2',222,'a@163.com');insert into tbl_user(NAME,age,email) values('3aa3',265,'c@163.com');insert into tbl_user(NAME,age,email) values('4aa4',21,'d@163.com');
经过测试得到结论:利用覆盖索引解决两边%的优化问题
explain select * from tbl_user where name like '%aa%';explain select id,name,age from tbl_user where name like '%aa%';explain select id,name,age,email from tbl_user where name like '%aa%';
字符串不加单引号会索引失效
结论:都能查询出结果,但是不带单引号的会导致索引失效,原因是不带单引号的mysql底层会做一个隐式的类型转换。
少用or,用它连接是会索引失效
小结
全值匹配我最爱,最左前缀要遵守 带头大哥不能丢,中间兄弟不能断 索引列上少计算,范围之后全失效 like百分写最右, 覆盖索引不写星 不等空值还有or,索引失效要少用 var引号不能丢,SQL高级也不难
小总结:
2.5.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','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);
分析sql
explain select * from test03 where c1 = 'a1';explain select * from test03 where c1 = 'a1' and c2 = 'a2';explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c3 = 'a3';explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c3 = 'a3' and c4 = 'a4';
结论:当查询为常量查询,且查询顺序与索引创建的顺序不一样,由图可知,同样也使用了索引。原因:MySQL在服务层的时候会自动把SQL进行优化,会根据我们所有创建的顺序来优化查询条件的顺序。
explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c4 > 'a4' and c3 = 'a3';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' order by c3;explain select * from test03 where c1 = 'a1' and c2 = 'a2' order by c3;
结论:c3的作用在排序而不是查找,用到了但是没有统计在结果中
explain select * from test03 where c1 = 'a1' and c2 = 'a2' order by c4;
结论:出现了Using filesort,原因:索引的使用跳过了c3,自己创建的索引不能达到需求,索引有了文件排序
explain select * from test03 where c1 = 'a1' and c5 = 'a5' order by c2,c3;
结论:只用c1一个字段索引,但是c2、c3用于排序且按照索引创建顺序来使用,所以无filesort
explain select * from test03 where c1 = 'a1' and c5 = 'a5' order by c3,c2;
结论:出现了filesort,我们建的索引是1234,它没有按照顺序来,3和2颠倒了,所以出现filesort
explain select * from test03 where c1 = 'a1' and c2 = 'a2' and c5 = 'a5' order by c3,c2;
结论:本例有常量c2的情况,因此排序就相当于order by c3,常量,所以没有出现filesort的情况
explain select * from test03 where c1 = 'a1' and c4 = 'a4' group by c2,c3;explain select * from test03 where c1 = 'a1' and c4 = 'a4' group by c3,c2;
结论:第一句sql用到了3个索引,一个用于查询,2个用于排序
? 第二局sql 用到了一个索引,用于查询,用于排序的字段与索引创建的顺序不同,所以出现了filesort
其中group by基本上都需要排序的,可能还会有临时表产生
- 定值、范围还是排序,一般order by是给个范围
- group by基本上都需要进行排序,会有临时表产生
- like后面看是否常量开头,是则使用索引,后续也能用,不是就断了
|