一次联合索引优化引发的思考

问题描述

DBA在群里报产线DB有故障,数据库所在机器的CPU使用率高达95%, 短时间内报了500+条慢查询,其中出现最多的是下面一条SQL语句:


file

file

产线出故障时,运维同学一般都是描述现象,一个现象要转换为可以具体排查的问题,往往需要做一些信息的确认和提取工作的。

就这个问题来说,CPU占用率高是现象,DBA帮我们做了第一步的信息提取:发现一条慢SQL。

但是这个问题还不够具体,比如:究竟是不是这条慢SQL导致了CPU高是需要确认的,另外这个慢SQL究竟有多慢也不知道,所以研发还需要做进一步的信息提取。

在我们的追问下,DBA又给出数据库系统记录的慢查询具体信息:


file

从截图上可以看出,这条SQL扫描了24万行记录,最终返回的结果只用到了256行,执行时间1~10s不等。
SQL中访问的唯一一张表webcal_entry_user, 是日程业务中的参会人信息表,表中有2000多万条数据,表和索引结构如下:


file

可以说,这条SQL结构并不复杂,没有联表,没有排序,唯一需要看的是索引的命中情况和是否命中最优索引,通过explain来分析下(下图是从beta截出来的,除了扫描行数与产线不一样,其它都相同):


file

从分析的结果来看只有一个条件过滤cal_login in (xxx)命中的了索引,这个字段的区分度并不差,可惜的是要查300用户,并且数年积累下来共计有24万条参会记录。
换句话说,用索引筛选完后,还有24万条记录需要从DB加载到内存用where条件再来筛一遍,扫描行数太多是慢的根因。

解决方案

先说结论,最终给DBA发了一条索引变更语句,产线变更后,扫描行数也下降到了1000行左右,常规执行时间下降到了34ms。

ALTER TABLE `webcal_entry_user` DROP INDEX `idx_cal_login`, ADD INDEX `idx_cal_login` (`cal_login`,`end_time`); 
file

对于产线故障来说,最终的解法往往都比较简单,复杂的地方在思考方案过程中所运用的知识和原理。对于联合索引来说,可能涉及到的知识点例如:

  • 用什么字段建联合索引?
  • 联合索引中多个字段的顺序选择,哪个字段放前边,哪个字段放后边?
  • 索引字段中等值查询与范围查询的区别
  • 既有where条件又有order by语句时如何选择索引字段?

背后的知识和思考

用什么字段建立联合索引?

索引的目的是为了快速过滤出业务需要的最小数据集合,如果任何一个单列索引过滤效果都不明显,就会考虑是不是再加一个过滤条件让最终结果的数据小一些。

知识点:用索引筛掉的数据越多,需要从磁盘加载到内存的数据集就越小。不过索引本身也是有开销的,所以创建索引时需要取一个权衡:能够解决问题的最小索引。

就以这个问题为例,cal_login字段过滤完后,还有24万条数据,就必须再找一个筛选效率好的条件来作为第2个索引字段,分析过程是:

  • cal_status in('A','W') :表示参会人接受会议邀请的状态,A(Accept)和 W(Wait)是绝大部分参会人的状态,此条件有和无对最终的扫描行数几乎没有区别;
  • status != 0 :表示会议状态为0(取消)以外的任何状态,同上,没有什么过滤效果;
  • start_time < 1652344200:表示会议开始时间小于2022-05-12 16:30:00的记录
  • end_time > 1652340600:表示会议结束时间大于2022-05-12 15:30:00的记录

为什么最终我们添加end_time字段作为索引而不用start_time呢?


file

file

基于对业务的理解来判断:

  • 这条SQL是用来获取参会人指定时间段内忙闲状态的,只有预约会议时才会调用;
  • 预约会议选的基本都是将来的时间,并且这个时间段跨度不会太大,常规可选的也就是0~24小时;
  • end_time > 1652340600可以理解为筛选指定时间段之后未召开的会议,start_time < 1652344200表示筛选指定时间段之前的历史会议,未召开的会肯定比历史的会数量少;

所以做技术的人,理解业务很重要。

那为什么我们是建立(cal_login,end_time)的联合索引,而不是(end_time,cal_login)的联合索引呢?

最左原则

知识点:多个字段建立的索引是有顺序的,只有左边的字段命中了索引,右边的字段才可能命中索引。

假如我们对(a, b)字段建立了索引,

Case1: 当where条件为下面的两种时

  a = 1
  a = 1 and b = 2

都是可以命中索引的。

