今天朋友拋了一個群里的討論給我,如下圖

這題我選擇了A,B,因為我認(rèn)為A和B都不滿足最左;最后朋友實際建表的測試結(jié)果如下(也就是說答案應(yīng)該是A,B,D)

按理說不等于不走索引應(yīng)該是SQL優(yōu)化的常識了,為什么我在選擇的時候排除了D呢?因為2個月前我遇到了這樣一個語句
SELECT MAX(id)
FROM a
WHERE user_id != '18'
AND '2019-02-13 00:00:00' >= create_time
GROUP BY user_id;

因為這個語句的關(guān)系,我一度認(rèn)為在5.7.25版本里不等于也已經(jīng)開始智能的走索引了;經(jīng)過這個選擇題,決定仔細(xì)探索一下MySQL的不等于
對原語句進(jìn)行不同的改寫
備注:
①索引idx_uid_ct為user_id和create_time的聯(lián)合索引idx_uid_ct(user_id,create_time)
②后面為了測試,特意刪除了idx_ct(create_time)
③id為表的主鍵列
eg1.去除group by的影響,type為range,覆蓋索引

eg2.去除max函數(shù),type為range,覆蓋索引

eg3.查詢項改為create_time、user_id、id中的任意1到3個,type為range,覆蓋索引

eg4.查詢項改為*或者是索引列(user_id,create_time,id)之外的任意值,索引前導(dǎo)列存在不等于,不走索引

eg5.查詢項改為*或者是索引列(user_id,create_time,id)之外的任意值,索引的非前導(dǎo)列存在不等于,走索引,extra為ICP

eg6.查詢項改為create_time或者user_id,where條件改為只有非前導(dǎo)列create_time,不滿足idx_uid_ct最左匹配規(guī)則,type為index,覆蓋索引

eg7.查詢項改為索引列(user_id,create_time,id)之外的任意值,where條件將user_id的不等于改為等于,create_time條件不變,type為range,Extra為ICP

eg8.查詢項改為索引列(user_id,create_time,id)之外的任意值,where條件將user_id的不等于改為大于或者小于,create_time條件不變,是否走索引需要優(yōu)化器進(jìn)行判斷


eg9.查詢項改為索引列(user_id,create_time,id)之中的任意1-3個值,where條件將user_id的不等于改為大于或者小于,create_time條件不變,type為range,覆蓋索引

結(jié)論
①對比上面的9個eg可以看出,當(dāng)查詢列位于主鍵或者索引列范圍內(nèi)的時候(或者像原語句那樣是主鍵/索引列的分組函數(shù)),整個語句會直接走覆蓋索引,這種情況下無論where條件里有沒有不等于都不會有影響
②參考eg4可知查詢列在索引列或主鍵列的范圍之外,索引前導(dǎo)列存在不等于,不走索引
③參考eg5可知,查詢列在索引列或主鍵列的范圍之外,索引非前導(dǎo)列存在不等于,仍然走索引,此時需要回表,因此extra為ICP。
④對比eg5和eg7,兩者的執(zhí)行計劃中的extra均為ICP,但key_len和type不同;eg5的非前導(dǎo)列在where條件中存在不等于,因此聯(lián)合索引中的非前導(dǎo)列create_time并沒有被利用到,所以key_len只有99;eg7的非前導(dǎo)列條件where條件中是一個范圍,由于是where條件中的第一個范圍,因此這個范圍可以走索引,所以key_len為105;eg5和eg7之所以都是ICP是因為查詢列user_name需要回表
⑤參考eg9,當(dāng)滿足覆蓋索引條件的時候,即使在where條件中有雙范圍,第一個范圍走了索引,第二個范圍沒有走索引(key_len為99)
⑥參考eg8,不滿足覆蓋索引條件時,where條件中只有第一個范圍能被索引利用到,是否走索引優(yōu)化器自身會做權(quán)衡,當(dāng)取值量太大時索引反而不如全表
⑦eg6是一個我不能理解的eg,滿足覆蓋索引條件時,即使where條件不滿足索引的最左規(guī)則,語句仍然走了索引只不過type為極為低效的index(全索引掃描),extra顯示此時仍然是覆蓋索引;有趣的是這里的key_len竟然是105,而實際上where條件里面只有create_time(該列的key_len應(yīng)該只有6才對)
綜上可以知道在覆蓋索引的情況下,不等于仍然會走索引(無論這個不等于是否存在于前導(dǎo)列的where條件上),而多重范圍的第一個范圍會走索引,而后面的多重范圍則不會走索引,只是用來作為過濾條件使用;
在非覆蓋的索引下,不等于不會走索引(如果是前導(dǎo)列存在不等于,這個索引直接不會走;如果是非前導(dǎo)列存在不等于,這個索引有可能被用到,但即使索引被用到,索引中這個存在不等于的列也不會被用到),而多重范圍的后幾層范圍肯定不會被用到,第一層范圍是否會用到需要優(yōu)化器進(jìn)行權(quán)衡
補充
SELECT sum( amount ) FROM t
WHERE
STATUS = 'success'
AND partner_id != 'aaa' AND partner_id != 'bbb' AND partner_id != 'ccc'
AND date_format( update_time, '%y-%m-%d' ) = date_format( now( ), '%y-%m-%d' );
建立測試索引
idx_0(status,partner_id,update_time,amount)
idx_1(status,partner_id,amount)
①在采用idx_1時,索引只走了idx_1中的status部分,這時候存在不等于條件的partner_id只是起到了過濾的作用(這個過濾作用使得該語句執(zhí)行速度提升10倍)
②在采用idx_0時,索引走了idx_0中的status和partner_id部分,這時候因為已經(jīng)是覆蓋查詢了,根據(jù)前面的實驗可以知道不等于也走了索引,但是最末端的update_time仍然沒有走索引,而只是起到了過濾的作用(這個過濾作用使得該語句在之前提升10倍的基礎(chǔ)上再提升了10倍)
結(jié)論
在覆蓋索引的情況下,不等于會走索引,多重范圍不會走索引,函數(shù)也不會走索引