SQL優(yōu)化INNER JOIN子查詢代替EXISTS

需求是將未分配的記錄,按照指定排序選一條分配給一個人。
最原始的實(shí)現(xiàn)是,查詢更新,先加鎖,select出一條,再update,然后釋放鎖,由于select很慢,還提前生成了緩存,兩分鐘生成一次。

經(jīng)過梳理,得到以下SQL,用的exists語法,直接update,利用MySQL的鎖機(jī)制保證不會重復(fù)分配。

UPDATE com_info c SET infoOperatorName='wy-test', infoOperatorFlag=1 
WHERE deleteFlag=0 AND infoType='F道路封閉' AND infoOperatorFlag=0 AND infoOperatorName='' 
AND EXISTS (SELECT 1 FROM ext_info e WHERE e.infoCode=c.infoCode AND e.infoCheckResult='有效') 
ORDER BY DATE(infoCreateTime) DESC, catagory_priority, city_priority, infoLevel DESC, infoCreateTime DESC LIMIT 1 ;

實(shí)測耗時4s左右,很穩(wěn)定。

一番優(yōu)化后,舍棄了exists,改用inner join + 子查詢,如下:

UPDATE com_info t1 INNER JOIN (
    SELECT c.infoCode FROM com_info c INNER JOIN ext_info e ON c.infoCode=e.infoCode  
    WHERE deleteFlag=0 AND infoType='F道路封閉' AND infoOperatorFlag=0 AND infoOperatorName='' 
    AND e.infoCheckResult='有效'
    ORDER BY DATE(infoCreateTime) DESC, catagory_priority, city_priority, infoLevel DESC, infoCreateTime DESC LIMIT 1
) t2 ON t1.infoCode=t2.infoCode SET infoOperatorName='wy-test', infoOperatorFlag=1;

實(shí)測耗時0.28s,很穩(wěn)定。

以下是執(zhí)行情況和執(zhí)行計劃:


執(zhí)行耗時

執(zhí)行計劃

這里INNER JOINEXISTS快的原因,其實(shí)也看場景。
由于兩張表的規(guī)模是一樣的,并且主鍵相同,所以JOIN會走主鍵索引,非常快。EXISTS反而會去遍歷了。

掃描方式 type,從慢到快:

  1. all : 全表掃描
  2. index : 全索引掃描,和全表掃描一樣。只是掃描表的時候按照索引次序進(jìn)行而不是行。主要優(yōu)點(diǎn)就是避免了排序, 但是開銷仍然非常大。如在Extra列看到Using index,說明正在使用覆蓋索引,只掃描索引的數(shù)據(jù),它比按索引次序全表掃描的開銷要小很多。這個比all效率要好一點(diǎn),主要有幾種情況,一是當(dāng)前的查詢是覆蓋索引的,即我們需要的數(shù)據(jù)在索引中就可以獲?。‥xtra中有Using Index),或者是使用了索引進(jìn)行排序,這樣就避免數(shù)據(jù)的重排序(Extra中無 Using Index)。如果Extra中Using Index與Using Where同時出現(xiàn)的話,則是利用索引查找鍵值的意思
  3. range : 范圍查詢,避免了全索引掃描,限制的范圍越小,效率越高
  4. index_subquery : 在 某 些 IN 查 詢 中 使 用 此 種 類 型 , 與 unique_subquery 類似,但是查詢的是非唯一 性索引
  5. unique_subquery : 在某些 IN 查詢中使用此種類型,而不是常規(guī)的 ref
  6. index_merge : 說明索引合并優(yōu)化被使用了
  7. ref_or_null : 如同 ref, 但是 MySQL 必須在初次查找的結(jié)果 里找出 null 條目,然后進(jìn)行二次查找。
  8. ref : 使用了非唯一性索引進(jìn)行數(shù)據(jù)的查找
  9. eq_ref : 使用的唯一性索引進(jìn)行數(shù)據(jù)查找,例如主鍵索引之類的
  10. const : 通常情況下,將一個主鍵放置到where后面作為條件查詢,mysql優(yōu)化器就能把這次查詢優(yōu)化轉(zhuǎn)化為一個常量,如何轉(zhuǎn)化以及何時轉(zhuǎn)化,這個取決于優(yōu)化器。這個比eq_ref效率高一點(diǎn)。
  11. system : 表只有一行。不過這種情況下就沒意義了。
  12. NULL : MySQL不用訪問表或者索引就直接能到結(jié)果。

(關(guān)于覆蓋索引:MySQL系列-優(yōu)化之覆蓋索引

測試時發(fā)現(xiàn),由于com_info表的查詢需求很多,因此針對不同的場景,對不同的字段建了各種索引,在測試數(shù)據(jù)庫和線上數(shù)據(jù)庫上測試發(fā)現(xiàn),不同的數(shù)據(jù)分布下,MySQL對索引的選取差別很大,線上庫采用了專門為這個場景建的聯(lián)合索引(包含了WHERE中需要的字段),而測試庫中則采用了index_merge的方式,挨個索引走一遍,取交集,實(shí)測index_merge還是要比ref慢一兩個數(shù)量級。

另外index_merge在這個場景下會引發(fā)死鎖。
因?yàn)檫@里用到了

UPDATE ... WHERE ... ORDER BY ... LIMIT ...;

由于有ORDER BY LIMIT存在,UPDARE的時候,會鎖住命中的所有行。而index_merge在高并發(fā)的時候,如果索引行數(shù)有重疊,因?yàn)榧渔i的順序可能不同,互相等待可能會導(dǎo)致死鎖。加鎖順序的原因是,MySQL會先用索引1進(jìn)行掃表,再用索引2進(jìn)行掃表,然后求交集形成一個合并索引。這個使用索引掃表的過程和我們本身SQL使用索引的順序可能存在互斥,造成了死鎖。

這里有個場景可能引發(fā)死鎖:
請求的WHERE條件不完全一樣,但包含了相同的某兩個字段,可能造成上述的問題。

index_merge引發(fā)的死鎖排查

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,652評論 18 399
  • 寫在開頭 這里所說的劣質(zhì)SQL限定在數(shù)據(jù)量未到分庫分表和使用分布式緩存程度,指那些執(zhí)行較差的查詢、插入、更新、刪除...
    alivs閱讀 793評論 0 0
  • 有了孩子的人都會有這樣一個感觸,“不是你教育孩子,而是孩子在教導(dǎo)你“,他們是天生的哲學(xué)家,生來就通曉一切的真理。 ...
    果蛋皮閱讀 828評論 3 2
  • 大腦如同肌肉,如果長期不用的話,真的是會退化的。人類一旦停止了稍微有深度的思考,面對問題的時候,就完全是憑著原始大...
    原同學(xué)閱讀 17,095評論 4 3
  • 我一直以為自己心靜如水 直到遇見他―― 一個逆著光行走的男孩 他驀然轉(zhuǎn)頭 笑了 有那么一瞬間我看呆了 但我并沒有以...
    仙女味的我閱讀 170評論 0 0

友情鏈接更多精彩內(nèi)容