http://blog.csdn.net/lemon89/article/details/50193891
相关文章:
深入理解Mysql——高性能索引与高性能SQL
深入理解Mysql——锁、事务与并发控制(辟谣)
深入理解Mysql——schema设计与大表alter操作
mysql explain-output 译文
主轴让磁盘盘片转动,然后传动手臂可伸展让读取头在盘片上进行读写操作。每个盘片有两面,都可记录信息,所以一张盘片对应着两个磁头。
磁盘物理结构如下图:
扇区:盘片被分为许多扇形的区域,每个区域叫一个扇区,硬盘中每个扇区的大小固定为512字节
磁道:盘片表面上以盘片中心为圆心,不同半径的同心圆环称为磁道。
一个I/O请求所花费的时间=寻道时间+旋转延迟+数据传输时间(约10ms)
当需要从磁盘读取数据时,系统会将数据逻辑地址传给磁盘,磁盘的控制电路按照寻址逻辑将逻辑地址翻译成物理地址,即确定要读的数据在哪个磁道,哪个扇区。为了读取这个扇区的数据,需要将磁头放到这个扇区上方,为了实现这一点,磁头需要移动对准相应磁道,这个过程叫做寻道,所耗费时间叫做寻道时间,然后磁盘旋转将目标扇区旋转到磁头下,这个过程耗费的时间叫做旋转时间。
将读写磁头移动至正确的磁道上所需要的时间。寻道时间越短,I/O操作越快,目前磁盘的平均寻道时间一般在3-15ms。
指盘片旋转将请求数据所在的扇区移动到读写磁盘下方所需要的时间。旋转延迟取决于磁盘转速,通常用磁盘旋转一周所需时间的1/2表示。比如:7200rpm的磁盘平均旋转延迟大约为60*1000/7200/2 = 4.17ms,而转速为15000rpm的磁盘其平均旋转延迟为2ms。
是指完成传输所请求的数据所需要的时间,它取决于数据传输率,其值等于数据大小除以数据传输率。数据传输时间通常远小于前两部分消耗时间。简单计算时可忽略。
当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到,所以每次读取页的整数倍(通常一个节点就是一页)。每一次IO读取的数据我们称之为一页(page)。
预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。
连续读写性能很好,但随机读写性能很差
机械硬盘的连续读写性能很好,但随机读写性能很差,这主要是因为磁头移动到正确的磁道上需要时间,随机读写时,磁头需要不停的移动,时间都浪费在了磁头寻址上,所以性能不高。
IOPS
IOPS(Input/Output Per Second)即指每秒内系统能处理的I/O请求数量。
随机读写频繁的应用,如小文件存储等,关注随机读写性能,IOPS是关键衡量指标。
可以推算出磁盘的IOPS = 1000ms / (Tseek + Trotation + Transfer)
常见磁盘的随机读写最大IOPS为:
磁盘吞吐量
指单位时间内可以成功传输的数据数量。
磁盘阵列与服务器之间的数据通道对吞吐量影响很大。
顺序读写频繁的应用,如视频点播,关注连续读写性能、数据吞吐量是关键衡量指标。
索引是什么?
MySQL官方对索引的定义为:索引是帮助MySQL高效获取数据的数据结构。简而言之,索引是数据结构。
B+Tree,简单来说就是一种为磁盘或者其他存储设备而设计的一种平衡二叉树,在B+tree中所有记录都按照key的大小存放在叶子结点上,各叶子结点直接用指针连接。
由二叉树,平衡二叉树,BTree演化而来。
二叉树 要保证父节点大于左子结点,小于右子节点。
平衡二叉树 在二叉树的基础上,还要保证任一结点的两个儿子字树高度差不大于1。
BTree 是一种自平衡二叉树,继承了上述平衡二叉树的特性,另外并保证了每个叶子结点到根节点的距离相同。
BTree vs B+Tree:
B+树与BTree主要不同就是data的存放位置,以及叶子结点的指针构成链表。
可以在这个网站上查看动画进行操作Algorithms。
聚集索引:
InnfoBD引擎是索引组织表,所有数据都存放在聚集索引中。
准确来说聚集索引并不是某种单独的索引类型,而是一种数据存储方式。就是指在同一个结构中保存了B+tree索引以及数据行。InnoDB中通常主键就是一个聚集索引。
innoDB中,用户如果没有设置主键索引,会随机选择一个唯一的非空索引替代,
如果没有这样的索引,会隐式的定义一个主键作为隐式的聚集索引。
通常将主键设置为一个与业务无关的自增数字,这样能保证按照主键顺序插入数据,避免页分裂以及碎片问题。
主键索引的非叶子结点存放的是<.key,address.>,address就是指向下一层的指针。
主键索引的叶子结点保存了所有列的信息,因此通过主键索引可以快速获取数据。
辅助索引
(或称为非聚集索引、二级索引)
辅助索引的叶子结点并没有存放数据,而是存放了主键值。
因为二级索引叶子页中存放了主键索引的值信息,如果主键索引很大的话,会导致所有索引都比较大。因此主键索引尽可能要小
也就是说使用辅助索引查询,会通过叶子结点找到对应的主键,在主键索引中找到最终的数据。
1.使用索引可以大大减少服务器需要扫描的数据量。
2.使用索引可以帮助服务器避免排序或者临时表
3.索引是随机I\O变为 顺序I\O.
索引并不是适用于任何情况。对于中型、大型表适用。对于小型表全表扫描更高效。而对于特大型表,考虑”分区”技术。
以下讲解使用如下表作为示例:
mysql> show create table people \G
*************************** 1. row ***************************
Table: people
Create Table: CREATE TABLE `people` (
`last_name` varchar(50) NOT NULL,
`first_name` varchar(50) NOT NULL,
`dob` date NOT NULL,
`gender` enum('m','f') NOT NULL,
KEY `last_name` (`last_name`,`first_name`,`dob`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
对于KEY last_name
(last_name
,first_name
,dob
),where 后的谓词必须包含last_name(组合索引的最左列),否则无法使用这个索引.
示例: select ... from people where .... last_name="..."....
这个语句将使用 last_name
(last_name
,first_name
,dob
)索引。
示例:
SELECT ... FROM people WHERE last_name="..." AND dob="..."
这个语句只使用了last_name
(last_name
,first_name
,dob
)的last_name列,因为缺少first_name,所以后续dob列也无法从索引中搜索。
示例:
SELECT ... FROM people WHERE last_name > "..." AND first_name= "..." AND dob= "..."
这个语句只使用了last_name
(last_name
,first_name
,dob
)的last_name列,因为last_name 使用了范围查询,所以后续索引的两个列无法使用。
什么事范围查询:
使用了范围查询的语句。范围查询指使用 “>” 、”<”、“between” “like”的查询。注意“in”不算范围查询,属于多值查询条件。
列作为函数参数或表达式的一部分无法正常使用索引。
示例1: SELECT ... FROM people WHERE last_name+1 = "1001"
示例2: SELECT ... FROM people FORCE INDEX(last_name) WHERE LEFT(last_name,3) = "..."
以上示例均无法使用last_name
(last_name
,first_name
,dob
)索引。explain 展示位全表扫描。
什么是索引的选择性:
索引的基数(Cardinality) / 表的总记录数(#T) select count(Distinct columnName)/count(*) from Table
范围从 1#T ~ 1 ,值越高查询效率越高。唯一索引的选择性是:1.
注:索引的基数(Cardinality)不重复的索引值。此处计算的基数(Cardinality),与SHOW INDEX 语句中的Cardinality并不一致!SHOW INDEX 语句中的只是预估值。
一般情况,将选择性高的列放在左边,选择性高代表这个列的过滤性较好,尽可能的尽快过滤掉无用的数据。
前缀索引
对于 较大的Varchar类型、Text类型、Blob类型,需要建立索引时必须使用前缀索引,因为mysql不允许索引完整大小,而且索引字段越大效率越差。
可以索引开始的部分字符串(取代全部),大大节约索引空间,提高索引效率。但这样会降低索引的选择性。
所以,对比较长的 (Varchar、Text、BLOB等等数据类型)列查询,要保证索引的选择性,又要不能太长以节省空间。所以“前缀”需要选的恰到好处:
“前缀索引”的基数应该接近完整的列索引的基数。
示例:前7个字符的前缀索引
mysql> select count(Distinct last_name)/count(*) from people ;
+------------------------------------+
| count(Distinct last_name)/count(*) |
+------------------------------------+
| 0.7059 |
+------------------------------------+
1 row in set (0.07 sec)
------------------------------------------------------------
mysql> select count(Distinct left(last_name,5))/count(*), count(Distinct left(la
st_name,6))/count(*) ,count(Distinct left(last_name,7))/count(*) from people \G
*************************** 1. row ***************************
count(Distinct left(last_name,5))/count(*): 0.6471
count(Distinct left(last_name,6))/count(*): 0.7059
count(Distinct left(last_name,7))/count(*): 0.7059
1 row in set (0.00 sec)
mysql> alter table people add key (last_name(6))
所以使用前6个字符即可达到完整字段的过滤性。
注意:
前缀索引是能够使索引更小,更快的方法,但是无法使用前缀索引做 Group By\Order By,也不能用前缀索引做覆盖查询(Using Index)。
除了使用前缀索引的方式处理这类大字段索引的情况,还有如下方式:
伪哈希索引
-- step1建表语句
CREATE TABLE `people` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`last_name` VARCHAR(50) COLLATE utf8_bin NOT NULL,
`first_name` VARCHAR(50) COLLATE utf8_bin NOT NULL,
`dob` DATE NOT NULL,
`gender` ENUM('m','f') COLLATE utf8_bin NOT NULL,
`blog_url` VARCHAR(128) COLLATE utf8_bin DEFAULT NULL,
`crc32_url` BIGINT(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `last_name` (`last_name`,`first_name`,`dob`),
KEY `crc32_url` (`crc32_url`)
) ENGINE=INNODB
因为blog_url是一个较长的字符串,所以直接将blog_url作为索引列会影响索引的整体效率。现在,尝试用一个伪hash值做一个伪hash索引。
-- step2 建立触发器,用于维护hash值
DELIMITER //
CREATE TRIGGER pseudohash_crc32_ins BEFORE INSERT
ON people FOR EACH ROW
BEGIN
SET New.crc32_url = CRC32(New.blog_url);
END //
CREATE TRIGGER pseudohash_crc32_upd BEFORE UPDATE
ON people FOR EACH ROW
BEGIN
SET New.crc32_url = CRC32(New.blog_url);
END //
DELIMITER;
效果如下,非常明显:
SELECT COUNT(1) FROM people ;
/**
636480
*/
SELECT blog_url FROM people WHERE blog_url="http://www.-588141732.com" AND crc32_url=1790086969
/**
sql执行时间:
执行耗时 : 0.004 sec
传送时间 : 0 sec
总耗时 : 0.004 sec
explain:
"id" "select_type" "table" "partitions" "type" "possible_keys" "key" "key_len" "ref" "rows" "filtered" "Extra"
"1" "SIMPLE" "people" \N "ref" "crc32_url" "crc32_url" "9" "const" "1" "10.00" "Using where"
*/
EXPLAIN SELECT blog_url FROM people WHERE blog_url="http://www.-588141732.com"
/**
sql执行时间:
执行耗时 : 0.413 sec
传送时间 : 0 sec
总耗时 : 0.413 sec
explain:
"id" "select_type" "table" "partitions" "type" "possible_keys" "key" "key_len" "ref" "rows" "filtered" "Extra"
"1" "SIMPLE" "people" \N "ALL" \N \N \N \N "630928" "10.00" "Using where"
*/
crc函数当数据量达到93000时,会产生1%的冲突。
如果要避免hash冲突的概率可以使用MD5()截取的方式取代crc()。
如:
SELECT CONV(RIGHT(MD5("http://www.-aqq3feaeaff41732fff.com"),16),16,10) AS hashCode;
三星索引(关系型数据库索引设计及优化)
1.一星 使用where后的谓词列,按照选择性构造索引。
2.二星 如果语句中有排序操作,使用索引自带的顺序的排序(消除fileSort)。
3.三星 如果可以的话,将select后的还不在索引中的列名放到索引后边,可以覆盖索引(using index),而不需要读取表数据。
如下这个sql就是一个三星索引。SELECT last_name,first_name,dob FROM people WHERE last_name=”101899” AND first_name=”10189900” AND dob=”2017-08-03” ORDER BY dob
在实际应用中,无法保证三个星每个星都满足。需要权衡取舍。
未使用的索引 也是累赘。建议删除。
关于explain的详细解释,请参考:
explain 详解
或者查看官网文档
explain-output
除了explain,还可以查看sql耗时分布情况。
Show Profiles \Show profile queryId
SHOW PROFILES;
/**
"Query_ID" "Duration" "Query"
...
"19" "0.00007350" "select ...."
"20" "0.00026150" "select state, round(sum(duration),5) ....."
...
*/
SHOW PROFILE FOR QUERY 24
/**
"Status" "Duration"
"Creating sort index" "0.748448"
"freeing items" "0.001355"
"starting" "0.000029"
"cleaning up" "0.000019"
"init" "0.000015"
"statistics" "0.000012"
"end" "0.000008"
"preparing" "0.000007"
"Opening tables" "0.000007"
"closing tables" "0.000006"
"Sending data" "0.000005"
"query end" "0.000005"
"optimizing" "0.000004"
"System lock" "0.000004"
"Sorting result" "0.000003"
"checking permissions" "0.000001"
"checking permissions" "0.000001"
"executing" "0.000001"
*/
Step1:客户端向Mysql服务器发送SQL语句。
使用”半双工”通信方式,客户端或服务端在一个连接上同一时刻只允许一方进行数据传输,并且直到数据传输完成,另一方才能执行传输。
当语句太长,超过 max_allowed_packet ,服务端会拒绝接收。
通常建议加上limit,可以减少不必要的数据从服务端发送到客户端。
Step2:服务器收到后先查询”查询缓存“,如果命中,从缓存中直接返回sql执行的结果集。否则,进入Step3。
这个缓存通过一个对大小写敏感的hash算法实现,及时只有一个字节不匹配,那也无法命中。
Step3:服务器解析、预处理、优化sql执行计划,然后将处理好的sql放入查询的执行计划中。
在这个阶段,sql会被转换为一个执行计划,使用这个执行计划于具体的存储引擎进行交换。这个阶段包括,解析、预处理、优化sql执行计划这三个子任务。
Step4:执行引擎通过调用”存储引擎”(如,innodb、myisam等)提供的API去执行这个计划。
Step1:explain查看 (show profile可以查看耗时分布)
Step2:确认优化目标\方向,对于复杂sql需要理清执行步骤
目标1. type是否能够按照 const>eq_reg>ref>range>index>ALL的顺序优化,最差也要达到range级别。
目标2. 避免filesort的出现、避免rows数据量太大等负面字段、索引选择性是否足够、对于关联查询尽量保证关联字段在第二张表上有可用索引(原因:NestLoop)。
Step3:遵照SQL索引原则增加或调整SQL,常见如下(可参考上文去理解)
除了full Join,其他所有类型的查询SQL,都以类似的方式执行。
NestLoop (内嵌套循环)算法,简单来说就是逐行查询处理,或者内嵌逐行查询。对于高版本的使用join buffer对上层表数据缓存,无需多次遍历上层表,下层表直接使用(Block NestLoop)。
以下以两个示例详细说明执行计划,其他join以及单表查询原理也是类似的!
Join执行顺序伪代码演示
示例1:内关联inner join
SELECT
people.id,user.id
FROM
user
INNER JOIN
people ON user.name = people.name
WHERE
user.enumType = 'orange'
AND people.enumType = 'orange';
示例1对应伪代码:
//先扫描先执行的表,优化器通常选择关联的较小的表,explain第一行。
people_iterator=people_table.iterator();
//逐行遍历,并且丢弃where筛选不通过的行
while(people_iterator.hashNext()){
people_item=people_iterator.next();
if(people_item.enumType=='orange'){
//筛选通过后,在进入第二个嵌套
user_iterator=user_table.iterator()
//逐行遍历第二个表
while(user_iterator.hashNext()){
user_item=user_iterator.next();
//过滤:on 的条件匹配以及当前表的where条件
if(user_item.name==people_item.name&&user_item.enumType=='orange' ){
output(people.id,user.id);
}
}
}
}
示例2:非内关联
SELECT
people.id,user.id
FROM
user
LEFT JOIN
people ON user.name = people.name
WHERE
user.enumType = 'orange'
AND people.enumType = 'orange';
示例2伪代码
//先扫描先执行的表,优化器通常选择关联的较小的表,explain第一行。
people_iterator=people_table.iterator();
//逐行遍历,并且丢弃where筛选不通过的行
while(people_iterator.hashNext()){
people_item=people_iterator.next();
if(people_item.enumType=='orange'){
//筛选通过后,在进入第二个嵌套
user_iterator=user_table.iterator()
//逐行遍历第二个表
while(user_iterator.hashNext()){
user_item=user_iterator.next();
//过滤:on 的条件匹配以及当前表的where条件
if(user_item.name==people_item.name&&user_item.enumType=='orange' ){
output(people.id,user.id);
}
//与innerjoin不同的,leftJoin需要即使on条件不成立,也要保留左边数据
else if(!is_innerJoin){
output(people.id,null);//保留左边数据
}
}
}
}
注意:尽量保证关联字段在第二张表上有可用索引。
(因为第一张表示全表扫描,然后会对第二张表用关联字段查询,详情请看NestLoop理解关联过程)
1.通常情况下,使用一个性能好的sql去做更多的事情,而不是使用多个sql。
除非这个sql过长效率低下或者对于delete这种语句,过长的delete会导致太多的数据被锁定,耗尽资源,阻塞其他sql。
2.分解关联查询。
将关联(** join……)放在应用中处理,执行小而简单的sql,好处是:
关于Count()
count()函数有两种含义:统计行数、统计列数。
比如:count(*)代表统计的行数;count(talbe.cloumn)代表统计的是这个列不为null的数量。
关于Limit
在使用Limit 1000,20这种操作的时候,mysql会扫描偏移量(1000条无效查询)数据,而只取后20条,尽量避免这种写法,想办法规避。
关于Union
需要将where、order by、limit 这些限制放入到每个子查询,才能重分提升效率。另外如非必须,尽量使用Union all,因为union会给每个子查询的临时表加入distinct,对每个临时表做唯一性检查,效率较差。
/*1.查看索引
(1)单位是GB*/
SELECT CONCAT(ROUND(SUM(index_length)/(1024*1024*1024), 6), ' GB') AS 'Total Index Size'
FROM information_schema.TABLES WHERE table_schema LIKE 'database';
/*
+------------------+
| Total Index Size |
+------------------+
| 1.70 GB |
+------------------+
*/
/*
(2)单位是MB
*/
SELECT CONCAT(ROUND(SUM(index_length)/(1024*1024), 6), ' MB') AS 'Total Index Size'
FROM information_schema.TABLES WHERE table_schema LIKE 'database';
/*
其中“database”为你所要查看的数据库
*/
/*
2.查看表空间
*/
SELECT CONCAT(ROUND(SUM(data_length)/(1024*1024*1024), 6), ' GB') AS 'Total Data Size'
FROM information_schema.TABLES WHERE table_schema LIKE 'database';
/*
+-----------------+
| Total Data Size |
+-----------------+
| 3.01 GB |
+-----------------+
*/
/*
3.查看数据库中所有表的信息
*/
SELECT CONCAT(table_schema,'.',table_name) AS 'Table Name',
table_rows AS 'Number of Rows',
CONCAT(ROUND(data_length/(1024*1024*1024),6),' G') AS 'Data Size',
CONCAT(ROUND(index_length/(1024*1024*1024),6),' G') AS 'Index Size' ,
CONCAT(ROUND((data_length+index_length)/(1024*1024*1024),6),' G') AS'Total'
FROM information_schema.TABLES
WHERE table_schema LIKE 'database';
关于SQL执行顺序可参考:
https://www.2cto.com/database/201512/453280.html
https://huoding.com/2013/06/04/261其余推荐文章:
http://blog.codinglabs.org/articles/index-condition-pushdown.html
https://segmentfault.com/a/1190000003072424
https://tech.meituan.com/mysql-index.html
https://tech.meituan.com/about-desk-io.html
http://www.orczhou.com/index.php/2013/04/how-mysql-choose-index-in-a-join/推荐书籍:
Mysql内核——innoDB存储引擎;
高性能mysql(https://dev.mysql.com/doc/index-other.html);