基础知识

Innodb存储引擎

  1. 支持行锁
  2. 支持事务;

Myisam存储引擎

  1. 只支持表锁;
  2. 不支持事务;

常见索引列表

  • 独立的列
  • 前缀索引(索引选择性)
  • 多列索引(并不是多个单列索引,索引顺序很重要)
  • 聚簇索引(保存了完整的数据记录,数据文件本身就是索引文件)
  • 覆盖索引(一个索引包含或覆盖所有查询的字段值,对于Innodb来说,可以避免对主键索引的二次查询)
  • 使用索引扫描做排序
  • 冗余索引和重复索引

索引对CRUD的影响

  • 一般来说,增加索引是为了提升查询速度,索引越多,查询速度应该越快(当索引太多时,由于选择判断的原因,查询不一定会变快);
  • 增加索引,create/delete/update等操作都会相应的变慢,尤其是新增索引导致内存瓶颈的时候尤甚;

mysql存储引擎、索引、服务器

  • 索引是在存储引擎层面实现;

示例:actor_id建立了索引(Innodb)

select actor_id from table where actor_id<5 and actor<>1;

----output-----
actor_id
2
3
4

执行过程:

  1. 存储引擎层面:使用索引范围扫描(actor_id<5),返回给服务器{1,2,3,4}这几条记录;
  2. 服务器层面:过滤存储引擎返回的记录,actor_id<>1,过滤掉actor_id=1的记录;
  3. 最终返回{2,3,4}记录;

从上面的执行过程可以看到:

  1. 存储引擎过滤-->服务器过滤-->返回给客户端;
  2. 如果存储引擎层面能够过滤掉足够多的无需记录,性能会有很大提升: 这部分主要是索引发挥的作用;
  3. 存储引擎返回的{1,2,3,4}条记录,都会加上锁(Innodb支持行锁),虽然{1}不是所需的:索引能够减少锁的个数,有效的提升并发性;
  4. 服务端过滤掉{1},会通知存储引擎,此时存储引擎可以将{1}的行锁解除,不必等到事务提交;

关于Innodb、索引、锁

很少有人知道的细节

