伍佰目录 短网址
  当前位置:海洋目录网 » 站长资讯 » 站长资讯 » 文章详细 订阅RssFeed

order by 导致索引选择错误

来源:网络转载 浏览:90次 时间:2022-12-03

线上出现的慢SQL:
 SELECT c.id FROM fnbl_pim_contact AS c WHERE c.fuserid = 6246640   and (first_name !='' or  middle_name is not null and middle_name !='' or last_name !='') AND c.status in ('N','U') order by id limit 1;
 需要十多秒甚至更长时间才能出来结果。
 这个表的大小在几千万的级别。
 mysql> desc SELECT c.id FROM fnbl_pim_contact AS c WHERE c.fuserid = 6246640   and (first_name !='' or  middle_name is not null and middle_name !='' or last_name !='') AND c.status in ('N','U') order by id limit 1;
+----+-------------+-------+-------+----------------------------------------------------------------------+------+---------+------+------+-------------+
| id | select_type | table | type  | possible_keys                                                        | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+-------+----------------------------------------------------------------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | c     | index | IDX_FUSERID_STATUS_ISDEL_ISPROFILE,IDX_FUSERID_ID,IDX_FUSERID_UPDATE | id   | 8       | NULL |  696 | Using where |
+----+-------------+-------+-------+----------------------------------------------------------------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)


从执行计划来看,看似索引页走了,扫描的行数也很少,不应该慢才对。但是需要注意到一点就是计划中显示 type为 index,这是索引扫描,这是仅次于ALL全表扫描的耗时操作。
这里为何要选择索引扫描呢,而且走的索引是主键。
 再看看这个表的索引。
 mysql> show index from fnbl_pim_contact;
+------------------+------------+------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table            | Non_unique | Key_name                           | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------------+------------+------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| fnbl_pim_contact |          0 | PRIMARY                            |            1 | userid      | A         |      361576 |     NULL | NULL   |      | BTREE      |         |               |
| fnbl_pim_contact |          0 | PRIMARY                            |            2 | id          | A         |    28202968 |     NULL | NULL   |      | BTREE      |         |               |
| fnbl_pim_contact |          1 | id                                 |            1 | id          | A         |    28202968 |     NULL | NULL   |      | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_CONTACT_STATUS_ISDEL           |            1 | userid      | A         |      239008 |     NULL | NULL   |      | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_CONTACT_STATUS_ISDEL           |            2 | status      | A         |      575570 |     NULL | NULL   | YES  | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_CONTACT_STATUS_ISDEL           |            3 | isdel       | A         |      575570 |     NULL | NULL   | YES  | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_CONTACT_STATUS_ISDEL           |            4 | isprofile   | A         |      600063 |     NULL | NULL   |      | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_CONTACT_USERID_UPDATE          |            1 | userid      | A         |      408738 |     NULL | NULL   |      | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_CONTACT_USERID_UPDATE          |            2 | last_update | A         |    14101484 |     NULL | NULL   | YES  | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_CONTACT_KEY_ID                 |            1 | id          | A         |    28202968 |     NULL | NULL   |      | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_FUSERID_STATUS_ISDEL_ISPROFILE |            1 | FUSERID     | A         |      220335 |     NULL | NULL   |      | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_FUSERID_STATUS_ISDEL_ISPROFILE |            2 | status      | A         |      552999 |     NULL | NULL   | YES  | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_FUSERID_STATUS_ISDEL_ISPROFILE |            3 | isdel       | A         |      552999 |     NULL | NULL   | YES  | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_FUSERID_STATUS_ISDEL_ISPROFILE |            4 | isprofile   | A         |      626732 |     NULL | NULL   |      | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_FUSERID_ID                     |            1 | FUSERID     | A         |      339794 |     NULL | NULL   |      | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_FUSERID_ID                     |            2 | id          | A         |    28202968 |     NULL | NULL   |      | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_FUSERID_UPDATE                 |            1 | FUSERID     | A         |      231171 |     NULL | NULL   |      | BTREE      |         |               |
| fnbl_pim_contact |          1 | IDX_FUSERID_UPDATE                 |            2 | last_update | A         |     7050742 |     NULL | NULL   | YES  | BTREE      |         |               |
+------------------+------------+------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
这么多索引,为何选择主键,这是语句中有order by id。优化器认为排序是比较耗时的操作,而走索引是不会有额外的操作,因此选择了主键。
去掉order by id来验证猜想:
mysql> desc SELECT c.id FROM fnbl_pim_contact AS c WHERE c.fuserid = 6246640   and (first_name !='' or  middle_name is not null and middle_name !='' or last_name !='') AND c.status in ('N','U') limit 1;
+----+-------------+-------+------+----------------------------------------------------------------------+--------------------+---------+-------+-------+-------------+
| id | select_type | table | type | possible_keys                                                        | key                | key_len | ref   | rows  | Extra       |
+----+-------------+-------+------+----------------------------------------------------------------------+--------------------+---------+-------+-------+-------------+
|  1 | SIMPLE      | c     | ref  | IDX_FUSERID_STATUS_ISDEL_ISPROFILE,IDX_FUSERID_ID,IDX_FUSERID_UPDATE | IDX_FUSERID_UPDATE | 8       | const | 40502 | Using where |
+----+-------------+-------+------+----------------------------------------------------------------------+--------------------+---------+-------+-------+-------------+
1 row in set (0.00 sec)


