<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一次非常有意思的 SQL 優(yōu)化經(jīng)歷:從 30248.271s 到 0.001s

          共 5553字,需瀏覽 12分鐘

           ·

          2022-04-11 15:47

          作者:?風過無痕的博客?

          cnblogs.com/tangyanbo/p/4462734.html


          場景

          我用的數(shù)據(jù)庫是mysql5.6,下面簡單的介紹下場景

          課程表

          create?table?Course(

          c_id?int?PRIMARY?KEY,

          name?varchar(10)


          )

          數(shù)據(jù)100條

          學生表:

          create?table?Student(

          id?int?PRIMARY?KEY,

          name?varchar(10)


          )

          數(shù)據(jù)70000條

          學生成績表SC

          CREATE?table?SC(

          ????sc_id?int?PRIMARY?KEY,

          ????s_id?int,

          ????c_id?int,

          ????score?int

          )

          數(shù)據(jù)70w條

          查詢目的:

          查找語文考100分的考生

          查詢語句:

          select?s.*?from?Student?s?where?s.s_id?in?(select?s_id?from?SC?sc?where?sc.c_id?=?0?and?sc.score?=?100?)

          執(zhí)行時間:30248.271s

          暈,為什么這么慢,先來查看下查詢計劃:

          EXPLAIN

          select?s.*?from?Student?s?where?s.s_id?in?(select?s_id?from?SC?sc?where?sc.c_id?=?0?and?sc.score?=?100?)

          發(fā)現(xiàn)沒有用到索引,type全是ALL,那么首先想到的就是建立一個索引,建立索引的字段當然是在where條件的字段。

          先給sc表的c_id和score建個索引

          CREATE?index?sc_c_id_index?on?SC(c_id);
          CREATE?index?sc_score_index?on?SC(score);

          再次執(zhí)行上述查詢語句,時間為: 1.054s

          快了3w多倍,大大縮短了查詢時間,看來索引能極大程度的提高查詢效率,建索引很有必要,很多時候都忘記建

          索引了,數(shù)據(jù)量小的的時候壓根沒感覺,這優(yōu)化的感覺挺爽。

          但是1s的時間還是太長了,還能進行優(yōu)化嗎,仔細看執(zhí)行計劃:

          查看優(yōu)化后的sql:

          SELECT
          ????`YSB`.`s`.`s_id`?AS?`s_id`,
          ????`YSB`.`s`.`name`?AS?`name`
          FROM
          ????`YSB`.`Student`?`s`
          WHERE
          ?????(
          ????????`YSB`.`s`.`s_id`?,?(
          ????????????SELECT
          ????????????????1
          ????????????FROM
          ????????????????`YSB`.`SC`?`sc`
          ????????????WHERE
          ????????????????(
          ????????????????????(`YSB`.`sc`.`c_id`?=?0)
          ????????????????????AND?(`YSB`.`sc`.`score`?=?100)
          ????????????????????AND?(
          ?????????????????????????(`YSB`.`s`.`s_id`)?=?`YSB`.`sc`.`s_id`
          ????????????????????)
          ????????????????)
          ????????)
          ????)

          補充:這里有網(wǎng)友問怎么查看優(yōu)化后的語句

          方法如下:

          在命令窗口執(zhí)行

          有type=all

          按照我之前的想法,該sql的執(zhí)行的順序應該是先執(zhí)行子查詢

          select?s_id?from?SC?sc?where?sc.c_id?=?0?and?sc.score?=?100

          耗時:0.001s

          得到如下結果:

          然后再執(zhí)行

          select?s.*?from?Student?s?where?s.s_id?in(7,29,5000)

          耗時:0.001s

          這樣就是相當快了啊,Mysql竟然不是先執(zhí)行里層的查詢,而是將sql優(yōu)化成了exists子句,并出現(xiàn)了EPENDENT SUBQUERY,

          mysql是先執(zhí)行外層查詢,再執(zhí)行里層的查詢,這樣就要循環(huán)70007*8次。

          那么改用連接查詢呢?

          SELECT?s.*?from

          Student?s

          INNER?JOIN?SC?sc

          on?sc.s_id?=?s.s_id

          where?sc.c_id=0?and?sc.score=100

          這里為了重新分析連接查詢的情況,先暫時刪除索引sc_c_id_index,sc_score_index

          執(zhí)行時間是:0.057s

          效率有所提高,看看執(zhí)行計劃:

          這里有連表的情況出現(xiàn),我猜想是不是要給sc表的s_id建立個索引

          CREATE index sc_s_id_index on SC(s_id);

          show index from SC

          在執(zhí)行連接查詢

          時間: 1.076s,竟然時間還變長了,什么原因?查看執(zhí)行計劃:

          優(yōu)化后的查詢語句為:

          SELECT
          ????`YSB`.`s`.`s_id`?AS?`s_id`,
          ????`YSB`.`s`.`name`?AS?`name`
          FROM
          ????`YSB`.`Student`?`s`
          JOIN?`YSB`.`SC`?`sc`
          WHERE
          ????(
          ????????(
          ????????????`YSB`.`sc`.`s_id`?=?`YSB`.`s`.`s_id`
          ????????)
          ????????AND?(`YSB`.`sc`.`score`?=?100)
          ????????AND?(`YSB`.`sc`.`c_id`?=?0)
          ????)

          貌似是先做的連接查詢,再進行的where條件過濾

          回到前面的執(zhí)行計劃:

          這里是先做的where條件過濾,再做連表,執(zhí)行計劃還不是固定的,那么我們先看下標準的sql執(zhí)行順序:

          正常情況下是先join再進行where過濾,但是我們這里的情況,如果先join,將會有70w條數(shù)據(jù)發(fā)送join做操,因此先執(zhí)行where

          過濾是明智方案,現(xiàn)在為了排除mysql的查詢優(yōu)化,我自己寫一條優(yōu)化后的sql

          SELECT
          ????s.*
          FROM
          ????(
          ????????SELECT
          ????????????*
          ????????FROM
          ????????????SC?sc
          ????????WHERE
          ????????????sc.c_id?=?0
          ????????AND?sc.score?=?100
          ????)?t
          INNER?JOIN?Student?s?ON?t.s_id?=?s.s_id

          即先執(zhí)行sc表的過濾,再進行表連接,執(zhí)行時間為:0.054s

          和之前沒有建s_id索引的時間差不多

          查看執(zhí)行計劃:

          先提取sc再連表,這樣效率就高多了,現(xiàn)在的問題是提取sc的時候出現(xiàn)了掃描表,那么現(xiàn)在可以明確需要建立相關索引

          CREATE?index?sc_c_id_index?on?SC(c_id);
          CREATE?index?sc_score_index?on?SC(score);

          再執(zhí)行查詢:

          SELECT
          ????s.*
          FROM
          ????(
          ????????SELECT
          ????????????*
          ????????FROM
          ????????????SC?sc
          ????????WHERE
          ????????????sc.c_id?=?0
          ????????AND?sc.score?=?100
          ????)?t
          INNER?JOIN?Student?s?ON?t.s_id?=?s.s_id

          執(zhí)行時間為:0.001s,這個時間相當靠譜,快了50倍

          執(zhí)行計劃:

          我們會看到,先提取sc,再連表,都用到了索引。

          那么再來執(zhí)行下sql

          SELECT?s.*?from

          Student?s

          INNER?JOIN?SC?sc

          on?sc.s_id?=?s.s_id

          where?sc.c_id=0?and?sc.score=100

          執(zhí)行時間0.001s

          執(zhí)行計劃:

          這里是mysql進行了查詢語句優(yōu)化,先執(zhí)行了where過濾,再執(zhí)行連接操作,且都用到了索引。

          2015-04-30日補充:最近又重新導入一些生產(chǎn)數(shù)據(jù),經(jīng)測試發(fā)現(xiàn),前幾天優(yōu)化完的sql執(zhí)行效率又變低了

          調(diào)整內(nèi)容為SC表的數(shù)據(jù)增長到300W,學生分數(shù)更為離散。

          先回顧下:

          show index from SC

          執(zhí)行sql
          SELECT?s.*?from

          Student?s

          INNER?JOIN?SC?sc

          on?sc.s_id?=?s.s_id

          where?sc.c_id=81?and?sc.score=84


          執(zhí)行時間:0.061s,這個時間稍微慢了點

          執(zhí)行計劃:

          這里用到了intersect并集操作,即兩個索引同時檢索的結果再求并集,再看字段score和c_id的區(qū)分度,

          單從一個字段看,區(qū)分度都不是很大,從SC表檢索,c_id=81檢索的結果是70001,score=84的結果是39425

          而c_id=81 and score=84 的結果是897,即這兩個字段聯(lián)合起來的區(qū)分度是比較高的,因此建立聯(lián)合索引查詢效率

          將會更高,從另外一個角度看,該表的數(shù)據(jù)是300w,以后會更多,就索引存儲而言,都是不小的數(shù)目,隨著數(shù)據(jù)量的

          增加,索引就不能全部加載到內(nèi)存,而是要從磁盤去讀取,這樣索引的個數(shù)越多,讀磁盤的開銷就越大,因此根據(jù)具體

          業(yè)務情況建立多列的聯(lián)合索引是必要的,那么我們來試試吧。

          alter?table?SC?drop?index?sc_c_id_index;
          alter?table?SC?drop?index?sc_score_index;
          create?index?sc_c_id_score_index?on?SC(c_id,score);

          執(zhí)行上述查詢語句,消耗時間為:0.007s,這個速度還是可以接收的

          執(zhí)行計劃:

          該語句的優(yōu)化暫時告一段落

          總結:

          1.mysql嵌套子查詢效率確實比較低

          2.可以將其優(yōu)化成連接查詢

          3.連接表時,可以先用where條件對表進行過濾,然后做表連接

          (雖然mysql會對連表語句做優(yōu)化)

          4.建立合適的索引,必要時建立多列聯(lián)合索引

          5.學會分析sql執(zhí)行計劃,mysql會對sql進行優(yōu)化,所以分析執(zhí)行計劃很重要

          索引優(yōu)化

          上面講到子查詢的優(yōu)化,以及如何建立索引,而且在多個字段索引時,分別對字段建立了單個索引

          后面發(fā)現(xiàn)其實建立聯(lián)合索引效率會更高,尤其是在數(shù)據(jù)量較大,單個列區(qū)分度不高的情況下。

          單列索引

          查詢語句如下:

          select?*?from?user_test_copy?where?sex?=?2?and?type?=?2?and?age?=?10

          索引:

          CREATE?index?user_test_index_sex?on?user_test_copy(sex);
          CREATE?index?user_test_index_type?on?user_test_copy(type);
          CREATE?index?user_test_index_age?on?user_test_copy(age);

          分別對sex,type,age字段做了索引,數(shù)據(jù)量為300w,查詢時間:0.415s

          執(zhí)行計劃:

          發(fā)現(xiàn)type=index_merge

          這是mysql對多個單列索引的優(yōu)化,對結果集采用intersect并集操作

          多列索引

          我們可以在這3個列上建立多列索引,將表copy一份以便做測試

          create?index?user_test_index_sex_type_age?on?user_test(sex,type,age);

          查詢語句:

          select?*?from?user_test?where?sex?=?2?and?type?=?2?and?age?=?10

          執(zhí)行時間:0.032s,快了10多倍,且多列索引的區(qū)分度越高,提高的速度也越多

          執(zhí)行計劃:

          最左前綴

          多列索引還有最左前綴的特性:

          執(zhí)行一下語句:

          select?*?from?user_test?where?sex?=?2
          select?*?from?user_test?where?sex?=?2?and?type?=?2
          select?*?from?user_test?where?sex?=?2?and?age?=?10

          都會使用到索引,即索引的第一個字段sex要出現(xiàn)在where條件中

          索引覆蓋

          就是查詢的列都建立了索引,這樣在獲取結果集的時候不用再去磁盤獲取其它列的數(shù)據(jù),直接返回索引數(shù)據(jù)即可

          如:

          select?sex,type,age?from?user_test?where?sex?=?2?and?type?=?2?and?age?=?10

          執(zhí)行時間:0.003s

          要比取所有字段快的多

          排序

          select?*?from?user_test?where?sex?=?2?and?type?=?2?ORDER?BY?user_name

          時間:0.139s

          在排序字段上建立索引會提高排序的效率

          create?index?user_name_index?on?user_test(user_name)

          最后附上一些sql調(diào)優(yōu)的總結,以后有時間再深入研究

          1. 列類型盡量定義成數(shù)值類型,且長度盡可能短,如主鍵和外鍵,類型字段等等

          2. 建立單列索引

          3. 根據(jù)需要建立多列聯(lián)合索引

          當單個列過濾之后還有很多數(shù)據(jù),那么索引的效率將會比較低,即列的區(qū)分度較低,

          那么如果在多個列上建立索引,那么多個列的區(qū)分度就大多了,將會有顯著的效率提高。

          1. 根據(jù)業(yè)務場景建立覆蓋索引

          只查詢業(yè)務需要的字段,如果這些字段被索引覆蓋,將極大的提高查詢效率

          1. 多表連接的字段上需要建立索引

          這樣可以極大的提高表連接的效率

          1. where條件字段上需要建立索引

          2. 排序字段上需要建立索引

          3. 分組字段上需要建立索引

          4. Where條件上不要使用運算函數(shù),以免索引失效

          瀏覽 23
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  地址一地址二日本视频免费 | 日韩肏逼| 亚洲免费观看高清无码在线观看 | 国产无码第一页 | 男人捅女人国产精品 |