MySQL索引和SQL调优手册

MySQL索引mysql

MySQL支持诸多存储引擎,而各类存储引擎对索引的支持也各不相同,所以MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引等等。为了不混乱,本文将只关注于BTree索引,由于这是日常使用MySQL时主要打交道的索引。redis

MySQL索引和SQL调优手册

 

MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。提取句子主干,就能够获得索引的本质:索引是数据结构。算法

MySQL索引原理sql

索引目的数据库

索引的目的在于提升查询效率,能够类比字典,若是要查“mysql”这个单词,咱们确定须要定位到m字母,而后从下往下找到y字母,再找到剩下的sql。若是没有索引,那么你可能须要把全部单词看一遍才能找到你想要的,若是我想找到m开头的单词呢?或者ze开头的单词呢?是否是以为若是没有索引,这个事情根本没法完成?缓存

我们去图书馆借书也是同样,若是你要借某一本书,必定是先找到对应的分类科目,再找到对应的编号,这是生活中活生生的例子,通用索引,能够加快查询速度,快速定位。安全

索引原理服务器

全部索引原理都是同样的,经过不断的缩小想要得到数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是咱们老是经过同一种查找方式来锁定数据。数据结构

数据库也是同样,但显然要复杂许多,由于不只面临着等值查询,还有范围查询(>、<、between)、模糊查询(like)、并集查询(or)、多值匹配(in【in本质上属于多个or】)等等。数据库应该选择怎么样的方式来应对全部的问题呢?并发

咱们回想字典的例子,能不能把数据分红段,而后分段查询呢?最简单的若是1000条数据,1到100分红第一段,101到200分红第二段,201到300分红第三段……这样查第250条数据,只要找第三段就能够了,一会儿去除了90%的无效数据。但若是是1千万的记录呢,分红几段比较好?

稍有算法基础的同窗会想到搜索树,其平均复杂度是lgN,具备不错的查询性能。但这里咱们忽略了一个关键的问题,复杂度模型是基于每次相同的操做成原本考虑的,数据库实现比较复杂,数据保存在磁盘上,而为了提升性能,每次又能够把部分数据读入内存来计算,由于咱们知道访问磁盘的成本大概是访问内存的十万倍左右,因此简单的搜索树难以知足复杂的应用场景。

索引结构

任何一种数据结构都不是凭空产生的,必定会有它的背景和使用场景,咱们如今总结一下,咱们须要这种数据结构可以作些什么,其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。那么咱们就想到若是一个高度可控的多路搜索树是否能知足需求呢?就这样,b+树应运而生。

b+树的索引结构解释

 

MySQL索引和SQL调优手册

 

 

浅蓝色的块咱们称之为一个磁盘块,能够看到每一个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P一、P二、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即三、五、九、十、1三、1五、2八、2九、3六、60、7五、7九、90、99。非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如1七、35并不真实存在于数据表中。

b+树的查找过程

如图所示,若是要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找肯定29在17和35之间,锁定磁盘块1的P2指针,内存时间由于很是短(相比磁盘的IO)能够忽略不计,经过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,经过指针加载磁盘块8到内存,发生第三次IO,同时内存中作二分查找找到29,结束查询,总计三次IO。

真实的状况是,3层的b+树能够表示上百万的数据,若是上百万的数据查找只须要三次IO,性能提升将是巨大的,若是没有索引,每一个数据项都要发生一次IO,那么总共须要百万次的IO,显然成本很是很是高。

b+树性质

一、经过上面的分析,咱们知道间越小,数据项的数量越多,树的高度越低。这就是为何每一个数据项,即索引字段要尽可能的小,好比int占4字节,要比bigint8字节少一半。这也是为何b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度降低,致使树增高。当数据项等于1时将会退化成线性表。

二、当b+树的数据项是复合的数据结构,好比(name,age,sex)的时候,b+数是按照从左到右的顺序来创建搜索树的,好比当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来肯定下一步的所搜方向,若是name相同再依次比较age和sex,最后获得检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪一个节点,由于创建搜索树的时候name就是第一个比较因子,必需要先根据name来搜索才能知道下一步去哪里查询。

