案例分析之mysql选错索引

案例分析之mysql选错索引,第1张

前言

案例取自极客时间《mysql45讲》

案例

模拟执行器分析查询语句

场景复现

奇了怪了,此时没用索引,进行了全表扫描

虽然使用了索引,但是还是扫描了37116行,不妨结合之前的知识分析一下:

1.另一个事务未提交,需要保存之前的数据的数据版本,因此delete10万行数据实际是标记数据,这样每一行数据就有两个数据版本,旧的是delete之前的,新的是标记为delete的,索引a上的数据有两份

2.那还多出来的1万7呢,之前介绍过索引树的叶子节点存的是主键,select * 还要进行回表查询,这里将回表的扫描行数一并算上

为什么会选错索引

选择索引是优化器的工作,优化器要找到最优的执行方案并选择最小的代价去执行,扫描行数是影响执行代价之一(扫描越小,访问磁盘次数越少,消耗CPU资源越少)

mysql执行语句之前需要通过根据信息来统计记录数

这个统计信息就是索引的区分度,即索引上不同的值越多,区分度越高越好(show index t 的 cardinality字段查看),索引的区分度是利用采样统计得到的即取小部分统计信息再乘以整体。

除了使用统计信息,还会计算回表代价(主键不需要回表)

如果是统计信息不对那就修正

另一种场景复现

按理说这是个空集,利用索引a只扫描1000行,利用索引b要扫描50000行,这里优化器竟然选择了索引b!!

mysql又选错了索引

解决办法

2.引导使用a索引

我们知道索引树上的数据是有序的,优化器使用b索引,一方面是认为索引b可以避免排序 ,order by a,b强制按照a,b排序意味着两个都需要排序,因此扫描行数成了影响决策的主要条件

3.删掉索引b

解决mysql选错索引主要有两大方向

1.强制指定索引

2.干涉优化器选择(比如增大limit数量,增加order by ,写成子查询)

MySQL选错索引导致的线上慢查询事故

mysql中走与不走索引的情况汇集(待全量实验)

       通常大家都会根据查询的WHERE条件来创建合适的索引,不过这只是索引优化的一个方面。设计优秀的索引应该考虑到整个查询,而不单单是WHERE条件部分。索引确实是一种查找数据的高效方式,但是MySQL也可以使用索引来直接获取列的数据,这样就不再需要读取数据行。如果索引的叶子节点中已经包含要查询的数据,那么还有什么必要再回到表中查询呢? 如果一个索引覆盖所有需要查询的字段的值,我们就称之为“覆盖索引”。

覆盖索引是非常有用的工具,能够极大地提高性能:

       在所有这些场景中,在索引中满足查询的成本一般比查询行要小得多。

       不是所有类型的索引都可以成为覆盖索引。覆盖索引必须要存储索引列的值,而哈希索引、空间索引和全文索引都不存储索引列的值,所以MySQL只能使用B+Tree索引所覆盖索引。另外,不同的存储引擎实现覆盖索引的方式也不同,而且不是所有的引擎都支持覆盖索引。

       当发起一个呗索引覆盖的查询是,在EXPLAIN的Extra列可以看到“Using index”的信息。

如: explain select col1 from layout_test where col2=99

       索引覆盖查询还有很多陷阱可能会导致无法实现优化。MySQL查询优化器会在执行查询前判断是否有一个索引能进行覆盖。假设索引覆盖了wehre条件中的字段,但不是整个查询涉及的字段。mysql5.5和更早的版本也总是会回表获取数据行,尽管并不需要这一行且最终会被过滤掉。

如: EXPLAIN select * from people where last_name='Allen' and first_name like '%Kim%'

这里索引无法覆盖该查询,有两个原因:

这条语句只检索1行,而之前的 like '%Kim%'要检索3行。

也有办法解决上面所说的两个问题,需要重写查询并巧妙设计索引。

       这种方式叫做延迟关联,因为延迟了对列的访问。在查询第一个阶段MySQL可以使用覆盖索引,因为索引包含了主键id的值,不需要做二次查找。

       在FROM子句的子查询中找到匹配的id,然后根据这些id值在外层查询匹配获取需要的所有列值。虽然无法使用索引覆盖整个查询,但总算比完全无法利用索引覆盖的好吧。

数据量大了怎么办?

       这样优化的效果取决于WHERE条件匹配返回的行数。假设这个people表有100万行,我们看一下上面两个查询在三个不同的数据集上的表现,每个数据集都包含100万行。

实例1中 ,查询返回了一个很大的结果集,因此看不到优化的效果。大部分时间都花在读取和发送数据上了。

实例2中 ,经过索引过滤,尤其是第二个条件过滤后只返回了很少的结果集,优化的效果非常明显:在这个数据及上性能提高了很多,优化后的查询效率主要得益于只需读取40行完整数据行,而不是原查询中需要的30000行。

实例3中 ,子查询效率反而下降。因为索引过滤时符合第一个条件的结果集已经很小了,所以子查询带来的成本反而比从表中直接提取完整行更高。

       在大多数存储引擎中,覆盖索引只能覆盖那些只访问索引中部分列的查询。不过,可以更进一步优化InnoDB。回想一下,InnoDB的二级索引的叶子节点都包含了主键的值,这意味着InnoDB的二级索引可以有效地利用这些额外的主键列来覆盖查询。

       例如,people表中last_name字段有一个二级索引,虽然该索引的列不包括主键id,但也能够用于对id做覆盖查询:

select id,last_name from people where last_name='hua'

上一篇给小伙伴们讲了关于SQL查询性能优化的相关技巧,一个好的查询SQL离不开合理的索引设计。这篇小二就来唠一唠怎么合理的设计一个索引来优化我们的查询速度,要是有不合理的地方...嗯..

