Java技术升华自留地mysql 知识库

阿里巴巴Java手册——MySQL数据库索引规约理解

2018-04-19  本文已影响237人  me0w
  1. 【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
    说明: 不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的; 另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
  1. 【强制】超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致; 多表关联查询时,保证被关联的字段需要有索引。
    说明: 即使双表 join 也要注意表索引、 SQL 性能。
  1. 【强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。
    说明: 索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度来确定。

索引基数cardinality=count(distinct 列名),索引的选择性=cardinality/count(*),这个数值通常在0~1之间,越接近1,查询效率越高,因为越接近1的时候,innodb引擎可以过滤的更多的行。而长度很长的varchar列会使索引变得很大且很慢,如果选择该列的部分左前缀,可以使索引选择性接近1,可以仅选择一定长度的左前缀进行索引。

  1. 【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
    说明: 索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。

b+树就是最左前缀匹配

  1. 【推荐】如果有 order by 的场景,请注意利用索引的有序性。 order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
    正例: where a=? and b=? order by c; 索引: a_b_c
    反例: 索引中有范围查找,那么索引有序性无法利用,如: WHERE a>10 ORDER BY b; 索引a_b 无法排序。

对于建一个表union_index_test(id,a,b,c),在此表上建立一个联合主键(a_b_c)


mysql> explain select * from union_index_test where a ='aa' and b='bb' order by c;
+----+-------------+------------------+------+---------------+-----------+---------+-------------+------+--------------------------+
| id | select_type | table            | type | possible_keys | key       | key_len | ref         | rows | Extra                    |
+----+-------------+------------------+------+---------------+-----------+---------+-------------+------+--------------------------+
|  1 | SIMPLE      | union_index_test | ref  | uni_index     | uni_index | 18      | const,const |    3 | Using where; Using index |
+----+-------------+------------------+------+---------------+-----------+---------+-------------+------+--------------------------+
1 row in set (0.01 sec)

mysql> explain select * from union_index_test where a='aa' order by b;
+----+-------------+------------------+------+---------------+-----------+---------+-------+------+--------------------------+
| id | select_type | table            | type | possible_keys | key       | key_len | ref   | rows | Extra                    |
+----+-------------+------------------+------+---------------+-----------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | union_index_test | ref  | uni_index     | uni_index | 9       | const |    7 | Using where; Using index |
+----+-------------+------------------+------+---------------+-----------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)

mysql> explain select * from union_index_test where a ='aa' order by c;
+----+-------------+------------------+------+---------------+-----------+---------+-------+------+------------------------------------------+
| id | select_type | table            | type | possible_keys | key       | key_len | ref   | rows | Extra                                    |
+----+-------------+------------------+------+---------------+-----------+---------+-------+------+------------------------------------------+
|  1 | SIMPLE      | union_index_test | ref  | uni_index     | uni_index | 9       | const |    7 | Using where; Using index; Using filesort |
+----+-------------+------------------+------+---------------+-----------+---------+-------+------+------------------------------------------+

可以看到extra里面额外利用了filesort进行排序。

  1. 【推荐】利用覆盖索引来进行查询操作, 避免回表。
    说明: 如果一本书需要知道第 11 章是什么标题,会翻开第 11 章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。
    正例: 能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效果,用 explain 的结果, extra 列会出现: using index。

覆盖索引指的是要查询的数据列都包含在索引中。
还是对于表tt(id,a,b,c),在此表上建立一个联合主键(a_b)
当要返回所有列的时候,extra列没有using index。

mysql> explain select * from tt where a='aa' and b='bb';
+----+-------------+-------+------+---------------+--------+---------+-------------+------+-----------------------+
| id | select_type | table | type | possible_keys | key    | key_len | ref         | rows | Extra                 |
+----+-------------+-------+------+---------------+--------+---------+-------------+------+-----------------------+
|  1 | SIMPLE      | tt    | ref  | tt_a_b        | tt_a_b | 14      | const,const |    1 | Using index condition |
+----+-------------+-------+------+---------------+--------+---------+-------------+------+-----------------------+
1 row in set (0.00 sec)

如果只返回索引里面的列, extra 列出现: using index。如果返回索引里的列和主键,也会返回using index,这是因为二级索引的叶子节点是有主键列的。

mysql> explain select a,b  from tt where a='aa' and b='bb';
+----+-------------+-------+------+---------------+--------+---------+-------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key    | key_len | ref         | rows | Extra                    |
+----+-------------+-------+------+---------------+--------+---------+-------------+------+--------------------------+
|  1 | SIMPLE      | tt    | ref  | tt_a_b        | tt_a_b | 14      | const,const |    1 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------------+------+--------------------------+
1 row in set (0.00 sec)
  1. 【推荐】利用延迟关联或者子查询优化超多分页场景。
    说明: MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。
    正例: 先快速定位需要获取的 id 段,然后再关联:SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
  1. 【推荐】 SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts最好。
    说明:
    1) consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
    2) ref 指的是使用普通的索引(normal index) 。
    3) range 对索引进行范围检索。
    反例: explain 表的结果, type=index,索引物理文件全扫描,速度非常慢,这个 index 级别比较 range 还低,与全表扫描是小巫见大巫。

用explain来解释执行计划,type列指的是MySQL在表中找到所需行的方式。常见类型如下:

ALL index range ref eq_ref const,system NULL

从左到右,查找性能由差到好。

  1. 【推荐】建组合索引的时候,区分度最高的在最左边。
    正例: 如果 where a=? and b=? , a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即
    可。
    说明: 存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如: where a>?and b=? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。

建立组合索引时(a,b)时,索引会先按照a排序,再按照b排序

10.【推荐】 防止因字段类型不同造成的隐式转换, 导致索引失效。
11.【参考】创建索引时避免有如下极端误解:
1) 宁滥勿缺。 认为一个查询就需要建一个索引。
2) 宁缺勿滥。 认为索引会消耗空间、严重拖慢更新和新增速度。
3) 抵制惟一索引。 认为业务的惟一性一律需要在应用层通过“先查后插”方式解决。

一些基本知识(都是基于innodb存储引擎)
一、innodb引擎
1.1 InnoDB体系架构
image.png
后台线程
[mysqld]
innodb_purge_threads=1

从1.2版本之后,可以设置多个purge线程,可以加快undo页的回收。

内存

查看缓存池实例show variables like 'innodb_buffer_pool_instances' \G;

image.png
还可以通过information_schema库里面的表来看缓存池的状态
innodb_buffer_pool_stats表

LRU List、FreeList 和FlushList


缓冲池的使用情况

缓存池命中率:
(1-innodb_buffer_pool_reads/innodb_buffer_pool_read_request)*100%

二、聚集索引和非聚集索引的结构

在innodb,表结构称之为索引组织表。innodb的索引的底层结构都是B+树,B+树包括叶子节点和非叶子节点。索引包括聚集索引和二级索引(secondary index),每个表只有一个聚集索引,是根据主键顺序存放的,聚集索引的叶子节点保存了行记录。mysql行由以下部分组成:

DB_TRX_ID DB_ROLL_PTR DB_ROW_ID other_rows

二级索引的叶子节点保存了所有的索引字段以及主键,如果where条件是二级索引,那么先要通过二级索引定位到主键,再去聚集索引里面获取其他字段。

三、多版本并发控制和二级索引 InnoDB multiversion concurrency control(MVCC)
InnoDB存储结构

rollback segment里面的回滚(undo)日志分为delete日志和update日志。插入回滚日志只有事务回滚的时候才会被用到,而且可以在事务提交时丢弃。更新回滚日志可以在一致性读中使用,它们只有在所有事务都执行完毕后才可以删除,因为InnoDB在一致性读中分配了一个快照,需要根据update undo log中的信息重建一个更早版本的数据库的行。(Update undo logs are used also in consistent reads, but they can be discarded only after there is no transaction present for which InnoDB has assigned a snapshot that in a consistent read could need the information in the update undo log to build an earlier version of a database row.这个长句给我看哭了~~~)
所以要经常性的提交事务,包括哪些只有重复读的事件。否则,innodb就不能及时丢弃update重做日志,rollback segment就会占用太多表空间。
在innodb多版本模式下,行删除并不是执行sql语句后立即物理性地从数据库移除,innodb会在它丢弃了update undo log之后,物理性移动对应的行和他的索引记录。这个移除操作称为purge,他十分迅速,一般和sql 声明执行删除同时发生。

聚集索引的更新操作是在原始位置(in-place)操作的,他们指向旧版本记录的指针列可以重构。二级索引则不是这样,二级索引不含有隐藏的系统列更新操作也不是in-place的。
当一个二级索引列更新时,旧的二级索引记录会被标记为deleted,新的记录会被插入,然后删除的记录会产生purge操作。当二级索引被标记为删除或者二级索引页被新的的事务更新时,InnoDB在聚集索引中查找数据库记录。在聚集索引中,检查该记录的DB_TRX_ID(事务版本号),如果记录在这个读事务开始之后被修改了,可以从回滚日志中回复正确的记录版本。如果一个二级索引的记录被标志为已经删除,或者二级索引页被一个新的事务更新了,那么覆盖索引技术接没有用了,只能从聚集索引中获取数据而不能直接从二级索引结构中直接返回。

ICP

index condition pushdown(ICP)是MySQL5.6启用的新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式。没有启用ICP,存储引擎遍历整个索引定位到基表中的行,然后将所有行返回至MySQL server层,server层根据where条件来定位行。当ICP启用时,如果经评估可以使用部分存在于索引列的where条件来查找,server层将这部分条件查询列下发到存储引擎层进行查找。然后,存储引擎通过使用索引项来检索pushed索引条件,并且只有行记录的值满足where条件时才将其返回至server层。ICP能减少引擎层访问基表的次数和MySQL Server 访问存储引擎的次数。
ICP使用条件:

四、分区

创建一个数据库


建库
对应的文件夹下面自动创建了一个opt文件 空的数据库
创建一个分区表之后,数据库的表结构
create table partition_test01(
id int auto_increment,
birth_day datetime not null,
sex char(1),
primary key(id,birth_day))
engine=innodb default charset=utf8 
partition by range(TO_DAYS(birth_day))(
partition p20180417 values less than (TO_DAYS('2018-04-18')),
partition p20180418 values less than (TO_DAYS('2018-04-19'))
);
对应的文件夹下面的文件如下,可以看到数据库为每个分区创建了一个文件: 包含分区表的数据库目录文件
上一篇下一篇

猜你喜欢

热点阅读