好比当(张三,F)这样的数据来检索时,b+树能够用name来指定搜索方向,但下一个字段age的缺失,因此只能把名字等于张三的数据都找到,而后再匹配性别是F的数据了, 这个是很是重要的性质,即索引的最左匹配特性。

MySQL 索引实现

在MySQL中,索引属于存储引擎级别的概念,不一样存储引擎对索引的实现方式是不一样的,本文主要讨论MyISAM和InnoDB两个存储引擎的索引实现方式。

MyISAM索引实现

MyISAM引擎使用B+Tree做为索引结构,叶节点的data域存放的是数据记录的地址。

下图是MyISAM索引的原理图:

 

MySQL索引和SQL调优手册

 

 

这里设表一共有三列,假设咱们以Col1为主键,则上图即是一个MyISAM表的主索引(Primary key)示意图。能够看出MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是惟一的,而辅助索引的key能够重复。若是咱们在Col2上创建一个辅助索引,则此索引的结构以下图所示:

 

MySQL索引和SQL调优手册

 

 

一样也是一颗B+Tree,data域保存数据记录的地址。所以,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,若是指定的Key存在,则取出其data域的值,而后以data域的值为地址,读取相应数据记录。

MyISAM的索引方式也叫作“非汇集”的,之因此这么称呼是为了与InnoDB的汇集索引区分。

InnoDB索引实现

虽然InnoDB也使用B+Tree做为索引结构,但具体实现方式却与MyISAM大相径庭。

第一个重大区别是InnoDB的数据文件自己就是索引文件。从上文知道,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件自己就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,所以InnoDB表数据文件自己就是主索引。

 

MySQL索引和SQL调优手册

 

 

上图是InnoDB主索引(同时也是数据文件)的示意图,能够看到叶节点包含了完整的数据记录。这种索引叫作汇集索引。由于InnoDB的数据文件自己要按主键汇集,因此InnoDB要求表必须有主键(MyISAM能够没有),若是没有显式指定,则MySQL系统会自动选择一个能够惟一标识数据记录的列做为主键,若是不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段做为主键,这个字段长度为6个字节,类型为长整形。

第二个与MyISAM索引的不一样是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的全部辅助索引都引用主键做为data域。例如,下图为定义在Col3上的一个辅助索引:

 

MySQL索引和SQL调优手册

 

 

这里以英文字符的ASCII码做为比较准则。汇集索引这种实现方式使得按主键的搜索十分高效,可是辅助索引搜索须要检索两遍索引:首先检索辅助索引得到主键,而后用主键到主索引中检索得到记录。

了解不一样存储引擎的索引实现方式对于正确使用和优化索引都很是有帮助,例如知道了InnoDB的索引实现后,就很容易明白为何不建议使用过长的字段做为主键,由于全部辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。再例如,用非单调的字段做为主键在InnoDB中不是个好主意,由于InnoDB数据文件自己是一颗B+Tree,非单调的主键会形成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段做为主键则是一个很好的选择。

如何创建合适的索引

创建索引的原理

一个最重要的原则是最左前缀原理,在提这个以前要先说下联合索引,MySQL中的索引能够以必定顺序引用多个列,这种索引叫作联合索引,通常的,一个联合索引是一个有序元组,其中各个元素均为数据表的一列。另外,单列索引能够当作联合索引元素数为1的特例。

索引匹配的最左原则具体是说,假如索引列分别为A,B,C,顺序也是A,B,C:

  • 那么查询的时候,若是查询【A】【A,B】 【A,B,C】,那么能够经过索引查询
  • 若是查询的时候,采用【A,C】,那么C这个虽然是索引,可是因为中间缺失了B,所以C这个索引是用不到的,只能用到A索引
  • 若是查询的时候,采用【B】 【B,C】 【C】,因为没有用到第一列索引,不是最左前缀,那么后面的索引也是用不到了
  • 若是查询的时候,采用范围查询,而且是最左前缀,也就是第一列索引,那么能够用到索引,可是范围后面的列没法用到索引

由于索引虽然加快了查询速度,但索引也是有代价的:索引文件自己要消耗存储空间,同时索引会加剧插入、删除和修改记录时的负担,另外,MySQL在运行时也要消耗资源维护索引,所以索引并非越多越好