Case2: 当where条件为

b= 2 and a =1

也是可以命中索引的。不是我们写的对,而是Mysql优化器帮我们做了调整,调整后按 a = 1 and b = 2 来执行去匹配索引中的字段顺序(a, b)。

Case3: 如果执行 b = 2 就命中不了索引了,这就是索引的最左匹配原则。

那为什么会这样呢?

索引能够加快查找的原理在于:索引字段是有顺序的,通过二分查找能够快速匹配目标数据。
而对于(a, b) 这类联合索引来说,B+树的结构大概如下:


file

可以看出,第一个字段a是顺序递增的(1,1,2,2,3,3),而第二个字段b总体上却没有明显的顺序(1,2,1,4,1,2),只是局部有序(a固定的时候b是递增的)。

  • 正是由于字段b整体无序,所以前面提到的直接执行b=2单个条件是无法使用索引的;
  • 因为a确定的时候b字段局部有序,所以 a = 1 and b = 2 这个组合条件中的b字段可以使用索引;

有的小伙伴还会抛出一个疑问:如果只考虑 a=1 and b=2 这种组合条件,创建联合索引时该创建(a, b) 还是(b,a)呢?

对于等值查询来说,一般是把区分度高的字段放在前面,像姓别、状态这类区分度很低的字段放后面。用show index from [table_name]可以查看:


file

这里限定了条件是等值查询,对于(>、<)这类范围查询,索引字段顺序的考量是有些区别的。

范围查询

知识点:范围查询右边的所有列都无法使用索引,在创建索引时应当将范围查询条件放在靠后的位置。

还是之前的(a, b)联合索引,例如这个组合条件:a > 1 and b = 2, a > 1可以命中索引,但 b=2却无法命中索引。

原因在于:a>1是一个模糊的范围,在这个模糊的范围里,数据结构上b字段是无序的(1,4,1,2)。

那回到我们这次遇到的情况,对于

cal_login in ('8418760','8418764','8418766')and end_time > 1652340600

这类组合条件,后面的end_time > 1652340600 肯定是范围查询,那前者呢?

说到这里,挺惭愧,自认为对索引还算有一定程度的了解,但在遇到这次的问题之前,这个Case我的理解一直是错的。

SQL里的 in 这种语法,看着像范围查询,但是实际动手在线下环境操作去创建一个(cal_login,end_time)这种联合索引,上面组合条件中的end_time是可以使用索引的,应该是SQL优化器做了专门的优化。

按照目前网上大部分文章的说法,能确定是范围查询的语法包括这几个 (>、<、between、like)。

order by / group by

一般创建联合索引还会面临的一个选择是如何让分组/排序这种语法命中索引。实际工作中用explain分析慢查询时会遇到这类问题:


file

extra一栏中的filesort表明使用内存排序,temporary表明由于要排序的记录太多,使用了磁盘临时文件。两种都比较耗资源,后者更甚,一旦使用了磁盘临时文件,这个SQL基本会成为慢查询的常客。

最好的情况是让order by 语句命中索引,相关知识点:

  • order by、group by使用索引的前提条件:相关字段在where条件中出现并在where条件执行的时候使用了索引;
  • 对于联合索引(a, b), 如果where条件中字段a是等值查询,则order by中的字段b可以使用索引排序;
  • 对于联合索引(a, b), 如果where条件中的字段a是in/or查询,则order by 中的字段b无法使用索引排序,因为order by 要求是全局有序才能排,而联合索引中的b只能做到局部有序;

例1, 适合给(a, b)建立联合索引,a=1的时候b相对有序:

SELECT * FROM `table` WHERE a = 1 ORDER BY b;

例2, 带范围查询又带order by 语句的SQL:

SELECT * FROM `table` WHERE a = 1 AND b IN (1,2,3) AND c > 3 ORDER BY c;

假如索引是(a, b, c), 由于b不确定,所以order by c是无法使用索引的,不过c > 3条件可以使用索引。

总结

  • 产线报故障时,首先要想想能获取哪些现场信息,一个具体的现场信息能帮我们快速锁定问题;
  • 做技术一定要关心业务,清楚的知道自己业务中哪些数据量大,哪些数据量小,哪些是低頻请求,哪些是高频请求,哪些情况会发生,哪些情况不会发生;
  • 建联合索引遵循最左原则,只有左边字段命中索引,右边字段才可能命中索引;
  • 范围查询字段始终放到联合索引的最后。

参考资料

本文由博客一文多发平台 OpenWrite 发布!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容