mysql索引-进阶篇(为查询创建索引)

innodb索引基础

很多调优人员(尽管没经验)认为,如果一个SQL语句使用了索引,那这个SQL就是被很好地优化过的,我对此感到很惊讶。你应该总是问自己,“这是不是可用的最好的索引?”或“再添加另外一个索引能否提升响应性能?”,又或者“全表扫描会不会更快地返回结果?”

这是一个系列~ 会持续更新

1、为SELECT语句创建索引

三星索引–理想的索引

  • 第一颗星:与一个查询相关的索引行是相邻的,或者足够靠近的。即,最小化索引必须扫描的索引片的宽度
  • 第二颗星:索引行的顺序和查询语句的需求一致
  • 第三颗星:索引行包含查询语句中的所有列

通俗的解释:第一颗星决定了扫描行数;第二课星决定了是否需要对结果集排序;第三颗星决定了是否需要回表操作【注释1,回表】

宽索引:把至少包含第三颗星的索引称为对应查询语句的宽索引(mysql里面称为覆盖索引)

实例

例子1 (简单sql的三星索引):

SELECT CNO, FNAME FROM CUST WHERE LNAME = :LNAME AND CITY = :CITY ORDER BY FNAME

如上简单场景,一个三星索引的构造是比较简单的:

  • 满足第一颗星,取出所有等值谓词的列(WHERE LNAME = …)。把这些列作为索引开头的列:idx(LNAME, CITY, …) 或者 idx(CITY, LNAME)
  • 满足第二颗星,取出ORDER BY列,加入索引,得到:idx(LNAME, CITY, FNAME, …)或者 idx(CITY, LNAME, FNAME, …)。这样结果集的记录就无需排序了。
  • 满足第三颗星,将剩余的列加入索引中,得到:idx(LNAME, CITY, FNAME, CNO)或者idx(CITY, LNAME, FNAME, CNO)

例子2(范围谓词和三星索引):

SELECT CNO, FNAME FROM CUST WHERE LNAME BETWEEN :LNAME1 AND LNAME2 AND CITY = :CITY ORDER BY FNAME

分析:

  1. 如果要满足第三星,毋庸置疑我们需要把 LNAME, CITY, FNAME, CNO都加入索引。现在我们要考虑的就是索引顺序。
  2. 如果想要避免排序(满足第二星),必须把FNAME加在LNAME的前面
  3. 如果将FNAME加在LNAME前面,将破坏第一星-- 查询行索引需要相邻

这时候就是一个取舍问题了,是idx_A(CITY, LNAME, FNAME, CNO) 还是 idx_B(CITY, FNAME, LNAME, CNO)?

通常这不是一个很难的选择,磁盘IO的效率应该是比CPU对结果进行排序的效率低的。
但是这个选择也不绝对,通常我们查列表,只需要要给用户显示屏幕的一页(例如:limit 20),这时候idx_B 可能会比idx_A快得多。

一些建议:

机械性地为每个查询设计最佳索引也是不明智的,因为索引的维护可能会使得一些程序速度太慢或者使磁盘负载超负荷。最佳索引是个好的开端,但在决定为一个查询建立最佳索引前需要考虑这个索引是否多余。

索引顺序

select * from A where b = 1 and c = 1 

针对上面的sql, 如果sum(b) = 1000 ;sum© = 100; 应该建 idx(c, b) 而不是 idx(b, c)。 当然这个依赖查询的入参,所以需要具体情况具体考虑

2. 为表连接设计索引

在1中我们讲到简单SELECT(单表)的索引设计,但在实际开发过程中,往往需要进行多表联合查询。接下来,我们讨论如何为表连接设计索引

2.1 前提知识

  • 连接谓词: 定义表与表之间的连接关系的谓词。
  • 本地谓词:只用于访问一张表的谓词。

join算法有三种,分别是:Nested Loop Join,Hash join,Sort Merge Join,由于mysql只支持Nested Loop Join,所以我们只谈这个。
在外表中找到一行满足本地谓词的记录,然后再从内层表中找到这一行数据相关的记录。

for (row r1: 外表table) {
    for (row r2: 内表table) {
        ...
    }
}

2.2 预测表的访问顺序

循环嵌套连接方式,表的访问顺序可能会对性能产生巨大的影响。未确定最佳访问顺序之前,是无法设计理想的索引。

经验法则:
将包含最低数量本地行的表作为外层表。
本地行的数量是指最大的过滤因子过滤本地谓词之后所剩余的行数。

经验法则忽略了:

  1. 排序
  2. 很小的表:非常小的表及其索引可能长期存在数据库的缓冲池中,至少在一个连接查询中,没有页会被读取多次。这样的表不是外层表时,对其大量的随机读也不会称为问题。
  3. 聚簇比例。索引中行的顺序和表中行的顺序的关联性(聚簇索引,该关联性在表重组后为100%)

2.3 索引设计

在连接谓词和本地谓词有索引 – 但这个需要先分析表的访问顺序,不然可能会导致一些冗余的索引。

在一个循环嵌套连接中,内层表通常需要好的宽索引,且以连接谓词做前导列。

总结:
对于为表连接设计索引时,我们先分析表的访问顺序,再结合‘索引设计与优化 - 1、为SELECT语句创建索引’提到的单表索引设计,大致就能设计出比较优秀的索引了。

注释

注释1,回表

mysql innodb引擎索引分为聚簇索引和普通索引(非聚簇索引),聚簇索引叶子节点保存了索引值、行记录数据,普通索引保存了主键(聚簇索引)的值

普通索引的查询过程:
从普通索引查询到主键,再根据主键查聚簇索引,定位到到行记录数据

回表:
先通过普通索引的值定位聚簇索引值,再通过聚簇索引的值定位行记录数据,需要扫描两次索引B+树,它的性能较扫一遍索引树更低。


[1] 数据库索引设计.电子工业出版社

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章