当然啦,开个玩笑,欢迎小伙伴们指正!

通常情况下,字段类型的选择是需要根据业务来判断的,通常需要遵循以下几点。

下列各种类型表格内容来自菜鸟教程,权当备忘。

优化建议:

注意: INT(2)设置的为显示宽度,而不是整数的长度,需要配合 ZEROFILL 使用 。

例如 id 设置为 TINYINT(2) UNSIGNED ,表示无符号,可以存储的最大数值为255,其中 TINYINT(2) 没有配合 ZEROFILL 实际没有任何意义,例如插入数字200,长度虽然超过了两位,但是这个时候是可以插入成功的,查询结果同样为200;插入数字5时,同样查询结果为5。

而 TINYINT(2) 配合 ZEROFILL 后,当插入数字5时,实际存储的还是5,不过在查询是MySQL会在前面补上一个0,即查询出来的实际为 05 。

优化建议:

优化建议:

通常来说,考虑好表中每个字段应该使用什么类型和长度,建完表需要做的事情不是马上建立索引,而是先把相关主体业务开发完毕,然后把涉及该表的SQL都拿出来分析之后再建立索引。

尽量少建立单值索引( 唯一索引除外 ),应当设计一个或者两三个联合索引,让每一个联合索引都尽量去包含SQL语句中的 where、order by、group by 的字段,同时确保联合索引的字段顺序尽量满足SQL查询的最左前缀原则。

索引基数是指这个字段在表里总共有多少个不同的值,比如一张表总共100万行记录,其中有个性别字段,性别一共有三个值:男、女、保密,那么该字段的基数就是3。

如果对这种小基数字段建立索引的话,因为索引树中只有男、女、保密三个值,根本没法进行快速的二分查找,同时还需要回表查询,还不如全表扫描嘞。

一般建立索引,尽量使用那些基数比较大的字段,那么才能发挥出B+树快速二分查找的优势来。

在 where 和 order by 出现索引设计冲突时,是优先针对where去设计索引?还是优先针对order by设计索引?

通常情况下都是优先针对 where 来设计索引,因为通常情况下都是先 where 条件使用索引快速筛选出来符合条件的数据,然后对进行筛选出来的数据进行排序和分组,而 where 条件快速筛选出来的的数据往往不会很多。

对生产实际运行过程中,或者测试环境大数据量测试过程中发现的慢查询SQL进行特定的索引优化、代码优化等策略。

终于轮到实战了,小二最喜欢实战了。

写到这里不得不吐槽一下,这个金三银四的跳槽季节,年前提离职了,结果离职还没办完就封村整整两个礼拜了,呜呜呜...

上节小二就提到会有个很有意思的小案例,那么在疫情当下,门都出不去的日子,感觉这个例子更有意思了,咱们来讨论一下各种社交平台怎么做的用户信息搜索呢。

社交平台有一个小伙伴们都喜欢的功能,搜索好友信息,比如小二熟练的点开省份...城市..性别..年龄..身高...

咳咳咳...小二怎么可能干这种事情,小二的心里只有代码,嗯...没错,就是这样。

这个就可以说是对于用户信息的查询筛选了,通常这种表都是非常大数据量的,在不考虑分库分表的情况下,怎么通过索引配合SQL来优化呢?

通常我们在编写SQL是会写出类似如下的SQL来执行,有 where、order by、limit 等条件来查询。

那么接下来小二一个一个慢慢增加字段来分析分析,怎么根据业务场景来设计索引。

针对这种情况,很简单,设计一个联合索引 (provice, city, sex) 就完事了。

那么这时候有小伙伴就会说了,很简单啊,范围字段放最后咱还是知道的,联合索引改成 (provice, city, sex, age) 不就可以了。

嗯,是的,这么干没毛病,但是小伙伴们有没有想过有些人万一既喜欢帅哥又喜欢美女,别想歪了哈...,挺多小姐姐就既喜欢帅哥又喜欢美女的。

那么这个时候小姐姐就不搜索性别了,那么这个时候联合索引只能用到前两个字段了,那么不符合咱们的专业标准啊,咋办呢?这时候还是有办法的,咱们只需要动动小脑袋改改SQL就行了,在没有选择性别时判断一下,改成下面这样就可以了。

咋办嘞,同样往联合索引里面塞,例如 (provice, city, sex, hobby, xx, age) 。

针对这种多个范围查询的话,为了比较好的利用索引,在业务允许的情况下可以使用固定范围,然后数据库字段存储范围标识就可以了,这样就转化为了等值匹配,就可以很好地利用索引了。

例如最后登录时间字段不记录最后登录时间,而是记录设置字段is_login_within_seven_days 在7天内有登录则为1,否则为0,最后索引设计成 (provice, city, sex, hobby, xx, is_login_within_seven_days, age) 。

那么根据场景最后设计出来的这个索引可能已经可以覆盖大部分的查询流量了,那么如果还有其他一部分热度比较高的查询怎么办呢,办法也很简单啊,再加一两个索引即可。

例如通常会查询这个城市比较受欢迎(评分:score)的小姐姐,这时候添加一个联合索引 (provice, city, sex, score) 那么就可以了。

可以看出,索引时必须结合场景来设计的,思路就是尽量用不超过3个复杂的联合索引来抗住大部分的80%以上的常用查询流量,然后再用一两个二级索引来抗下一些非常用查询流量。

以上就是小二要给大家分享的索引设计,如果能动动你发财的小手给小二点个免费的赞就更好啦~

下篇小二就来讲讲MySQL事务和锁机制。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/7272993.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-03
下一篇 2023-04-03

发表评论

登录后才能评论

评论列表(0条)

保存