第五章 创建高性能的索引(下)

接上文: 第五章 创建高性能的索引(上)

覆盖索引

如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为: 覆盖索引. 覆盖索引的好处有:

  1. 索引条目远小于数据行大小,能够极大地提高性能,所以如果只需要读取索引,那么MySQL就会极大地减少数据访问量
  2. 因为索引是按照值顺序存储的,所以对于I/O密集型的范围查询会比随机从磁盘中读取每一行数据的I/O要少的多
  3. 如果不覆盖索引,则会产生回表查询, 先定位主键值,再定位行记录,它的性能较低

当发起一个被索引覆盖的查询时, 在EXPLAIN的Extra列可以看到'Using index'的信息.

小技巧: 延迟关联

select * from products where actor='SEAN CARREY' and title like '%APOLLO%';

上面的select * 包含了所有的列, 因此没办法使用覆盖索引, 回表需要扫描很多不满足条件的行. 但它的where条件是可以有索引可以覆盖的, 利用延迟关联(deferred join)的技巧, 建立(actor, title, prod_id)索引, 利用子查询的覆盖索引只过滤出满足条件的行:

select * from products join (select prod_id from products where actor='SEAN CARREY' and title like '%APOLLO%')as t1 on (t1.prod_id=products.prod_id);

分页查询时这个技巧常常被使用:

-- 索引:(threa_id, deleted)
select * from t join (select id
from t where thread_id = 5616385 and deleted = 0
order by id limit 50000, 10) t1 on t.id=t1.id;

使用索引扫描来排序

MySQL有两种方式可以生成有序的结果:通过排序操作;或者按索引顺序扫描
如果EXPLAIN出来的type列的值为index,则说明使用了索引扫描排序

只有当索引的列顺序和order by子句的顺序完全一致, 且所有列的排序方向(倒序或正序)都一样时, MySQL才能使用索引来对结果排序. 当关联多张表, 则只有当Order by子句引用的字段全部为第一个表时, 才能使用索引来排序. 且Order By子句要满足索引的最左前缀的要求.

下面这个例子, where子句的前缀列是范围时, 也无法使用索引扫描排序:

-- 有索引(rental_date, inventory_id, customer_id)
... where rental_date='2005-05-25' and inventory_id in (1,2) order by customer_id.

冗余和重复索引

重复索引:
MySQL允许在相同列上创建多个索引,但这样需要单独维护重复的索引,并且优化查询的时候也需要逐个进行考虑,会影响性能,应该避免这么做.

冗余索引
如果已经创建了索引(A, B),在创建索引(A),那么就是冗余索引,因为它只是前一个索引的前缀, 如果再创建(B, A), 则不是冗余索引.

冗余索引通常发生在表添加新索引的时候。如增加一个新的索引(A, B),而没有扩展已有索引(A),导致(A)成为冗余索引。或者将索引扩展为(A, 主键ID),对InnoDB来说,主键已经包含在二级索引中了,因此也是冗余的.

解决方法是: drop掉重复和冗余索引即可.

索引和锁

索引可以让查询锁定更少的行. nnoDB只有在访问行的时候才会对其加锁, 而索引能够减少InnoDB访问的行数, 从而减少锁的数量.
但这只有当InnoDB在存储引擎能够过滤掉不需要的行时才有效,如果索引无法过滤掉无效的行,那么在InnoDB检索到数据并返回给服务器层之后,MySQL服务器才能应用Where子句,这时已经无法避免锁定行了:InnoDB已经锁住了这些行,到适当的时候才释放。

Explain时Extra列的'using where'的意思是: MySQL服务器将存储引擎返回行以后再应用where过滤条件.

InnoDB的行锁是建立在索引的基础之上的,行锁锁的是索引,不是数据,所以提高并发写的能力要在查询字段添加索引.

索引案例学习

  1. 索引排序和索引查询经常有矛盾:
    如果使用某个索引进行范围查询, 就无法再使用另一个索引(或该索引的后续字段)进行排序了.

  2. 范围条件查询和等值条件查询有区别:
    对于范围条件查询, MySQL无法再使用范围列后面的其他索引了, 而对于"多个等值条件查询"则没有这个限制.