在使用InnoDB存储引擎时,若是没有特别的须要,请永远使用一个与业务无关的自增字段做为主键。若是从数据库索引优化角度看,使用InnoDB引擎而不使用自增主键绝对是一个糟糕的主意。

InnoDB使用汇集索引,数据记录自己被存于主索引(一颗B+Tree)的叶子节点上。这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放,所以每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,若是页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)。若是表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页。以下:

 

MySQL索引和SQL调优手册

 

 

这样就会造成一个紧凑的索引结构,近似顺序填满。因为每次插入时也不须要移动已有数据,所以效率很高,也不会增长不少开销在维护索引上。

若是使用非自增主键(若是身份证号或学号等),因为每次插入主键的值近似于随机,所以每次新纪录都要被插到现有索引页得中间某个位置,以下:

 

MySQL索引和SQL调优手册

 

 

此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增长了不少开销,同时频繁的移动、分页操做形成了大量的碎片,获得了不够紧凑的索引结构,后续不得不经过OPTIMIZE TABLE来重建表并优化填充页面。

所以,只要能够,请尽可能在InnoDB上采用自增字段作主键。

创建索引的经常使用技巧

一、最左前缀匹配原则,很是重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就中止匹配,好比a = 1 and b = 2 and c > 3 and d = 4 若是创建(a,b,c,d)顺序的索引,d是用不到索引的,若是创建(a,b,d,c)的索引则均可以用到,a,b,d的顺序能够任意调整。

二、=和in能够乱序,好比a = 1 and b = 2 and c = 3 创建(a,b,c)索引能够任意顺序,mysql的查询优化器会帮你优化成索引能够识别的形式

三、尽可能选择区分度高的列做为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大咱们扫描的记录数越少,惟一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不一样,这个值也很难肯定,通常须要join的字段咱们都要求是0.1以上,即平均1条扫描10条记录

四、索引列不能参与计算,保持列“干净”,好比from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,缘由很简单,b+树中存的都是数据表中的字段值,但进行检索时,须要把全部元素都应用函数才能比较,显然成本太大。因此语句应该写成create_time = unix_timestamp(’2014-05-29’);

五、尽可能的扩展索引,不要新建索引。好比表中已经有a的索引,如今要加(a,b)的索引,那么只须要修改原来的索引便可,固然要考虑原有数据和线上使用状况

MySQL优化

配置优化

配置优化指的MySQL 的 server端的配置,通常对于业务方而言,能够不用关注,毕竟会有专门的DBA来处理,可是对于原理的了解,我想,咱们开发,是须要了解的。

基本配置

innodb_buffer_pool_size

这是安装完InnoDB后第一个应该设置的选项。缓冲池是数据和索引缓存的地方:这个值越大越好,这能保证你在大多数的读取操做时使用的是内存而不是硬盘。典型的值是5-6GB(8GB内存),20-25GB(32GB内存),100-120GB(128GB内存)。

innodb_log_file_size

这是redo日志的大小。redo日志被用于确保写操做快速而可靠而且在崩溃时恢复。一直到MySQL 5.1,它都难于调整,由于一方面你想让它更大来提升性能,另外一方面你想让它更小来使得崩溃后更快恢复。

幸运的是从MySQL 5.5以后,崩溃恢复的性能的到了很大提高,这样你就能够同时拥有较高的写入性能和崩溃恢复性能了。一直到MySQL 5.5,redo日志的总尺寸被限定在4GB(默承认以有2个log文件)。这在MySQL 5.6里被提升了。若是你知道你的应用程序须要频繁的写入数据而且你使用的时MySQL 5.6,你能够一开始就把它这是成4G。

max_connections

若是你常常看到‘Too many connections'错误,是由于max_connections的值过低了。这很是常见由于应用程序没有正确的关闭数据库链接,你须要比默认的151链接数更大的值。

max_connection值被设高了(例如1000或更高)以后一个主要缺陷是当服务器运行1000个或更高的活动事务时会变的没有响应。在应用程序里使用链接池或者在MySQL里使用进程池有助于解决这一问题。

InnoDB配置

innodb_file_per_table