mysql> SELECT c.id FROM fnbl_pim_contact AS c WHERE c.fuserid = 6246640   and (first_name !='' or  middle_name is not null and middle_name !='' or last_name !='') AND c.status in ('N','U') limit 1;
+---------+
| id      |
+---------+
| 3778623 |
+---------+
1 row in set (0.00 sec)
从上面结果看来,猜测是正确的。但是去掉order by id不合符业务,所以不能简单的去掉order by id来处理。


我们知道当where条件和order by字段在同一个索引时,order by也是不会有额外的排序操作的。
我们看到,表中还有个索引为IDX_FUSERID_ID(fuserid,id),从语句where 条件和order by来看,选择这条索引是比较好的。强制走IDX_FUSERID_ID 索引看看:
mysql> desc  SELECT c.id FROM fnbl_pim_contact AS c force index(IDX_FUSERID_ID) WHERE c.fuserid = 6246640   and (first_name !='' or  middle_name is not null and middle_name !='' or last_name !='') AND c.status in ('N','U') order by id limit 1;
+----+-------------+-------+------+----------------+----------------+---------+-------+-------+-------------+
| id | select_type | table | type | possible_keys  | key            | key_len | ref   | rows  | Extra       |
+----+-------------+-------+------+----------------+----------------+---------+-------+-------+-------------+
|  1 | SIMPLE      | c     | ref  | IDX_FUSERID_ID | IDX_FUSERID_ID | 8       | const | 45412 | Using where |
+----+-------------+-------+------+----------------+----------------+---------+-------+-------+-------------+
1 row in set (0.00 sec)
可以看到type由 index改变为ref,这是非唯一性索引访问,比索引扫描快很多。
mysql>   SELECT c.id FROM fnbl_pim_contact AS c force index(IDX_FUSERID_ID) WHERE c.fuserid = 6246640   and (first_name !='' or  middle_name is not null and middle_name !='' or last_name !='') AND c.status in ('N','U') order by id limit 1;
+---------+
| id      |
+---------+
| 3673097 |
+---------+
1 row in set (0.00 sec)
同时可以看到执行时间基本没有。


从这个例子可以看出,优化器在索引选择上有时也会出现误差。分析一条慢SQL,要从多个角度去看,而不能只看已个方面,从上面的执行计划中,rows 很小,给人的感觉就是扫描的行数
都这么少,怎么会慢呢。但是同时不能忘了 扫描方式为index这种耗时的操作。因此优化可以从这个角度去考虑。
借用一个阿里DBA的公式来讲解他在做sql优化的时候遵循的原则:


          T=S/V(T代表时间,S代表路程,V代表速度)


S指SQL所需访问的资源总量,V指SQL单位时间所能访问的资源量,T自然就是SQL执行所需时间了;我们为了获得SQL最快的执行时间,可以根据公式定义上去反推:


在S不变的情况下,我们可以提升V来降低T:通过适当的索引调整,我们可以将大量的速度较慢的随机IO转换为速度较快的顺序IO;通过提升服务器的内存,使得将更多的数据放到内存中,会比数据放到磁盘上会得到明显的速度提升;采用电子存储介质进行数据存储和读取的SSD,突破了传统机械硬盘的性能瓶颈,使其拥有极高的存储性能;在提升V上我们可以采用较高配置的硬件来完成速度的提升;
在V不变的情况下,我们可以减小S来降低T:这是SQL优化中非常核心的一个环节,在减小S环节上,DBA可以做的可以有很多,通常可以在查询条件中建立适当的索引,来避免全表扫描;有时候可以改写SQl,
添加一些适当的提示符,来改变SQL的执行计划,使SQL以最少的扫描路径完成查询;当这些方法都使用完了之后,你是否还有其他方案来优化喃?
还有就是要深入的了解业务,当DBA深入的了解业务之后,这个时候能站在业务上,又站DB角度上考虑,这个时候在去做优化,有时候能达到事半功倍的效果。


其实我们这里参考的就是V不变,缩小S。从而达到降低T.


http://tieba.baidu.com/p/2638469262


  推荐站点

  • At-lib分类目录At-lib分类目录

    At-lib网站分类目录汇集全国所有高质量网站,是中国权威的中文网站分类目录,给站长提供免费网址目录提交收录和推荐最新最全的优秀网站大全是名站导航之家

    www.at-lib.cn
  • 中国链接目录中国链接目录

    中国链接目录简称链接目录,是收录优秀网站和淘宝网店的网站分类目录,为您提供优质的网址导航服务,也是网店进行收录推广,站长免费推广网站、加快百度收录、增加友情链接和网站外链的平台。

    www.cnlink.org
  • 35目录网35目录网

    35目录免费收录各类优秀网站,全力打造互动式网站目录,提供网站分类目录检索,关键字搜索功能。欢迎您向35目录推荐、提交优秀网站。

    www.35mulu.com
  • 就要爱网站目录就要爱网站目录

    就要爱网站目录,按主题和类别列出网站。所有提交的网站都经过人工审查,确保质量和无垃圾邮件的结果。

    www.912219.com
  • 伍佰目录伍佰目录

    伍佰网站目录免费收录各类优秀网站,全力打造互动式网站目录,提供网站分类目录检索,关键字搜索功能。欢迎您向伍佰目录推荐、提交优秀网站。

    www.wbwb.net