对于MySQL,最简单的衡量查询开销的三个指标如下:
-
响应时间:服务时间和排队时间之和。服务时间是指数据库处理这个查询真正花了多长时间。排队时间是指服务器因为等待某些资源而没有真正执行查询的时间——可能是等I/O操作完成,也可能是等待行锁。 -
扫描的行数:一条查询,如果性能很差,最常见的原因是访问的数据太多。大部分性能低下的查询都可以通过减少访问的数据量的方式进行优化。有时候也可能是访问了太多的列;(每次看到SELECT*的时候都需要用怀疑的眼光审视,是不是真的需要返回全部的列,很可能不是必需的。取出全部列,会让优化器无法完成索引覆盖扫描这类优化,还会为服务器带来额外的I/O、内存和CPU的消耗) -
返回的行数:会给服务器带来额外的I/O、内存和CPU的消耗(使用limit限制返回行数)
select * from film_actor where film_id = 1 -- film_id列有索引
-
在索引中使用WHERE条件来过滤不匹配的记录。这是在存储引擎层完成的。 -
使用索引覆盖扫描(在Extra列中出现了Using index)来返回记录,直接从索引中过滤不需要的记录并返回命中的结果。这是在MySQL服务器层完成的,但无须再回表查询记录。 举例说明一下:索引列a,b,c,查询条件时 a = xx and c = xx -
从数据表中返回数据,然后过滤不满足条件的记录(在Extra列中出现Using where)。这在MySQL服务器层完成,MySQL需要先从数据表中读出记录然后过滤。
-
索引将相关的记录放到一起则获得“一星”; -
如果索引中的数据顺序和查找中的排列顺序一致则获得“二星”; -
如果索引中的列包含了查询中需要的全部列则获得“三星”。
-
可以把相互关联的数据保存在一起,数据访问更快。聚簇索引将索引和数据保存在同一个B-tree中,因此从聚簇索引中获取数据通常比在非聚簇索引中查找要快。
-
插入速度严重依赖于插入顺序。按照主键的顺序插入行是将数据加载到InnoDB表中最快的方式。但如果不是按照主键的顺序加载数据,那么在加载完成后最好使用OPTIMIZE TABLE命令重新组织一下表。 -
更新聚簇索引列的代价很高,因为它会强制InnoDB将每个被更新的行移动到新的位置。 -
基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临页分裂(page split)的问题。当行的主键值要求必须将这一行插入某个已满的页中时,存储引擎会将该页分裂成两个页面来容纳该行,这就是一次页分裂操作。页分裂会导致表占用更多的磁盘空间。
-
索引条目通常远小于数据行大小,所以如果只需要读取索引,那么MySQL就会极大地减少数据访问量。 -
因为索引是按照列值的顺序存储的(至少在单页内如此),所以对于I/O密集型的范围查询会比随机从磁盘读取每一行数据的I/O要少得多。 -
由于InnoDB的聚簇索引的特点,覆盖索引对InnoDB表特别有用。InnoDB的二级索引在叶子节点中保存了记录的主键值,所以如果二级索引能够覆盖查询,则可以避免对主键索引的二次查询。(二级索引访问需要两次索引查找,而不是一次,通过二级索引查找行,存储引擎需要找到二级索引的叶子节点,以获得对应的主键值,然后根据这个值去聚簇索引中查找对应的行。)
select * from payment where staff_id = 123 and customer_id = 456
select * from payment where staff_id = 123 -- 索引是customer_id,staff_id,因为漏掉了customer_id,所以走不上索引
不需要文件排序
select * from rental where rental_date = '2005-05-25' order by inventory_id, customer_id -- rental_date是常量,后面的排序列则认为符合最左前缀
select * from rental where rental_date = '2005-05-25' order by inventory_id, customer_id,id -- rental_date是常量,后面的排序列则认为符合最左前缀,虽然索引里面没有显示指定id列,id是隐性的包含在索引中的,所以也无需文件排序
需要文件排序
-
下面这个查询也没有问题,因为ORDER BY使用的两列就是索引的最左前缀(索引顺序:rental_date,inventory_id,customer_id):
select * from rental where rental_date > '2005-05-25' order by rental_date, inventory_id -- 符合最左前缀
-
下面这个查询使用了两种不同的排序方向,但是索引中的列都是按正序排序的(索引顺序:rental_date,inventory_id,customer_id):
select * from rental where rental_date = '2005-05-25' order by inventory_id asc, customer_id desc -- 满足最左前缀,但是排序方向不一样
-
在下面这个查询的ORDER BY子句中,引用了一个不在索引中的列(索引顺序:rental_date,inventory_id,customer_id):
select * from rental where rental_date = '2005-05-25' order by inventory_id ,staff_id -- staff_id不在索引中
-
下面这个查询的WHERE和ORDER BY中的列无法组合成索引的最左前缀(索引顺序:rental_date,inventory_id,customer_id):
select * from rental where rental_date = '2005-05-25' order by customer_id -- 不满足最左前缀,漏掉了inventory_id
-
下面这个查询在索引列的第一列上是范围条件,所以MySQL无法使用索引的其余列(索引顺序:rental_date,inventory_id,customer_id):
select * from rental where rental_date > '2005-05-25' order by inventory_id -- 第一列如果是范围查询,则认为不符合最左前缀
-
这个查询在inventory_id列上有多个等于条件。对于排序来说,这也是一种范围查询(索引顺序:rental_date,inventory_id,customer_id):
select * from rental where rental_date = '2005-05-25' and inventory_id in (1,2) order by customer_id -- inventory_id 条件是范围查询,则认为不符合最左前缀
想了解更多干货,可通过下方扫码关注
可扫码添加上智启元官方客服微信👇
I love the way you explain things; it’s obvious and concise.
This post is accessible and understandable, well done.
Hello! I’ve been reading your web site for some time
now and finally got the bravery to go ahead and give you a shout out from Houston Texas!
Just wanted to say keep up the good work!
you too