这项设置告知InnoDB是否须要将全部表的数据和索引存放在共享表空间里(innodb_file_per_table = OFF) 或者为每张表的数据单独放在一个.ibd文件(innodb_file_per_table = ON)。每张表一个文件容许你在drop、truncate或者rebuild表时回收磁盘空间。

这对于一些高级特性也是有必要的,好比数据压缩。可是它不会带来任何性能收益。你不想让每张表一个文件的主要场景是:有很是多的表(好比10k+)。MySQL 5.6中,这个属性默认值是ON,所以大部分状况下你什么都不须要作。对于以前的版本你必需在加载数据以前将这个属性设置为ON,由于它只对新建立的表有影响。

innodb_flush_log_at_trx_commit

默认值为1,表示InnoDB彻底支持ACID特性。当你的主要关注点是数据安全的时候这个值是最合适的,好比在一个主节点上。可是对于磁盘(读写)速度较慢的系统,它会带来很巨大的开销,由于每次将改变flush到redo日志都须要额外的fsyncs。

将它的值设置为2会致使不太可靠(reliable)由于提交的事务仅仅每秒才flush一次到redo日志,但对于一些场景是能够接受的,好比对于主节点的备份节点这个值是能够接受的。若是值为0速度就更快了,但在系统崩溃时可能丢失一些数据:只适用于备份节点。

innodb_flush_method

这项配置决定了数据和日志写入硬盘的方式。通常来讲,若是你有硬件RAID控制器,而且其独立缓存采用write-back机制,并有着电池断电保护,那么应该设置配置为O_DIRECT;不然,大多数状况下应将其设为fdatasync(默认值)。sysbench是一个能够帮助你决定这个选项的好工具。

innodb_log_buffer_size

这项配置决定了为还没有执行的事务分配的缓存。其默认值(1MB)通常来讲已经够用了,可是若是你的事务中包含有二进制大对象或者大文本字段的话,这点缓存很快就会被填满并触发额外的I/O操做。看看Innodb_log_waits状态变量,若是它不是0,增长innodb_log_buffer_size。

其余设置

query_cache_size

query cache(查询缓存)是一个众所周知的瓶颈,甚至在并发并很少的时候也是如此。最佳选项是将其从一开始就停用,设置query_cache_size = 0(如今MySQL 5.6的默认值)并利用其余方法加速查询:优化索引、增长拷贝分散负载或者启用额外的缓存(好比memcache或redis)。

若是你已经为你的应用启用了query cache而且尚未发现任何问题,query cache可能对你有用。这是若是你想停用它,那就得当心了。

log_bin

若是你想让数据库服务器充当主节点的备份节点,那么开启二进制日志是必须的。若是这么作了以后,还别忘了设置server_id为一个惟一的值。就算只有一个服务器,若是你想作基于时间点的数据恢复,这(开启二进制日志)也是颇有用的:从你最近的备份中恢复(全量备份),并应用二进制日志中的修改(增量备份)。

二进制日志一旦建立就将永久保存。因此若是你不想让磁盘空间耗尽,你能够用 PURGE BINARY LOGS 来清除旧文件,或者设置 expire_logs_days 来指定过多少天日志将被自动清除。记录二进制日志不是没有开销的,因此若是你在一个非主节点的复制节点上不须要它的话,那么建议关闭这个选项。

skip_name_resolve

当客户端链接数据库服务器时,服务器会进行主机名解析,而且当DNS很慢时,创建链接也会很慢。所以建议在启动服务器时关闭skip_name_resolve选项而不进行DNS查找。惟一的局限是以后GRANT语句中只能使用IP地址了,所以在添加这项设置到一个已有系统中必须格外当心。

SQL 调优

通常要进行SQL调优,那么就说有慢查询的SQL,系统或者server能够开启慢查询日志,尤为是线上系统,通常都会开启慢查询日志,若是有慢查询,能够经过日志来过滤。可是知道了有须要优化的SQL后,下面要作的就是如何进行调优