select actor_id from actor where actor_id>45; -- 范围查询
select actor_id from actor where actor_id in (1, 4, 99); -- 等值查询

优化排序

-- 索引(sex, rating)
select <cols> from profiles where sex='M' order by rating limit 10;

上面的排序用到了索引, 速度是很快的. 但是当翻页时, 靠后的查询仍然会很慢:

-- 索引(sex, rating)
select <cols> from profiles where sex='M' order by rating limit 100000, 10;

原因是: MySQL需要每个满足条件的都回表取到行数据, 然后丢弃. 这样会丢弃前面大量不需要的行.

这时可以使用延迟关联的技巧, 通过覆盖索引查询返回需要的主键, 再根据这些主键关联原表获得所需要的行:

-- 索引(sex, rating)
select <cols> from profiles inner join 
(select id from profiles where sex='M' order by rating limit 100000, 10) 
as x using(id);

维护索引和表

即使用正确的类型创建了表并加上了合适的索引后,还需要维护表和索引来确保它们正常工作,目的如下:

  1. 找到并修复损坏的表
  2. 维护准确的索引统计信息
  3. 减少碎片

找到并修复损坏的表

可以通过CHECK TABLE检查是否发生了表错误
可以用REPAIR TABLE或者一个不作任何操作的ALTER操作来修复表

更新索引统计信息

MySQL的查询优化器会通过2个API来了解存储引擎的索引值的分布信息, 以决定如何使用索引:

  1. records_in_range(),通过向存储引擎传入两个边界值获取在这个范围大概有多少条记录
  2. info(),返回各种类型的数据,包括索引的基数(每个键值有多少条记录)

InnoDB会在表首次打开, 或者执行analyze table, 抑或表的大小发生非常大的变化时,计算索引的统计信息.

查看索引或表统计信息sql语句:

Select * from information_schema.statistics where table_name='actor' and table_schema='sakila’;

show index from sakila.actor;

show table status from sakila where name='actor';

注意: 查看索引统计信息可能会导致统计信息的更新, 造成性能问题.

减少索引和数据的碎片

B-Tree索引可能导致碎片化,会导致查询效率降低。有三类数据碎片

  1. 行碎片:数据行被存储到多个地方的多个片段中
  2. 行间碎片:逻辑上顺序的页,或者行在磁盘上不是顺序存储的
  3. 剩余空间碎片化:数据也中有大量的空余空间

对于MyISAM表,三类碎片都可能发生,InnoDB不会出现短小的行碎片.

下面三种方式都可以消除碎片化:

  1. OPTIMIZE TABLE
  2. 导入导出数据
  3. 不做任何操作的ALTER TABLE(标准版MySQL该方法只会消除聚簇索引的碎片化, 可以先删除所有索引, 再alter table, 再重建索引来消除索引的碎片化)

总结

选择索引以及利用索引查询时的三个原则:

  1. 单行访问是很慢的. 最好读取的块中包含尽可能多需要的行,使用索引可以创建位置引用以提升效率.
  2. 按顺序访问范围数据是很快的,原因如下:
  • 顺序I/O不需要多次磁盘寻道,比随机I/O快
  • 如果服务器能够按顺序读取数据,那么就不再需要额外的排序操作,并且GROUP BY查询也无须再做排序和将行按组进行聚合计算了
  1. 索引覆盖查询是很快的, 若一个索引包含了查询需要的所有列, 那就不需要再回表查询, 这就避免了大量的单行访问, 而第1点已经写明单行访问是很慢的.

现实使用中,很难做到每一个查询都有完美的索引,这时候需要根据需求有所取舍地创建合适的索引,而非根据惯例一刀切.

如何判断系统中的索引是否合理? 按响应时间对查询做分析, 找出消耗最长时间的查询或给服务器带来最大压力的查询, 然后检查这些查询的schema, SQL和索引结构.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343