Innodb在二级索引上使用共享(读)锁,但访问主键索引需要排他(写)锁。(不太理解这样做的好处

这消除了使用覆盖索引的可能性,并且使得select for updatelock in share mode或非锁定查询要慢很多;(
没搞明白为什么**)


索引优化示例

在where中使用IN来匹配最左前缀

比如QQ使用性别、年龄、地区等来筛选用户,大部分时候,都会将性别、年龄、地区作为过滤条件;

而性别只有m/f 两种分类,选择性很差,但是搜索频率相当高 ,此时我们可以对性别进行索引,排在最左列,如:

alter table xxx add key(sex,region,age);

如果某次查询,没有将sex作为查询条件,该如何使用上面的索引呢?

技巧:使用IN

select * from table where sex in ('m','f') and region ='xxxx' and age > 12;

上面使用IN来过滤,并不会过滤掉任何行,但是可以让mysql使用最左前缀进行优化;

mysql会将上述的查询优化为:

select * from table where sex = 'm'  and region ='xxxx' and age > 12;
and
select * from table where sex = 'f' and region ='xxxx' and age > 12;

使用IN进行优化的适用条件

只有当该列的值比较少时,使用IN来进行最左前缀匹配优化才是可行的,如果IN的列表特别长,性能可能会适得其反;

示例:

select * from table where sex in ('m','f') and region in ('beiJing', 'shanghai','suzhou') and age > 12;

优化器会将上述查询转换为:2*3=6中组合,没增加一个IN,组合数都会成指数级增长;


将需要做范围查询的列放到索引的最后面

如果需要对性别、地区、年龄做索引,索引顺序应当如何?

根据选择性越高,就应该放在索引的最前列原则,年龄和地区可能肯定被放在性别前面;

但是根据查询频率来看,每次基本上都会使用性别查询,根据上一条的优化原则(在where中使用IN来匹配最左前缀),性别放在第一列反而更合适;

查询年龄时,基本都是 age>xxx and age<xxx,当然可以使用IN进行优化,但是age的范围太广,IN优化反而达不到好的效果;

为了能够匹配更多的最左前缀列,将范围查询的列放到索引的最后面放在最后反而更合适,因为Mysql无法使用【范围列】后面的索引列

alter table xxx add key("sex","region","age");

避免多个范围条件

因为Mysql无法使用【范围列】后面的索引列

所以当出现多个范围条件时,最多只能使用其中的1列进行索引,无法同时使用索引;


冗余索引与重复索引

示例(以Innodb的B+Tree索引为例)

有索引(A,B) ==>(A)为冗余索引,因为索引(A)只是索引(A,B)的前缀索引;
有索引(A,B) ==>(A,B,Id)是冗余索引,其中ID为主键,因为Innodb中,二级索引本身就包含主键;
有索引(A,B) ==>(B,A)不是冗余索引
有索引(A,B) ==>(B)不是冗余索引
有B-Tree索引(A,B) ==>Hash索引(A,B)不是冗余索引 (索引类型不同)

一般来讲,应当消除冗余索引

比如最初有如下查询:假设有索引(state_id)

select count(*) from table where state_id = 'xxx';   //`覆盖索引`

后来增加新的查询

select state_id, city, address from table where state_id = 'xxx';   //变为`非覆盖索引`

为了提高第二条语句的查询效率,应该扩展(state_id)索引

alter table xxx drop key state_id,
add key state_id_2(state_id, city, address);

如此修改后,查询1和查询2都可以使用新的索引:state_id_2(state_id, city, address);

有时冗余索引能提升查询速度(以空间换取查询时间)

还是以上述查询为例,索引由 state_id(state_id)变为state_id_2(state_id, city, address),势必导致索引变大,占用的空间更多;

相同内存下,索引载入内存的数量减少,同时索引的遍历变慢,如果数据量很大,将导致查询1变慢,尤其是使用了前缀压缩索引时更为明显;

如果此时允许冗余索引,将会是查询1和查询2都变快,当然,势必导致占用的存储空间更大,也会影响update/create/delete的性能;


使用索引扫描做排序

Innodb的B+TREE是按照列的顺序进行排列的,因此可以使用索引扫描做排序

使用索引扫描做排序的条件

  1. 索引的列顺序与order by的列顺序完全相同(最左前缀原则使然);
  2. 所有列的排序方向都一致,要么都正序,要么都反序(因为多列索引的排序顺序是一致的):如果隔列排序顺序不同,可以考虑新增字段,存放对应列的反转串;

索引扫描读取数据 vs 全表扫描读取数据 (性能比较)

  • 索引扫描排序时,如果需要进行主键的二次索引,通常都是随机IO
  • 全表扫描,通常都是顺序IO

因此按索引顺序读取完整记录(涉及到主键的二次索引查询)的速度通常比顺序的全表扫描慢;

结论

使用索引扫描排序时,尽量能使用覆盖索引,并且排序的数据条数尽量小,避免造成大量的随机IO

索引排序最重要的用法(order by + limit

上面讲到了,索引排序如果涉及到主键的二次查询,通常都是随机IO,如果数据量很大,这势必严重影响性能;

如果在查询语句中有limit做限制,通常数据量都很小,即使是随机IO,对性能的影响也可以忽略不计;

因此,索引排序最重要的用法是使用order bylimit的结合使用;

特殊情况:可以是索引排序不满足最左前缀原则

在where字句中,对索引的前导列使用了常量值时,可以不满足最左前缀原则;

示例:

KEY `poolId_domainID_idx` (`pool_id`,`domain_id`)  //索引

查询:前导列pool_id使用常量

MySQL [test]> explain select * from pool_configs where pool_id = 'xxx' order by domain_id ;
+----+-------------+--------------+------+-----------------------------------+---------------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+------+-----------------------------------+---------------------+---------+-------+------+-------------+
| 1 | SIMPLE | pool_configs | ref | pool_id_index,poolId_domainID_idx | poolId_domainID_idx | 96 | const | 1 | Using where |
+----+-------------+--------------+------+-----------------------------------+---------------------+---------+-------+------+-------------+
1 row in set (0.00 sec)

mysql在进行优化时,会将上述查询优化为:order by pool_id, domain_id(优化后其实是满足最左前缀原则的)

MySQL [test]> explain select * from pool_configs where pool_id = 'xxx' order by pool_id, domain_id ;
+----+-------------+--------------+------+-----------------------------------+---------------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+------+-----------------------------------+---------------------+---------+-------+------+-------------+
| 1 | SIMPLE | pool_configs | ref | pool_id_index,poolId_domainID_idx | poolId_domainID_idx | 96 | const | 1 | Using where |
+----+-------------+--------------+------+-----------------------------------+---------------------+---------+-------+------+-------------+
1 row in set (0.00 sec)

覆盖索引

对于Innodb存储引擎,主键使用聚簇索引(包含所有的记录),而二级索引包含主键值,因此正常查询时,一般都会进行主键索引的二次查询(二级索引先找到主键值,然后在根据主键值找到对应的记录);

如果二级索引中包含了要查询的所有数据列,则无需在根据主键值查询完整的记录,减少一次索引查询,可以大大提升查询性能。

示例:

二级索引:poolId_domainID_idx (pool_id,domain_id)包含了两列

| pool_configs | CREATE TABLE `pool_configs` (
`id` char(32) NOT NULL,
`pool_id` char(32) NOT NULL,
`region_name` varchar(50) DEFAULT NULL,
`domain_id` char(32) DEFAULT NULL,
`value` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `pool_id_index` (`pool_id`),
KEY `poolId_domainID_idx` (`pool_id`,`domain_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |

查询pool_iddomain_id将使用覆盖索引;

如下所示:Extra的内容为Using index,即表示使用覆盖索引;

MySQL [test]> explain select pool_id , domain_id from pool_configs ;
+----+-------------+--------------+-------+---------------+---------------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+-------+---------------+---------------------+---------+------+------+-------------+
| 1 | SIMPLE | pool_configs | index | NULL | poolId_domainID_idx | 193 | NULL | 71 | Using index |
+----+-------------+--------------+-------+---------------+---------------------+---------+------+------+-------------+

对比不是使用覆盖索引的查询;

下面的语句查询所有列,而并没有任何索引能覆盖所有列,因此无法使用覆盖索引

MySQL [test]> explain select * from pool_configs ;
+----+-------------+--------------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | pool_configs | ALL | NULL | NULL | NULL | NULL | 71 | NULL |
+----+-------------+--------------+------+---------------+------+---------+------+------+-------+

优化技巧

查询时尽量不要使用*返回所有列,因此可能导致无法使用覆盖索引

在大多数存储引擎中,覆盖索引只能覆盖那些只访问索引中部分列的查询。

因此查询时,如果只需要个别的列(如:只需要 id, pool_id, domain_id),则不要使用*返回所有列;

因为Innodb的二级索引中包含主键值,所以Innodb查询包含主键列不会对覆盖索引造成影响;

使用延时关联技术优化查询,使用覆盖索引

该技巧较高,目前自己还无法掌握,并且优化后的查询性能与数据分布密切相关;

充分利用Innodb二级索引包含主键值的特性,优化查询,达到覆盖索引查询效果

上面的示例中,索引poolId_domainID_idx只包含poolIddomainId,并不包含主键值id,根据Innodb二级索引包含主键值的特性,

如下查询也会使用覆盖索引:

MySQL [test]> explain select id , pool_id , domain_id from pool_configs ;
+----+-------------+--------------+-------+---------------+---------------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+-------+---------------+---------------------+---------+------+------+-------------+
| 1 | SIMPLE | pool_configs | index | NULL | poolId_domainID_idx | 193 | NULL | 71 | Using index |
+----+-------------+--------------+-------+---------------+---------------------+---------+------+------+-------------+
1 row in set (0.00 sec)

Innodb聚簇索引--主键选择

在Innodb中表中按主键顺序插入行

  • 可以选择一个自增列(auto_increment)作为主键;
  • 避免使用UUID作为主键(尤其是比较长随机的):随机插入时,会导致大量的页分裂碎片

何为页分裂

mysql在将数据记录写入磁盘之前,会先在内存中缓存多条记录,熟悉内存模型的应该知道内存分页,好像默认大小是16K;

当记录个数达到一定值后,总数据大小接近内存分页大小,此时才会将内存中的数据刷入到磁盘(磁盘也是分页的),充分利用内存缓存和分页机制;

上述过程中,这些记录都存储在同一页中;

当聚簇索引的主键为随机值时,因为Innodb会按照主键顺序存储,所以通常都会将新插入的记录,插入到已经刷入到磁盘的--分页中(此页可能已经满了),

如果是这样,将会导致磁盘数据重新读入内存,并导致页分裂(因为一页大小是固定的,插入新记录,肯定需要页分裂);

Innodb使用自增列作为主键--缺点

高并发场景下,所有的插入同时进行,会导致主键的上界成为热点,高并发可能会导致间隙锁的激励竞争(不太理解为什么会导致间隙锁的竞争)。

另一个热点是auto_increment锁机制:这个很好理解;

解决方案

  1. 考虑重新设计表或应用;
  2. 更改配置:innodb_autoinc_lock_mode (这个没有研究过,具体细节不清楚);

总结

  1. 自增列作为主键,在高并发情况下,可能会有激烈的所竞争;
  2. 在非高并发情况下,使用自增的列作为主键,是比较好的选择;

多列索引-索引列顺序选择

将`选择性`最高的列放到索引最前列。(这个法则可能没有想象的重要,通常不如避免随机IO和排序重要)

选择性:是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值;

Index Selectivity = Cardinality / #T,具体可以参见:http://www.cnblogs.com/ssslinppp/p/8206093.html

说明

下面介绍的基于平均选择性(适用于绝大部分情况)基于数据分布--选择性(适用于少部分特殊情况),并没有将排序、分组、范围查询考虑进去;

排序、分组、范围查询对性能造成的影响不能忽视。

示例:

select * from pool_configs where pool_id='7b8f0f5e2fbb4d9aa2d5fd55466dsij2' and domain_id = 'aed318549ff511e6b70cfa163eb66336';

如何决定索引的列顺序(pool_id, domain_id)还是(domain_id,pool_id);

基于平均选择性(适用于绝大部分情况)

查看索引选择性:

select count(distinct pool_id)/count(*) as poolIdSelect,
count(distinct domain_id)/count(*) as domainIdSelect,
count(*) from pool_configs; +--------------+----------------+----------+
| poolIdSelect | domainIdSelect | count(*) |
+--------------+----------------+----------+
| 0.0676 | 0.0270 | 74 |
+--------------+----------------+----------+

从上面可以看出,poolId的选择性更好一些,创建索引时应该使用:(注:该种方式只是考虑到了平均选择性,并没有考虑到数据的分布情况)

alter table pool_configs add key('pool_id','domain_id');

基于数据分布--选择性(适用于少部分特殊情况)

在上面介绍了基于平均选择性(适用于绝大部分场景)的情况,现在考虑下面的一种特殊情况;

假设有一个表,包含字段 groupId和userId,大部分情况下,使用如下索引肯定是好的:

alter table xxx add key('groupId','userId');(大部分情况下,比较好)
alter table xxx add key('userId','groupId');(可能不是好选择)

但是,如果有个特殊的GroupId(可能是管理员组Id,假设值为:xxxx001),而几乎所有的记录都包含该GroupId(可能数据是从某处迁移过来,默认使用该GroupId),

select count(*), sum(groupId=xxxx001), sum(userId=xxx) from tablexxx \G; 

-----output-----
count(*): 100W
sum(groupId=xxxx001): 99W
sum(userId=xxx):5w

则根据上面的平均选择性创建的索引可能几乎不起作用,这种特殊的数据分布,使用平均选择性可能会是情况变得更糟,甚至摧毁整个系统;

解决方案:

在应用层面解决,不允许查询groupId=xxx001的语句执行,特殊情况特殊对待;


索引合并

如下sql

select * from where busiId=001 or orgaId=004;

上面的sql使用or,mysql在进行查询时,可能会使用索引合并来进行优化;

可以通过explain来查看是否进行了索引合并(在Extra会出现Using union(xxx,xxx)字样);

索引合并

简单描述就是:

  1. 查询busiId=001的所有记录;
  2. 查询orgaId=004的所有记录;
  3. 根据orand决定对上述的结果进行或与运算;

索引合并,有时候能起到很好的优化效果,有时候并非如此;

出现索引合并,通常意味着,表上的索引设计的很糟糕

比如:

  • 有1w条数据;
  • busiId=001查询出8000条数据;
  • orgaId=004查询出7000条数据;
  • 则进行union合并时,总共需要处理1.5w条数据,还不如直接进行全表扫描效率高;
  • 索引合并通常会消耗更多的CPU和内存资源,可能还不如如下sql有效率;
select * from where busiId=001
union all
select * from where orgaId=004;

优化方式

最好的方式,还是考虑表结构和索引是否最优;

方式1:关闭索引合并功能,optimizer_switch;

方式2:使用ignore index提示,让优化器忽略掉某些索引;

【mysql】索引优化记录的更多相关文章

  1. mysql索引优化

    mysql 索引优化 >mysql一次查询只能使用一个索引.如果要对多个字段使用索引,建立复合索引. >越小的数据类型通常更好:越小的数据类型通常在磁盘.内存和CPU缓存中都需要更少的空间 ...

  2. Mysql 索引优化分析

    MySQL索引优化分析 为什么你写的sql查询慢?为什么你建的索引常失效?通过本章内容,你将学会MySQL性能下降的原因,索引的简介,索引创建的原则,explain命令的使用,以及explain输出字 ...

  3. 知识点:Mysql 索引优化实战(3)

    知识点:Mysql 索引原理完全手册(1) 知识点:Mysql 索引原理完全手册(2) 知识点:Mysql 索引优化实战(3) 知识点:Mysql 数据库索引优化实战(4) 索引原理知识回顾 索引的性 ...

  4. MySQL索引优化步骤总结

    在项目使用mysql过程中,随着系统的运行,发现一些慢查询,在这里总结一下mysql索引优化步骤 1.开发过程优化 开发过程中对业务表中查询sql分析sql执行计划(尤其是业务流水表),主要是查看sq ...

  5. MySQL索引优化看这篇文章就够了!

    阅读本文大概需要 5 分钟. 来源:cnblogs.com/songwenjie/p/9410009.html 本文主要讨论MySQL索引的部分知识.将会从MySQL索引基础.索引优化实战和数据库索引 ...

  6. mysql索引优化比普通查询速度快多少

    mysql索引优化比普通查询速度快多少 一.总结 一句话总结:普通查询全表查询,速度较慢,索引优化的话拿空间换时间,一针见血,所以速度要快很多. 索引优化快很多 空间换时间 1.软件层面优化数据库查询 ...

  7. mySql索引优化分析

    MySQL索引优化分析 为什么你写的sql查询慢?为什么你建的索引常失效?通过本章内容,你将学会MySQL性能下降的原因,索引的简介,索引创建的原则,explain命令的使用,以及explain输出字 ...

  8. 讲真,MySQL索引优化看这篇文章就够了

    本文主要讨论MySQL索引的部分知识.将会从MySQL索引基础.索引优化实战和数据库索引背后的数据结构三部分相关内容,下面一一展开. 一.MySQL——索引基础 首先,我们将从索引基础开始介绍一下什么 ...

  9. 深入浅出Mysql索引优化专题分享|面试怪圈

    文章纲要 该文章结合18张手绘图例,21个SQL经典案例.近10000字,将Mysql索引优化经验予以总结,你可以根据纲要来决定是否继续阅读,完成这篇文章大概需要25-30分钟,相信你的坚持是不负时光 ...

  10. 【ZZ】MySQL 索引优化全攻略 | 菜鸟教程

    MySQL 索引优化全攻略 http://www.runoob.com/w3cnote/mysql-index.html

随机推荐

  1. 检测IP地址冲突的shell脚本-check_server_ip_conflict.sh

    check_server_ip_conflict.sh 使用arping获取对应IP地址的MAC地址,如果和预料的不一致则报警: #!/bin/bash epg_addr_01="00:50 ...

  2. Cocos2dx 中的点击事件

    简单记录一下2dx的鼠标交互事件.以及精灵绑定盒的点击判定   Layer 子类的 init方法中:   auto listener = EventListenerTouchOneByOne::cre ...

  3. P2331 [SCOI2005]最大子矩阵 (动规:分类讨论状态)

    题目链接:传送门 题目: 题目描述 这里有一个n*m的矩阵,请你选出其中k个子矩阵,使得这个k个子矩阵分值之和最大.注意:选出的k个子矩阵不能相互重叠. 输入输出格式 输入格式: 第一行为n,m,k( ...

  4. java中实现Comparable接口实现自定义排序

    class Student implements Comparable{ String name; int gpa; @Override public int compareTo(Object arg ...

  5. django 浅谈CSRF(Cross-site request forgery)跨站请求伪造

    浅谈CSRF(Cross-site request forgery)跨站请求伪造(写的非常好) 本文目录 一 CSRF是什么 二 CSRF攻击原理 三 CSRF攻击防范 回到目录 一 CSRF是什么 ...

  6. (10)MySQL触发器(同时操作两张表)

    什么是触发器 触发器是与表有关的数据库对象,在满足定义条件时触发,并执行触发器中定义的语句集合.触发器的这种特性可以协助应用在数据库端确保数据的完整性. 举个例子,比如你现在有两个表[用户表]和[日志 ...

  7. hdu3746 Cyclic Nacklace KMP

    CC always becomes very depressed at the end of this month, he has checked his credit card yesterday, ...

  8. 重新生成ssh

    解决码云出现git@gitee.com: Permission denied (publickey). 很久之前好像改了ssh的一些配置,导致现在对git进行一些操作时,就会出现 当时就去百度,结果很 ...

  9. 怎样解题 (G. 波利亚 著)

    第一部分 (已看) 目的 1. 帮助学生 2. 问题,建议,思维活动 3. 普遍性 4. 常识 5. 教师和学生,模仿和实践 主要部分,主要问题 6. 四个阶段 7. 理解题目 8. 例子 9. 拟订 ...

  10. ORACLE 12C 之集群日志位置变化

    如果你还是使用 oracle 11g RAC 的目录结构方式寻找集群的日志,你会发现目录中所有的日志都是空的.actdb21:/oracle/app/12.2.0/grid/log/actdb21(+ ...