慢查询优化基本步骤

  1. 先运行看看是否真的很慢,注意设置SQL_NO_CACHE
  2. where条件单表查,锁定最小返回记录表。这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起,单表每一个字段分别查询,看哪一个字段的区分度最高
  3. explain查看执行计划,是否与1预期一致(从锁定记录较少的表开始查询)
  4. order by limit 形式的sql语句让排序的表优先查
  5. 了解业务方使用场景
  6. 加索引时参照建索引的几大原则
  7. 观察结果,不符合预期继续从0分析

经常使用调优手段

执行计划explain

在平常工做中,咱们有时会开慢查询去记录一些执行时间比较久的SQL语句,找出这些SQL语句并不意味着完事了,咱们经常用到explain这个命令来查看一个这些SQL语句的执行计划,查看该SQL语句有没有使用上了索引,有没有作全表扫描,这均可以经过explain命令来查看。

因此咱们深刻了解MySQL的基于开销的优化器,还能够得到不少可能被优化器考虑到的访问策略的细节,以及当运行SQL语句时哪一种策略预计会被优化器采用。

使用explain 只须要在原有select 基础上加上explain关键字就能够了,以下:

mysql> explain select * from servers;
+----+-------------+---------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | servers | ALL | NULL | NULL | NULL | NULL | 1 | NULL |
+----+-------------+---------+------+---------------+------+---------+------+------+-------+
1 row in set (0.03 sec)

简要解释下explain各个字段的含义

  • id : 表示SQL执行的顺序的标识,SQL从大到小的执行
  • select_type:表示查询中每一个select子句的类型
  • table:显示这一行的数据是关于哪张表的,有时不是真实的表名字
  • type:表示MySQL在表中找到所需行的方式,又称“访问类型”。经常使用的类型有:ALL, index, range, ref, eq_ref, const, system, NULL(从左到右,性能从差到好)
  • possible_keys:指出MySQL能使用哪一个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不必定被查询使用
  • Key:key列显示MySQL实际决定使用的键(索引),若是没有选择索引,键是NULL。
  • key_len:表示索引中使用的字节数,可经过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并不是实际使用长度,即key_len是根据表定义计算而得,不是经过表内检索出的)
  • ref:表示上述表的链接匹配条件,即哪些列或常量被用于查找索引列上的值
  • rows:表示MySQL根据表统计信息及索引选用状况,估算的找到所需的记录所须要读取的行数,理论上行数越少,查询性能越好
  • Extra:该列包含MySQL解决查询的详细信息

EXPLAIN的特性

  • EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响状况
  • EXPLAIN不考虑各类Cache
  • EXPLAIN不能显示MySQL在执行查询时所做的优化工做
  • 部分统计信息是估算的,并不是精确值
  • EXPALIN只能解释SELECT操做,其余操做要重写为SELECT后查看执行计划。

实战演练

表结构和查询语句

假若有以下表结构

