1. MySQL的架构介绍
1.1 MySQL简介:
MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司。
MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
Mysql是开源的,所以你不需要支付额外的费用。
Mysql支持大型的数据库。可以处理拥有上千万条记录的大型数据库。
MySQL使用标准的SQL数据语言形式。
Mysql可以允许于多个系统上,并且支持多种语言。这些编程语言包括C、C++、Python、Java、Perl、PHP、Eiffel、Ruby和Tcl等。
Mysql对PHP有很好的支持,PHP是目前最流行的Web开发语言。
MySQL支持大型数据库,支持5000万条记录的数据仓库,32位系统表文件最大可支持4GB,64位系统支持最大的表文件为8TB。
Mysql是可以定制的,采用了GPL协议,你可以修改源码来开发自己的Mysql系统(其实阿里巴巴的“去IOE”中,使用的数据库就是自己开发的阿里版的MySQL)。
1.2 一个高级的J2E工程师需要面对MySQL要有那些基本功夫呢?
首先,你要逐步理解mysql内核;其次,sql优化、mysql服务器的优化和各种参数常量设定与各种参数常量设定。然后,要会主从复制、容灾备份、sql编程和相应的软硬件升级。当然,这只是一个高级的J2E工程师面对MySQL要有的基本功夫。因为完整的mysql优化需要很深的功底,大公司甚至有专门的DBA工程师进行负责。
2. 工欲善其事必先利其器(Mysql Linux版的安装)
我个人是比较推荐去官网下载软件和对应的文档的。官网下载地址:http://dev.mysql.com/downloads/mysql/
MySQL Server
MySQL Client
下载完成以后,准备安装以前首先,检查当前系统是否安装过mysql:
查询命令:rpm -qa|grep -i mysql 删除命令:rpm -e RPM软件包名(该名字是上一个命令查出来的名字)
在安装过程中,安装mysql服务端(注意提示),然后再安装mysql客户端。安装完成以后,可以查看MySQL安装时创建的mysql用户和mysql组:
安装完毕以后,我们可以根据自己的需要来进行mysql服务的启和停:
1 # ps -ef|grep mysql
2 # service mysql start
3 # service mysql stop
mysql服务启动后,开始连接(注意这里,因为MySQL默认没有密码,所以这里我们没有输入密码就直接连上了):
所以我们在按照安装Server时,根据提示修改登录密码即可。
如果我们经常要使用MySQL来进行开发,就需要将其设置为自启动mysql服务:
只要是搞开发过来的,肯定遇到过乱码之类的问题。所以怎么解决呢?
1 查看字符集
1 show variables like 'character%';
2 show variables like '%char%';
看看出现的结果:
默认的是客户端和服务器都用了latin1,所以会乱码。
2 修改
1 [client]
2 #password = your_password
3 port = 3306
4 socket = /var/lib/mysql/mysql.sock
5 default-character-set=utf8
6
7 # The MySQL server
8 [mysqld]
9 port = 3306
10 character_set_server=utf8
11 character_set_client=utf8
12 collation-server=utf8_general_ci
13 socket = /var/lib/mysql/mysql.sock
14 skip-external-locking
15 key_buffer_size = 384M
16 max_allowed_packet = 1M
17 table_open_cache = 512
18 sort_buffer_size = 2M
19 read_buffer_size = 2M
20 read_rnd_buffer_size = 8M
21 myisam_sort_buffer_size = 64M
22 thread_cache_size = 8
23 query_cache_size = 32M
24 # Try number of CPU's*2 for thread_concurrency
25 thread_concurrency = 8
26
27 [mysql]
28 no-auto-rehash
29 default-character-set=utf8
3 重启mysql
1 service mysql stop;
2 service mysql start;
4 重新连接后重新create databse并使用新建库,然后再重新建表试试
5 还是乱码的话就设值init_connect='SET NAMES utf8' ##设定连接mysql是UTF8编码
在windows系统下我们可以很快找到MySQL的安装路径,那在linux下呢?
这其实也很简单,在linux下查看安装目录的命令如下:
1 ps -ef|grep mysql
3. Mysql主要配置文件含义及如何配置
3.1 二进制日志log-bin (主从复制)
3.2 错误日志log-error
默认是关闭的,记录严重的警告和错误信息,每次启动和关闭的详细信息等。
3.3 查询日志log
默认关闭,记录查询的sql语句,如果开启会减低mysql的整体性能,因为记录日志也是需要消耗系统资源的
3.4 数据文件
frm文件:存放表结构
myd文件:存放表数据
myi文件:存放表索引
4. Mysql逻辑架构简单聊聊
4.1 总体概览
和其它数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,
插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
4.1.1.连接层
最上层是一些客户端和连接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于tcp/ip的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
4.1.2.服务层
第二层架构主要完成核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化及部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定查询表的顺序,是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存。如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
4.1.3.引擎层
存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取(MyISAM和InnoDB)。
4.1.4.存储层
数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
4.2 查询说明
首先,mysql的查询流程大致是:
mysql客户端通过协议与mysql服务器建连接,发送查询语句,先检查查询缓存,如果命中,直接返回结果,否则进行语句解析有一系列预处理,比如检查语句是否写正确了,然后是查询优化(比如是否使用索引扫描,如果是一个不可能的条件,则提前终止),生成查询计划,然后查询引擎启动,开始执行查询,从底层存储引擎调用API获取数据,最后返回给客户端。怎么存数据、怎么取数据,都与存储引擎有关。然后,mysql默认使用的BTREE索引,并且一个大方向是,无论怎么折腾sql,至少在目前来说,mysql最多只用到表中的一个索引。
5. Mysql存储引擎
1 #看你的mysql现在已提供什么存储引擎: mysql> show engines;
2 #看你的mysql当前默认的存储引擎: 5 mysql> show variables like '%storage_engine%';
6. 阿里巴巴、淘宝用哪个???
Percona 为 MySQL 数据库服务器进行了改进,在功能和性能上较 MySQL 有着很显著的提升。该版本提升了在高负载情况下的 InnoDB 的性能、为 DBA 提供一些非常有用的性能诊断工具;另外有更多的参数和命令来控制服务器行为。
该公司新建了一款存储引擎叫xtradb完全可以替代innodb,并且在性能和并发上做得更好,阿里巴巴大部分mysql数据库其实使用的percona的原型加以修改。
7. 影响mysql的性能因素
7.1 业务需求对mysql的影响(合适合度)
举一个例子吧:
【要求】
统计对该产品的评价帖子,要实时的!
【问题】
假如这个数据量很小OK,可以实时查询,假如越来越多(例如淘宝店),积累了好多万客户评论帖子
1 select count(*)对于刚开始数量很小时可以,如果数据增大已经有千万级的,一个查询就哭吧,成为系统性能瓶颈。
2 你正在统计时候也有买家卖家修改增删评论,你不能要求用户说我统计的时候你们不许动。
【解决】
单独一个表,单独一个字段来保存这个帖子数目。 每一个买家新增一条评论就是需要更新一个这个数字,各买家是多个(高并发)不定时的在发布帖子,实时修改更新后再统计select count(*)。 就算我们使用的是Innodb存储引擎,一个update数字他就是行锁,高并发的瓶颈出现了
【结论】
很多的统计信息都是准实时的而不是实时统计,网站的一些数量信息、分页信息、排序信息、点击率信息等等一般都不是实时的而是准实时的。
7.2 存储定位对mysql的影响
7.3 Schema设计对系统的性能影响
两个尽量原则:尽量减少对数据库访问的请求;尽量减少无用数据的查询请求
7.4 硬件环境对系统性能的影响
7.4.1 典型OLTP应用系统
什么是OLTP:OLTP即联机事务处理,就是我们经常说的关系数据库,意即记录即时的增、删、改、查,就是我们经常应用的东西,这是数据库的基础
对于各种数据库系统环境中大家最常见的OLTP系统,其特点是并发量大,整体数据量比较多,但每次访问的数据比较少,且访问的数据比较离散,活跃数据占总体数据的比例不是太大。对于这类系统的数据库实际上是最难维护,最难以优化的,对主机整体性能要求也是最高的。因为不仅访问量很高,数据量也不小。
针对上面的这些特点和分析,我们可以对OLTP的得出一个大致的方向。
虽然系统总体数据量较大,但是系统活跃数据在数据总量中所占的比例不大,那么我们可以通过扩大内存容量来尽可能多的将活跃数据cache到内存中;虽然IO访问非常频繁,但是每次访问的数据量较少且很离散,那么我们对磁盘存储的要求是IOPS 【 (Input/Output Operations Per Second),即每秒进行读写(I/O)操作的次数】表现要很好,吞吐量是次要因素;并发量很高,CPU每秒所要处理的请求自然也就很多,所以CPU处理能力需要比较强劲;虽然与客户端的每次交互的数据量并不是特别大,但是网络交互非常频繁,所以主机与客户端交互的网络设备对流量能力也要求不能太弱。
7.4.2 典型OLAP应用系统
用于数据分析的OLAP系统的主要特点就是数据量非常大,并发访问不多,但每次访问所需要检索的数据量都比较多,而且数据访问相对较为集中,没有太明显的活跃数据概念。
什么是OLAP:OLAP即联机分析处理,是数据仓库的核心部心,所谓数据仓库是对于大量已经由OLTP形成的数据的一种分析型的数据库,用于处理商业智能、决策支持等重要的决策信息;数据仓库是在数据库应用到一定程序之后而对历史数据的加工与分析
基于OLAP系统的各种特点和相应的分析,针对OLAP系统硬件优化的大致策略如下:
数据量非常大,所以磁盘存储系统的单位容量需要尽量大一些;
单次访问数据量较大,而且访问数据比较集中,那么对IO系统的性能要求是需要有尽可能大的每秒IO吞吐量,所以应该选用每秒吞吐量尽可能大的磁盘;
虽然IO性能要求也比较高,但是并发请求较少,所以CPU处理能力较难成为性能瓶颈,所以CPU处理能力没有太苛刻的要求;
虽然每次请求的访问量很大,但是执行过程中的数据大都不会返回给客户端,最终返回给客户端的数据量都较小,所以和客户端交互的网络设备要求并不是太高;
此外,由于OLAP系统由于其每次运算过程较长,可以很好的并行化,所以一般的OLAP系统都是由多台主机构成的一个集群,而集群中主机与主机之间的数据交互量一般来说都是非常大的,所以在集群中主机之间的网络设备要求很高。
8. 查询与索引优化分析
8.1 为什么要优化?
现象:性能下降SQL慢、执行时间长、等待时间长。
原因:
查询语句写的烂
索引失效(单值、复合)
关联查询太多join(设计缺陷或不得已的需求)
关联查询太多join(设计缺陷或不得已的需求)
9. 常见通用的Join查询
9.1 SQL执行顺序
手写:
机读:
结论:
9.2 Join图
1 A、B两表共有
select * from tbl_emp a inner join tbl_dept b on a.deptId = b.id;
2 A、B两表共有+A的独有
select * from tbl_emp a left join tbl_dept b on a.deptId = b.id;
3 A、B两表共有+B的独有
select * from tbl_emp a right join tbl_dept b on a.deptId = b.id;
4 A的独有
select * from tbl_emp a left join tbl_dept b on a.deptId = b.id where b.id is null;
5 B的独有
select * from tbl_emp a right join tbl_dept b on a.deptId = b.id where a.deptId is null; #B的独有
6 AB全有
#MySQL Full Join的实现 因为MySQL不支持FULL JOIN,下面是替代方法
#left join + union(可去除重复数据)+ right join
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
7 A的独有+B的独有
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;
9.4 建表SQL
1 CREATE TABLE `tbl_dept` (
2 `id` INT(11) NOT NULL AUTO_INCREMENT,
3 `deptName` VARCHAR(30) DEFAULT NULL,
4 `locAdd` VARCHAR(40) DEFAULT NULL,
5 PRIMARY KEY (`id`)
6 ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
7
8 CREATE TABLE `tbl_emp` (
9 `id` INT(11) NOT NULL AUTO_INCREMENT,
10 `name` VARCHAR(20) DEFAULT NULL,
11 `deptId` INT(11) DEFAULT NULL,
12 PRIMARY KEY (`id`),
13 KEY `fk_dept_id` (`deptId`)
14 #CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `tbl_dept` (`id`)
15 ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
16
17
18
19 INSERT INTO tbl_dept(deptName,locAdd) VALUES('RD',11);
20 INSERT INTO tbl_dept(deptName,locAdd) VALUES('HR',12);
21 INSERT INTO tbl_dept(deptName,locAdd) VALUES('MK',13);
22 INSERT INTO tbl_dept(deptName,locAdd) VALUES('MIS',14);
23 INSERT INTO tbl_dept(deptName,locAdd) VALUES('FD',15);
24
25
26 INSERT INTO tbl_emp(NAME,deptId) VALUES('z3',1);
27 INSERT INTO tbl_emp(NAME,deptId) VALUES('z4',1);
28 INSERT INTO tbl_emp(NAME,deptId) VALUES('z5',1);
29
30 INSERT INTO tbl_emp(NAME,deptId) VALUES('w5',2);
31 INSERT INTO tbl_emp(NAME,deptId) VALUES('w6',2);
32
33 INSERT INTO tbl_emp(NAME,deptId) VALUES('s7',3);
34
35 INSERT INTO tbl_emp(NAME,deptId) VALUES('s8',4);
36
37 INSERT INTO tbl_emp(NAME,deptId) VALUES('s9',51);
38
10. 什么是索引
10.1 是什么
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。可以得到索引的本质:索引是数据结构。
索引的目的在于提高查询效率,可以类比字典,如果要查“mysql”这个单词,我们肯定需要定位到m字母,然后从下往下找到y字母,再找到剩下的sql。如果没有索引,那么你可能需要a----z,如果我想找到Java开头的单词呢?或者Oracle开头的单词呢?是不是觉得如果没有索引,这个事情根本无法完成?所以你可以简单理解为“排好序的快速查找结构”。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,
这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图就是一种可能的索引方式示例:
左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址
为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。我们平常所说的索引,如果没有特别指明,都是指B+树结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈稀索引(hash index)等。
10.2 优缺点
优点:通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗;提高数据检索的效率,降低数据库的IO成本
缺点:实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的;虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息;索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句
10.3 mysql索引分类
单值索引:即一个索引只包含单个列,一个表可以有多个单列索引。
唯一索引:索引列的值必须唯一,但允许有空值。
复合索引:即一个索包含多个列。
基本语法:
创建:
1. CREATE [UNIQUE ] INDEX indexName ON mytable(columnname(length)); -->如果是CHAR,VARCHAR类型,length可以小于字段实际长
度;如果是BLOB和TEXT类型,必须指定length。
2. ALTER mytable ADD [UNIQUE ] INDEX [indexName] ON (columnname(length))
删除:
DROP INDEX [indexName] ON mytable;
查看:
SHOW INDEX FROM table_name
使用ALTER命令(有四种方式来添加数据表的索引):
1. ALTER TABLE tbl_name ADD PRIMARY KEY (column_list): 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
2. ALTER TABLE tbl_name ADD UNIQUE index_name (column_list): 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
3. ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出现多次。
4. ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT ,用于全文索引。
10.4 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,显然成本非常非常高。
10.5 哪些情况需要创建索引
1. 主键自动建立唯一索引
2. 频繁作为查询条件的字段应该创建索引
3. 查询中与其它表关联的字段,外键关系建立索引
4. 频繁更新的字段不适合创建索引(因为每次更新不单单是更新了记录还会更新索引,加重了IO负担)
5. Where条件里用不到的字段不创建索引
6. 单键/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
7. 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
8. 查询中统计或者分组字段
10.6 哪些情况不要创建索引
1. 表记录太少
2. 经常增删改的表(因为提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件)
3. 数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
11. MySql Query Optimizer
11.1 Mysql中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划(他认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间)
11.2 当客户端向MySQL 请求一条Query,命令解析器模块完成请求分类,区别出是 SELECT 并转发给MySQL Query Optimizer时,MySQL Query Optimizer 首先会对整条Query进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对 Query 中
的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析 Query 中的 Hint 信息(如果有),看显示Hint信息是否可以完全确定该Query 的执行计划。如果没有 Hint 或Hint 信息还不足以完全确定执行计划,则会读取所涉及对象的统计信
息,根据 Query 进行写相应的计算分析,然后再得出最后的执行计划。
11.3 MySQL常见瓶颈
CPU:CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候;
IO:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候;
服务器硬件的性能瓶颈:可以通过(Linux命令)top,free, iostat和vmstat命令来查看系统的性能状态。
11.4 EXPLAIN关键字
使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。通过它可以得到表的读取顺序、数据读取操作的操作类型、哪些索引可以使用、哪些索引被实际使用、表之间的引用、每张表有多少行被优化器查询。
那怎么用呢?
其实,Explain + SQL语句即可。
上图各字段解释(执行计划包含的信息)如下:
1. id
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序。
三种情况:
1.1. id相同,执行顺序由上至下
1.2. id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行。
1.3. id相同不同,同时存在
id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行。(衍生 = DERIVED)
2. 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。
3. table
显示这一行的数据是关于哪张表的
4. 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就能将该查询转换为一个常量;
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。
5. possible_keys
显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
6. key
实际使用的索引。如果为NULL,则没有使用索引。查询中若使用了覆盖索引,则该索引仅出现在key列表中(如下图所示:)
7. key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好。key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的
8. ref
显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值(示例如下:)
由key_len可知t1表的idx_col1_col2被充分使用,col1匹配t2表的col1,col2匹配了一个常量,即 'ac'
9. rows
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数
10. Extra
包含不适合在其他列中显示但十分重要的额外信息
Using filesort :
说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序”。
Using temporary:
使了用临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。
USING index:
表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!如果同时出现using where,表明索引被用来执行索引键值的查找;如果没有同时出现using where,表明索引用来读取数据而非执行查找动作。
覆盖索引(Covering Index):
覆盖索引(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(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
distinct:
优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作。
举个例子,热热身吧!讲了那么多,是不是有些晕了。没关系,让我们通过一个小例子看看。
第一行(执行顺序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 where other_column=''】
第三行(执行顺序3):select列表中的子查询select_type为subquery,为整个查询中的第二个select。【select id from t3】
第四行(执行顺序1):select_type为union,说明第四个select是union里的第二个select,最先执行【select name,id from t2】
第五行(执行顺序5):代表从union的临时表中读取行的阶段,table列的<union1,4>表示用第一个和第四个select的结果进行union操作。【两个结果union操作】
12. 索引优化
12.1 索引分析
1. 单表
建表SQL:
1 CREATE TABLE IF NOT EXISTS `article` (
2 `id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
3 `author_id` INT(10) UNSIGNED NOT NULL,
4 `category_id` INT(10) UNSIGNED NOT NULL,
5 `views` INT(10) UNSIGNED NOT NULL,
6 `comments` INT(10) UNSIGNED NOT NULL,
7 `title` VARBINARY(255) NOT NULL,
8 `content` TEXT NOT NULL
9 );
10
11 INSERT INTO `article`(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES
12 (1, 1, 1, 1, '1', '1'),
13 (2, 2, 2, 2, '2', '2'),
14 (1, 1, 3, 3, '3', '3');
15
16 SELECT * FROM article;
案例分析:
1 #查询 category_id 为 1 且 comments 大于 1 的情况下,views 最多的 article_id。
2 EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
3
4 #结论:很显然,type 是 ALL,即最坏的情况。Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的。
5
6
7 #开始优化:
8 # 1.1 新建索引+删除索引
9 #ALTER TABLE `article` ADD INDEX idx_article_ccv ( `category_id` , `comments`, `views` );
10 create index idx_article_ccv on article(category_id,comments,views);
11 DROP INDEX idx_article_ccv ON article
12
13
14 # 1.2 第2次EXPLAIN
15 EXPLAIN SELECT id,author_id FROM `article` WHERE category_id = 1 AND comments >1 ORDER BY views DESC LIMIT 1;
16 EXPLAIN SELECT id,author_id FROM `article` WHERE category_id = 1 AND comments =3 ORDER BY views DESC LIMIT 1
17 #结论:
18 #type 变成了 range,这是可以忍受的。但是 extra 里使用 Using filesort 仍是无法接受的。
19 #但是我们已经建立了索引,为啥没用呢?
20 #这是因为按照 BTree 索引的工作原理,
21 # 先排序 category_id,
22 # 如果遇到相同的 category_id 则再排序 comments,如果遇到相同的 comments 则再排序 views。
23 #当 comments 字段在联合索引里处于中间位置时,
24 #因comments > 1 条件是一个范围值(所谓 range),
25 #MySQL 无法利用索引再对后面的 views 部分进行检索,即 range 类型查询字段后面的索引无效。
26
27
28 # 1.3 删除第一次建立的索引
29 DROP INDEX idx_article_ccv ON article;
30
31 # 1.4 第2次新建索引
32 #ALTER TABLE `article` ADD INDEX idx_article_cv ( `category_id` , `views` ) ;
33 create index idx_article_cv on article(category_id,views);
34
35 # 1.5 第3次EXPLAIN
36 EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
37 #结论:可以看到,type 变为了 ref,Extra 中的 Using filesort 也消失了,结果非常理想。
38 DROP INDEX idx_article_cv ON article;
2. 两表
建表SQL:
1 CREATE TABLE IF NOT EXISTS `class` (
2 `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
3 `card` INT(10) UNSIGNED NOT NULL,
4 PRIMARY KEY (`id`)
5 );
6 CREATE TABLE IF NOT EXISTS `book` (
7 `bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
8 `card` INT(10) UNSIGNED NOT NULL,
9 PRIMARY KEY (`bookid`)
10 );
11
12 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
13 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
14 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
15 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
16 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
17 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
18 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
19 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
20 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
21 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
22 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
23 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
24 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
25 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
26 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
27 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
28 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
29 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
30 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
31 INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
32
33 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
34 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
35 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
36 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
37 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
38 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
39 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
40 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
41 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
42 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
43 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
44 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
45 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
46 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
47 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
48 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
49 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
50 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
51 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
52 INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
53
54
55
案例分析:
1 # 下面开始explain分析
2 EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
3 #结论:type 有All
4
5 # 添加索引优化
6 ALTER TABLE `book` ADD INDEX Y ( `card`);
7
8 # 第2次explain
9 EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
10 #可以看到第二行的 type 变为了 ref,rows 也变成了优化比较明显。
11 #这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,
12 #所以右边是我们的关键点,一定需要建立索引。
13
14 # 删除旧索引 + 新建 + 第3次explain
15 DROP INDEX Y ON book;
16 ALTER TABLE class ADD INDEX X (card);
17 EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
18
19
20 # 然后来看一个右连接查询:
21 #优化较明显。这是因为 RIGHT JOIN 条件用于确定如何从左表搜索行,右边一定都有,所以左边是我们的关键点,一定需要建立索引。
22 EXPLAIN SELECT * FROM class RIGHT JOIN book ON class.card = book.card;
23 DROP INDEX X ON class;
24 ALTER TABLE book ADD INDEX Y (card);
25 # 右连接,基本无变化
26 EXPLAIN SELECT * FROM class RIGHT JOIN book ON class.card = book.card;
27
3. 三表
建表SQL:
1 CREATE TABLE IF NOT EXISTS `phone` (
2 `phoneid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
3 `card` INT(10) UNSIGNED NOT NULL,
4 PRIMARY KEY (`phoneid`)
5 ) ENGINE = INNODB;
6
7 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
8 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
9 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
10 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
11 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
12 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
13 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
14 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
15 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
16 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
17 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
18 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
19 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
20 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
21 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
22 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
23 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
24 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
25 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
26 INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
案例:
1 ALTER TABLE `phone` ADD INDEX z ( `card`);
2
3 ALTER TABLE `book` ADD INDEX Y ( `card`);#上一个case建过一个同样的
4
5
6 EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card=book.card LEFT JOIN phone ON book.card = phone.card;
7
8 # 后 2 行的 type 都是 ref 且总 rows 优化很好,效果不错。因此索引最好设置在需要经常查询的字段中。
9 ==================================================================================
10 【结论】
11 Join语句的优化
12
13 尽可能减少Join语句中的NestedLoop的循环总次数;“永远用小结果集驱动大的结果集”。
14 优先优化NestedLoop的内层循环;
15 保证Join语句中被驱动表上Join条件字段已经被索引;
16 当无法保证被驱动表的Join条件字段被索引且内存资源充足的前提下,不要太吝惜JoinBuffer的设置;
12.2 索引失效(应该避免)
建表SQL:
1 CREATE TABLE staffs (
2 id INT PRIMARY KEY AUTO_INCREMENT,
3 NAME VARCHAR (24) NOT NULL DEFAULT '' COMMENT '姓名',
4 age INT NOT NULL DEFAULT 0 COMMENT '年龄',
5 pos VARCHAR (20) NOT NULL DEFAULT '' COMMENT '职位',
6 add_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间'
7 ) CHARSET utf8 COMMENT '员工记录表' ;
8
9
10 INSERT INTO staffs(NAME,age,pos,add_time) VALUES('z3',22,'manager',NOW());
11 INSERT INTO staffs(NAME,age,pos,add_time) VALUES('July',23,'dev',NOW());
12
13 SELECT * FROM staffs;
14
15 ALTER TABLE staffs ADD INDEX idx_staffs_nameAgePos(name, age, pos);
案例(索引失效):
1. 全值匹配我最爱
1 EXPLAIN SELECT * FROM staffs WHERE NAME = 'July';
2 EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25;
3 EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25 AND pos = 'dev';
2. 最佳左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
1 EXPLAIN SELECT * FROM staffs WHERE age = 25 AND pos = 'dev';
2
3 EXPLAIN SELECT * FROM staffs WHERE pos = 'dev';
3. 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
1 EXPLAIN SELECT * FROM staffs WHERE left(NAME,4) = 'July';
2
3 索引列上使用了表达式,如where substr(a, 1, 3) = 'hhh',where a = a + 1,表达式是一大忌讳,再简单mysql也不认。
4 有时数据量不是大到严重影响速度时,一般可以先查出来,比如先查所有有订单记录的数据,再在程序中去筛选
4. 存储引擎不能使用索引中范围条件右边的列
5. 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *
6. mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描
7. is null ,is not null 也无法使用索引
8. like以通配符开头('%abc...')mysql索引失效会变成全表扫描的操作
9. 字符串不加单引号索引失效
10. 少用or,用它来连接时会索引失效
所以我们来一个小小的总结:
接着我们以一个例子来深刻体会体会:
SQL如下:
【建表语句】
create table test03(
id int primary key not null auto_increment,
c1 char(10),
c2 char(10),
c3 char(10),
c4 char(10),
c5 char(10)
);
insert into test03(c1,c2,c3,c4,c5) values('a1','a2','a3','a4','a5');
insert into test03(c1,c2,c3,c4,c5) values('b1','b2','b3','b4','b5');
insert into test03(c1,c2,c3,c4,c5) values('c1','c2','c3','c4','c5');
insert into test03(c1,c2,c3,c4,c5) values('d1','d2','d3','d4','d5');
insert into test03(c1,c2,c3,c4,c5) values('e1','e2','e3','e4','e5');
select * from test03;
【建索引】
create index idx_test03_c1234 on test03(c1,c2,c3,c4);
show index from test03;
问题:我们创建了复合索引idx_test03_c1234 ,根据以下SQL分析下索引使用情况?
explain select * from test03 where c1='a1';
explain select * from test03 where c1='a1' and c2='a2';
explain select * from test03 where c1='a1' and c2='a2' and c3='a3';
explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4';
1)
explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4';
2)
explain select * from test03 where c1='a1' and c2='a2' and c4='a4' and c3='a3';
3)
explain select * from test03 where c1='a1' and c2='a2' and c3>'a3' and c4='a4';
4)
explain select * from test03 where c1='a1' and c2='a2' and c4>'a4' and c3='a3';
5)
explain select * from test03 where c1='a1' and c2='a2' and c4='a4' order by c3;
c3作用在排序而不是查找
6)
explain select * from test03 where c1='a1' and c2='a2' order by c3;
7)
explain select * from test03 where c1='a1' and c2='a2' order by c4;
出现了filesort
8)
8.1 explain select * from test03 where c1='a1' and c5='a5' order by c2,c3;
只用c1一个字段索引,但是c2、c3用于排序,无filesort
8.2 explain select * from test03 where c1='a1' and c5='a5' order by c3,c2;
出现了filesort,我们建的索引是1234,它没有按照顺序来,3 2 颠倒了
9)
explain select * from test03 where c1='a1' and c2='a2' order by c2,c3;
10)
explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c2,c3; 用c1、c2两个字段索引,但是c2、c3用于排序,无filesort
explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c3,c2; 本例有常量c2的情况,和8.2对比
explain select * from test03 where c1='a1' and c5='a5' order by c3,c2; filesort
11)
explain select * from test03 where c1='a1' and c4='a4' group by c2,c3;
12)
explain select * from test03 where c1='a1' and c4='a4' group by c3,c2; Using where; Using temporary; Using filesort
我们不难得出:
定值、范围还是排序,一般order by是给个范围,group by 基本上都需要进行排序,会有临时表产生。
12.3 Show Profile
是什么:是mysql提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量。默认情况下,参数处于关闭状态,并保存最近15次的运行结果。
分析步骤;
1. 是否支持,看看当前的mysql版本是否支持
Show variables like 'profiling'; //默认是关闭,使用前需要开启
2. 开启功能,默认是关闭,使用前需要开启
3. 运行SQL
4. 查看结果,show profiles;
5. 诊断SQL,show profile cpu,block io for query 上一步前面的问题SQL数字号码;
6. 日常开发需要注意的地方总结(如有遗漏,还望同行不吝赐教)
converting HEAP to MyISAM 查询结果太大,内存都不够用了往磁盘上搬了。
create tmp table 创建临时表,这个要注意。
Copying to tmp table on disk 把内存临时表复制到磁盘。
locked 是否上锁,这个也要注意。
12.4 索引优化的需要注意的一些
对于单键索引,尽量选择针对当前query过滤性更好的索引;
在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。
在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引。
尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的。
少用Hint强制索引。
13. 查询优化
13.1 永远小表驱动大表,类似嵌套循环Nested Loop
13.2 order by关键字优化
1. ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序
建表SQL:
1 CREATE TABLE tblA(
2 #id int primary key not null auto_increment,
3 age INT,
4 birth TIMESTAMP NOT NULL
5 );
6
7 INSERT INTO tblA(age,birth) VALUES(22,NOW());
8 INSERT INTO tblA(age,birth) VALUES(23,NOW());
9 INSERT INTO tblA(age,birth) VALUES(24,NOW());
10
11 CREATE INDEX idx_A_ageBirth ON tblA(age,birth);
12
13 SELECT * FROM tblA;
案例:
MySQL支持二种方式的排序,FileSort和Index,Index效率高。它指MySQL扫描索引本身完成排序。FileSort方式效率较低。
ORDER BY满足两情况,会使用Index方式排序:
ORDER BY 语句使用索引最左前列;使用Where子句与Order BY子句条件列组合满足索引最左前列
2. 尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀
3. 如果不在索引列上,filesort有两种算法:mysql就要启动双路排序和单路排序
双路排序:MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出。从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。
取一批数据,要对磁盘进行了两次扫描,众所周知,IO是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。
单路排序:从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。
4. 结论及引申出的问题
由于单路是后出的,总体而言好过双路;但是用单路有问题(在sort_buffer中,方法B比方法A要多占用很多空间,因为方法B是把所有字段都取出, 所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排
序(创建tmp文件,多路合并),排完再取取sort_buffer容量大小,再排……从而多次I/O。本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。)
5. 单路有问题,所以我们要用一些优化策略
增大max_length_for_sort_data参数的设置;
增大sort_buffer_size参数的设置;
提高Order By的速度
1. Order by时select * 是一个大忌只Query需要的字段, 这点非常重要。在这里的影响是:
1.1 当Query的字段大小总和小于max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 否则用老算法——多路排序。
1.2 两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size。
2. 尝试提高 sort_buffer_size
不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的
3. 尝试提高 max_length_for_sort_data
提高这个参数, 会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率.
6. 总结
13.3 GROUP BY关键字优化
1. group by实质是先排序后进行分组,遵照索引建的最佳左前缀
2. 当无法使用索引列,增大max_length_for_sort_data参数的设置+增大sort_buffer_size参数的设置
3. where高于having,能写在where限定的条件就不要去having限定了。
/***一个高级的J2E工程师需要面对MySQL要有那些基本功夫呢<上>***/
一个高级的J2E工程师需要面对MySQL要有那些基本功夫呢?这个系列第一篇就到此结束了,第一篇对mysql的架构、mysql性能因素进行介绍,并对查询与索引优化进行了分析。再接下来的中、下两篇中,我再会为介绍大数据量处理理论、分区分库分表、MySql锁机制和主从复制等。不要急,敬请期待下一篇博文!
/***一个高级的J2E工程师需要面对MySQL要有那些基本功夫呢<上>***/
本文为博主原创文章,转载请注明出处!
http://www.cnblogs.com/libingbin/
感谢您的阅读。