高性能Mysql读后感(二)

高性能索引

1. 独立的列

索引列不能是表达式的一部分, 也不能是函数的参数.
/* 不能使用 user_id 列上的索引 */
select … where user_id + 1 = 5;
/* 不能使用 date 列上的索引 */
select … where TO_DAYS(CURRENT_DATE) - TO_DAYS(date) <=10;
始终将索引列单独放在比较符号的一侧

2. 前缀索引和索引选择性

对很长的字符列(BLOB, TEXT, 很长的VARCHAR)做索引列是, 可以索引开始的部分字符. —> 前缀索引
mysql也不允许索引这些列的完整长度.

不重复的索引值(基数)和数据表的记录总数(#T)的比值. 取值为 1/#T~1 之间,
比值越高查询效率越高(因为可以过滤掉更多的数据行), 查询效率越高. —> 索引选择性
唯一索引的选择性是1.

合适的长度.
前缀的”基数”应该接近于完整列的”基数”
注意
mysql 无法使用前缀索引做 order by 和 group by, 也无法做索引覆盖扫描.

ps: 后缀索引, mysql原声不支持, 但是可以吧列值 字符串反转 在入库, 后加前缀索引即可.

3. 多列索引

在多个列上建立独立的单列索引大部分情况下并不能提高 mysql 的查询性能.
mysql5.0 和更新版本引入了 索引合并 的策略.

ex:
select user_id,role_id from user_role
where user_id = 1 or role_id = 1;
可能会变成使用 union all (mysql5.0之后, 优化器做的优化)
select user_id,role_id from user_role where user_id = 1
union all
select user_id,role_id from user_role where role_id = 1 and user_id <> 1;
* 当对多个索引做相交时(多个 and 条件) —> 意味着需要一个包含相关列的多列索引.
* 当对多个索引做联合时(多个 or 条件) —> 会耗费大量CPU和内存资源在算法的缓存, 排序, 和 合并上.
特别是索引选择性不高时.
* 优化器不会把以上计算到 “查询成本” 里,有可能执行此查询计划还不如走全表扫描.

ps: optimizer_switch 关闭索引合并
ignore index 让优化器忽略掉某些索引

4. 合适的索引列顺序 (B-Tree索引)

正确的索引顺序依赖于使用该索引的查询.
并且更好的满足查询中的排序和分组. (B-Tree索引是顺序存储数据的)

如果只考虑优化 where条件, 不考虑排序和分组. 那么将选择性最高的列放在前面比较好.
否则就实际去测试吧…经验法则未必适合所有的场景.

5. 聚簇索引 (InnoDB)

并不是一种单独的索引类型, 而是一种数据存储格式.

InnoDB的聚簇索引就是在同一个结构中保存了B-Tree索引和数据行. (想想myisam)

InnoDB默认会选择表的主键(primary key), 如果没有这样的索引,
InnoDB存储引擎会隐试的定义一个”主键”来作为聚簇索引

好处:

  1. 把相关的数据保存在一起, (同一个”页面”内的数据, 这里指硬盘的页, 相对的文件碎片会少些)
  2. 数据访问更快. 因为聚簇索引将数据和索引保存在同一个B-Tree中.
  3. 覆盖索引扫描的查询可以直接使用 页节点(B-Tree的页节点) 的主键值.
    缺点:
  4. 提升I/O密集型应用的性能. 如果能把数据全部放在内存中,
    那么顺序I/O访问就不重要了,聚簇索引也就没有了优势
  5. 插入速度严重依赖于插入顺序. 顺序写入的速度最快.
    (如果不是主键顺序写入的话,在加载完数据后,最好 OPTIMIZE TABLE下)
  6. 更新聚簇索引列的代价非常高, 因为InnoDB会强制被更新的行移动到新位置(在硬盘中的位置)
  7. 在插入 或 更新时, 可能造成”页分裂”(硬盘中的).
    当某个页存满时(写入或更新数据到该行), 存储引擎会将该页分裂为两个页来存储数据该行数据
  8. 可能会导致全表扫描变慢, 在行比较稀疏(主键列不连续), 或者页分裂较多的情况下导致数据存储不连续
  9. 二级索引需要访问两次索引查找, 而不是一次.
    * InnoDB和MyISAM存放表的抽象图 *

这里写图片描述

* 在InnoDB表中按主键的顺序写入行 *

最简单的是使用 AUTO_INCREMENT 的自增列.

最好避免随机的(不连续的且值分布范围非常大的)聚簇索引. 比如UUID.

* 顺序的主键造成的问题 *
在高并发的情况下. 按主键顺序插入可能会造成争用, 并发插入可能导致间隙锁. 写入可能会成为瓶颈.

6. 覆盖索引

mysql有时也可以使用索引来直接获取列的数据. (这样做就不再需要读取数据行了)
一个索引包含(或者说覆盖)所有需要查询的字段的值 —> 覆盖索引

* 只需要扫描索引而无需回表 *

  1. 索引的条目通常是小于数据行数的. 减少数据的访问量, 减少磁盘I/O.
    因为索引通常比数据更小, 可以更好的放入内存. (尤其是MyISAM, 其可以压缩索引)
  2. 一般情况下索引是按照列值得顺序存储的(至少是在”单个硬盘页”内是这样的).
    所以对于范围查询(<,>等)会好些, 部分随机I/O访问变成顺序I/O.
    (OPTIMIZE 命令优化索引完全顺序排列)
  3. MyISAM 存储引擎只在内存中缓存索引, 数据的缓存完全依赖于操作系统.
    所以在访问数据时会进行一次系统调用,这可能有严重的系统性能问题.
  4. InnoDB 存储引擎使用的是聚簇索引, 而且 InnoDB 的二级索引在叶子节点中保存的是行的主键值,
    如果二级索引可以覆盖查询, 那么久不用再去访问一次聚簇索引进行二次查询了.

覆盖索引的使用条件

在 索引中 满足查询的成本 比 查询行 的成本 小很多 时

维护索引也是有代价的, 更新记录行时, 新增记录行时, 还有事务中 等等.

覆盖索引必须要存储索引列的值.
mysql 只能会用 B-Tree 索引做覆盖索引.
哈希索引, 空间索引, 和全文索引 都不存储索引列的值.

* 覆盖索引的 坑 *
mysql 查询优化器会在执行查询前 判断是否有一个索引能进行覆盖. 如果索引覆盖了 where 条件中的字段,
但不是整个查询涉及的字段, 如果条件为假, mysql5.5和其之前的版本也总是会回表获取数据行.
(取一些不需要的数据, 然后在过滤掉返回给客户端)

Tips:
InnoDB 的二级索引的叶子节点都包含了主键值, 变相的 相当于
key (username) == key (username,id) —> 其实 id 是 username 列上的索引中保存的值

7. 通过索引扫描来做排序

mysql 的排序方式有两种:
1. 普通的排序操作 file_sort
2. 按索引顺序扫描
如果有 join 操作的时候, order by 后的所有字段都是第一张表的时候, 才能使用到索引来排序.
并且也需要满足 索引的最左前缀原则

ps:
key i_date_username_age (date, username, age)

* 但是 如果将排序的第一个字段被制定为常量时,可以不遵循最左前缀原则 *
select … WHERE date = 2017-08-08 order by username, age
* 排序的顺序也很重要 ASC / DESC *
排序不同,不能使用索引
select … WHERE date = 2017-08-08 order by username asc, age desc
* 注意范围条件 是不能使用索引来排序的 *

select … where date < 2017-08-08 order username, age

8. 压缩索引

针对 MyISAM 引擎, 减小索引的大小, 可以让更多的索引放在内存中.

9. 冗余和重复的索引

每创建一个索引, mysql 都要对其进行维护, 并且优化器在优化的时候, 也会去逐个进行考虑.
如果有了(a, b), 有建了(a), 那么(a)就是重复的索引了, 可以删除掉.
但是(b,a)相对而(a,b)来说就不是冗余索引.
对于 InnoDB 存储引擎, (a,id) 索引, id 就是多余的了, 因为索引 a 的值就是其对应的 id.

10. 未使用的索引

删除之.
percona toolkit —> pt-index-usage

11. 索引和锁

索引可以让 锁定 根据索引过滤出来的数据行.
InnoDB只有在访问数据行的时候, 才会对其加锁. 而索引可以减少InnoDB访问的数据行.
InnoDB 根据索引检索数据返回给mysql服务器层后, mysql 服务器根据where字句过滤数据.
此时, 这些数据行已经被锁住了.
全表扫描的话就等于是锁表了.
ps: InnoDB在 二级索引上使用的是 共享(读)锁, 主键索引的时候是 排他(写)锁.