circlemessage_idx_0 | CREATE TABLE `circlemessage_idx_0` (
`circle_id` bigint(20) unsigned NOT NULL COMMENT '群组id',
`from_id` bigint(20) unsigned NOT NULL COMMENT '发送用户id',
`to_id` bigint(20) unsigned NOT NULL COMMENT '指定接收用户id',
`msg_id` bigint(20) unsigned NOT NULL COMMENT '消息ID',
`type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '消息类型',
PRIMARY KEY (`msg_id`,`to_id`),
KEY `idx_from_circle` (`from_id`,`circle_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

经过执行计划explain分析以下查询语句

mysql> explain select msg_id from circlemessage_idx_0 where to_id = 113487 and circle_id=10019063 and msg_id>=6273803462253938690 and from_id != 113487 order by msg_id asc limit 30;
+----+-------------+---------------------+-------+-------------------------+---------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+-------+-------------------------+---------+---------+------+--------+-------------+
| 1 | SIMPLE | circlemessage_idx_0 | range | PRIMARY,idx_from_circle | PRIMARY | 16 | NULL | 349780 | Using where |
+----+-------------+---------------------+-------+-------------------------+---------+---------+------+--------+-------------+
1 row in set (0.00 sec)

 

mysql> explain select msg_id from circlemessage_idx_0 where to_id = 113487 and circle_id=10019063 and from_id != 113487 order by msg_id asc limit 30;
+----+-------------+---------------------+-------+-----------------+---------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+-------+-----------------+---------+---------+------+------+-------------+
| 1 | SIMPLE | circlemessage_idx_0 | index | idx_from_circle | PRIMARY | 16 | NULL | 30 | Using where |
+----+-------------+---------------------+-------+-----------------+---------+---------+------+------+-------------+
1 row in set (0.00 sec)

问题分析

经过上面两个执行计划能够发现当没有msg_id >= xxx这个查询条件的时候,检索的rows要少不少,而且二者查询的时候都用到了索引,并且用到的还只是主键索引。那说明索引应该是不合理的,没有发挥最大做用。

分析这个执行计划能够看到,当包含msg_id >= xxx 查询条件的时候,rows有34w多行,这种状况,说明检索太多,要么就是表里面确实有这么大,要么就是索引不合理没有用到索引,大都状况是没用合理用到索引。列中所用到的索引也是PRIMARY,那就多是(msg_id,to_id)的其中一个,注意咱们创建表的时候msg_id索引的顺序是在to_id前面的,所以MySQL查询必定会优先用msg_id索引,在使用了msg_id索引后,就已经检索出了34w行,而且因为msg_id的查询条件是大于等于,所以,再这个查询条件后,就不能再用到to_id的索引。

而后再看key_len长度为16,结合 key为PRIMARY,那么能够分析得知,只有一个主键索引被用到。

最后看看 type 值,是range,那么就说明这个查询要么是范围查询,要么就是多值匹配。

请注意,from_id != xxx这样的语句,是没法用到索引的。只有from_id = xxx就能够用到因此,所以from id 的索引其实能够不用,创建索引的时候就要考虑清楚

如何优化

既然知道索引不合理,那么就要分析并调整索引。通常而言,咱们既然要从单表里面查询,那么就须要可以知道大致,单表里面大体会有哪些数据,如今的量级大概是多少。

而后开始下一步的分析,既然msgid是被设置为了主键,那必定是全局惟一的,全部,有多少数据量就至少会有多少条msgid;那么检索msg_id基本就是检索整个表了。咱们要作的优化就是要尽可能减小索引,减小查询的行数;那么就须要思考,经过查询哪些字段才可以减小行数?好比,一个张表里面,所属某个用户的数据,会不会比查询msgid的行数要少?查询某个用户而且是属于某个圈子的,那会不会就更少了?等等。

而后根据实际状况分析,单表里面命中to_id 的行数应该是会小于命中msg_id的,所以要首先保证可以使用到to_id的索引,为此,能够设置主键的时候把msg_id和to_id的顺序交互一下;可是,因为已是线上的表,已经有了大量数据,而且业务开始运行,这种状况下,修改主键会引起不少问题(固然修改索引是OK的),所以,不建议直接修改主键。

那么,为了保证有效使用to_id的索引,就要新建一个联合索引;那么新建的联合索引的第一索引字段必然是to_id,针对此业务场景,最好可以再加上circle_id索引,这样能够快速索引;这样就获得了新的联合索引(to_id,circle_id)的索引,而后,由于要找msg_id,为此,在此基础上,再加上msg_id。最终获得的联合索引为(to_id,circle_id,msg_id);这样的话,就可以快速检索这样的查询语句了:where to_id = xxx and circle_id = xxx and msgId >= xxx

固然,索引的创建,也不是说某个sql 语句须要啥索引,就创建某个联合索引,这样的话,索引太多的话,写的性能受影响(插入、删除、修改),而后存储空间也会相应增大;另外mysql在运行时也会消耗资源维护索引,因此,索引并非越多越好,须要结合查询最频繁、最影响性能的sql来创建合适的索引。须要再说明的是,一个联合索引或者一组主键就是一个btree,多个索引就是多个btree

总结

首先咱们须要深刻理解索引的原理和实现,当理解了原理后,才可以更有助于咱们创建合适的索引。而后咱们创建索引的时候,不要想固然,要先想清楚业务逻辑,再创建对应的表结构和索引。须要再次强调以下几点:

  • 索引不是越多越好
  • 区分主键和索引
  • 理解索引结构原理
  • 理解查询索引规则
相关文章
相关标签/搜索