<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>

          啥?我寫(xiě)的一條SQL讓公司網(wǎng)站癱瘓了...

          共 15078字,需瀏覽 31分鐘

           ·

          2020-11-01 21:44

          一條慢查詢(xún)會(huì)造成什么后果?之前我一直覺(jué)得不就是返回?cái)?shù)據(jù)會(huì)慢一些么,用戶(hù)體驗(yàn)變差?


          圖片來(lái)自 Pexels


          其實(shí)遠(yuǎn)遠(yuǎn)不止,我經(jīng)歷過(guò)幾次線上事故,有一次就是由一條 SQL 慢查詢(xún)導(dǎo)致的。

          那次是一條 SQL 查詢(xún)耗時(shí)達(dá)到 2-3 秒「沒(méi)有命中索引,導(dǎo)致全表掃描」,由于是高頻查詢(xún),并發(fā)一起來(lái)很快就把 DB 線程池打滿(mǎn)了,導(dǎo)致大量查詢(xún)請(qǐng)求堆積,DB 服務(wù)器 CPU 長(zhǎng)時(shí)間 100%+,大量請(qǐng)求 timeout...


          最終系統(tǒng)崩潰,老板登場(chǎng)!可見(jiàn),團(tuán)隊(duì)如果對(duì)慢查詢(xún)不引起足夠的重視,風(fēng)險(xiǎn)是很大的。


          經(jīng)過(guò)那次事故我們老板就說(shuō)了:誰(shuí)的代碼再出現(xiàn)類(lèi)似事故,開(kāi)發(fā)和部門(mén)領(lǐng)導(dǎo)一起走人,嚇得一大堆領(lǐng)導(dǎo)心發(fā)慌,趕緊招了兩位 DBA 同事???。

          慢查詢(xún),顧名思義,執(zhí)行很慢的查詢(xún)。有多慢?超過(guò) long_query_time 參數(shù)設(shè)定的時(shí)間閾值(默認(rèn) 10s),就被認(rèn)為是慢的,是需要優(yōu)化的。慢查詢(xún)被記錄在慢查詢(xún)?nèi)罩纠铩?/span>


          慢查詢(xún)?nèi)罩灸J(rèn)是不開(kāi)啟的,如果你需要優(yōu)化 SQL 語(yǔ)句,就可以開(kāi)啟這個(gè)功能,它可以讓你很容易地知道哪些語(yǔ)句是需要優(yōu)化的(想想一個(gè) SQL 要 10s 就可怕)。好了,下面我們就一起來(lái)看看怎么處理慢查詢(xún)。


          慢查詢(xún)配置


          開(kāi)啟慢查詢(xún)


          MySQL 支持通過(guò)以下方式開(kāi)啟慢查詢(xún):

          • 輸入命令開(kāi)啟慢查詢(xún)(臨時(shí)),在 MySQL 服務(wù)重啟后會(huì)自動(dòng)關(guān)閉。

          • 配置 my.cnf(Windows 是 my.ini)系統(tǒng)文件開(kāi)啟,修改配置文件是持久化開(kāi)啟慢查詢(xún)的方式。


          方式一:通過(guò)命令開(kāi)啟慢查詢(xún)


          步驟 1:查詢(xún) slow_query_log 查看是否已開(kāi)啟慢查詢(xún)?nèi)罩荆?br>
          show?variables?like?'%slow_query_log%';

          mysql>?show?variables?like?'%slow_query_log%';
          +---------------------+-----------------------------------+
          |?Variable_name???????|?Value?????????????????????????????|
          +---------------------+-----------------------------------+
          |
          ?slow_query_log??????|?OFF???????????????????????????????|
          |?slow_query_log_file?|?/var/lib/mysql/localhost-slow.log?|
          +---------------------+-----------------------------------+
          2?rows?in?set?(0.01?sec)

          步驟 2:開(kāi)啟慢查詢(xún)命令:
          set?global?slow_query_log='ON';

          步驟 3:指定記錄慢查詢(xún)?nèi)罩?SQL 執(zhí)行時(shí)間得閾值(long_query_time 單位:秒,默認(rèn) 10 秒)。


          如下我設(shè)置成了 1 秒,執(zhí)行時(shí)間超過(guò) 1 秒的 SQL 將記錄到慢查詢(xún)?nèi)罩局校?br>

          set?global?long_query_time=1;

          步驟 4:查詢(xún) “慢查詢(xún)?nèi)罩疚募娣盼恢谩薄?br>
          show?variables?like?'%slow_query_log_file%';

          mysql>?show?variables?like?'%slow_query_log_file%';
          +---------------------+-----------------------------------+
          |?Variable_name???????|?Value?????????????????????????????|
          +---------------------+-----------------------------------+
          |?slow_query_log_file?|?/var/lib/mysql/localhost-slow.log?|
          +---------------------+-----------------------------------+
          1?row?in?set?(0.01?sec)

          slow_query_log_file 指定慢查詢(xún)?nèi)罩镜拇鎯?chǔ)路徑及文件(默認(rèn)和數(shù)據(jù)文件放一起)。


          步驟 5:核對(duì)慢查詢(xún)開(kāi)啟狀態(tài),需要退出當(dāng)前 MySQL 終端,重新登錄即可刷新。


          配置了慢查詢(xún)后,它會(huì)記錄以下符合條件的 SQL:

          • 查詢(xún)語(yǔ)句

          • 數(shù)據(jù)修改語(yǔ)句

          • 已經(jīng)回滾的 SQL


          方式二:通過(guò)配置 my.cnf(Windows 是 my.ini)系統(tǒng)文件開(kāi)啟(版本:MySQL 5.5 及以上)。


          在 my.cnf 文件的 [mysqld] 下增加如下配置開(kāi)啟慢查詢(xún),如下圖:
          #?開(kāi)啟慢查詢(xún)功能
          slow_query_log=ON
          #?指定記錄慢查詢(xún)?nèi)罩維QL執(zhí)行時(shí)間得閾值
          long_query_time=1
          #?選填,默認(rèn)數(shù)據(jù)文件路徑
          #?slow_query_log_file=/var/lib/mysql/localhost-slow.log

          重啟數(shù)據(jù)庫(kù)后即持久化開(kāi)啟慢查詢(xún),查詢(xún)驗(yàn)證如下:
          mysql>?show?variables?like?'%_query_%';
          +------------------------------+-----------------------------------+
          |?Variable_name????????????????|?Value?????????????????????????????|
          +------------------------------+-----------------------------------+
          |
          ?have_query_cache?????????????|?YES???????????????????????????????|
          |?long_query_time??????????????|?1.000000??????????????????????????|
          |
          ?slow_query_log???????????????|?ON????????????????????????????????|
          |?slow_query_log_file??????????|?/var/lib/mysql/localhost-slow.log?|
          +------------------------------+-----------------------------------+
          6?rows?in?set?(0.01?sec)


          慢查詢(xún)?nèi)罩窘榻B


          如上圖,是執(zhí)行時(shí)間超過(guò) 1 秒的 SQL 語(yǔ)句(測(cè)試):

          • 第一行:記錄時(shí)間。

          • 第二行:用戶(hù)名 、用戶(hù)的 IP 信息、線程 ID 號(hào)。

          • 第三行:執(zhí)行花費(fèi)的時(shí)間【單位:秒】、執(zhí)行獲得鎖的時(shí)間、獲得的結(jié)果行數(shù)、掃描的數(shù)據(jù)行數(shù)。

          • 第四行:這 SQL 執(zhí)行的時(shí)間戳。

          • 第五行:具體的 SQL 語(yǔ)句。


          Explain 分析慢查詢(xún) SQL


          分析 MySQL 慢查詢(xún)?nèi)罩荆?Explain 關(guān)鍵字可以模擬優(yōu)化器執(zhí)行 SQL 查詢(xún)語(yǔ)句,來(lái)分析 SQL 慢查詢(xún)語(yǔ)句。


          下面我們的測(cè)試表是一張 137w 數(shù)據(jù)的 app 信息表,我們來(lái)舉例分析一下。


          SQL 示例如下:

          --?1.185s
          SELECT?*?from?vio_basic_domain_info?where?app_name?like?'%翻譯%'?;


          這是一條普通的模糊查詢(xún)語(yǔ)句,查詢(xún)耗時(shí):1.185s,查到了 148 條數(shù)據(jù)。


          我們用 Explain 分析結(jié)果如下表,根據(jù)表信息可知:該 SQL 沒(méi)有用到字段 app_name 上的索引,查詢(xún)類(lèi)型是全表掃描,掃描行數(shù) 137w。

          mysql>?EXPLAIN?SELECT?*?from?vio_basic_domain_info?where?app_name?like?'%翻譯%'?;
          +----+-------------+-----------------------+------------+------+---------------+------+---------+------+---------+----------+-------------+
          |?id?|?select_type?|?table?????????????????|?partitions?|?type?|?possible_keys?|?key??|?key_len?|?ref??|?rows????|?filtered?|?Extra???????|
          +----+-------------+-----------------------+------------+------+---------------+------+---------+------+---------+----------+-------------+
          |
          ??1?|?SIMPLE??????|?vio_basic_domain_info?|?NULL???????|?ALL??|?NULL??????????|?NULL?|?NULL????|?NULL?|?1377809?|????11.11?|?Using?where?|
          +----+-------------+-----------------------+------------+------+---------------+------+---------+------+---------+----------+-------------+
          1?row?in?set,?1?warning?(0.00?sec)


          當(dāng)這條 SQL 使用到索引時(shí),SQL 如下:查詢(xún)耗時(shí):0.156s,查到 141 條數(shù)據(jù):

          --?0.156s
          SELECT?*?from?vio_basic_domain_info?where?app_name?like?'翻譯%'?;


          Explain 分析結(jié)果如下表;根據(jù)表信息可知:該 SQL 用到了 idx_app_name 索引,查詢(xún)類(lèi)型是索引范圍查詢(xún),掃描行數(shù) 141 行。


          由于查詢(xún)的列不全在索引中(select *),因此回表了一次,取了其他列的數(shù)據(jù)。

          mysql>?EXPLAIN?SELECT?*?from?vio_basic_domain_info?where?app_name?like?'翻譯%'?;
          +----+-------------+-----------------------+------------+-------+---------------+--------------+---------+------+------+----------+-----------------------+
          |?id?|?select_type?|?table?????????????????|?partitions?|?type??|?possible_keys?|?key??????????|?key_len?|?ref??|?rows?|?filtered?|?Extra?????????????????|
          +----+-------------+-----------------------+------------+-------+---------------+--------------+---------+------+------+----------+-----------------------+
          |
          ??1?|?SIMPLE??????|?vio_basic_domain_info?|?NULL???????|?range?|?idx_app_name??|?idx_app_name?|?515?????|?NULL?|??141?|???100.00?|?Using?index?condition?|
          +----+-------------+-----------------------+------------+-------+---------------+--------------+---------+------+------+----------+-----------------------+
          1?row?in?set,?1?warning?(0.00?sec)


          當(dāng)這條 SQL 使用到覆蓋索引時(shí),SQL 如下:查詢(xún)耗時(shí):0.091s,查到 141 條數(shù)據(jù)。

          --?0.091s
          SELECT?app_name?from?vio_basic_domain_info?where?app_name?like?'翻譯%'?;


          Explain 分析結(jié)果如下表;根據(jù)表信息可知:和上面的 SQL 一樣使用到了索引,由于查詢(xún)列就包含在索引列中,又省去了 0.06s 的回表時(shí)間。

          mysql>?EXPLAIN?SELECT?app_name?from?vio_basic_domain_info?where?app_name?like?'翻譯%'?;
          +----+-------------+-----------------------+------------+-------+---------------+--------------+---------+------+------+----------+--------------------------+
          |?id?|?select_type?|?table?????????????????|?partitions?|?type??|?possible_keys?|?key??????????|?key_len?|?ref??|?rows?|?filtered?|?Extra????????????????????|
          +----+-------------+-----------------------+------------+-------+---------------+--------------+---------+------+------+----------+--------------------------+
          |
          ??1?|?SIMPLE??????|?vio_basic_domain_info?|?NULL???????|?range?|?idx_app_name??|?idx_app_name?|?515?????|?NULL?|??141?|???100.00?|?Using?where;?Using?index?|
          +----+-------------+-----------------------+------------+-------+---------------+--------------+---------+------+------+----------+--------------------------+
          1?row?in?set,?1?warning?(0.00?sec)


          那么是如何通過(guò) EXPLAIN 解析結(jié)果分析 SQL 的呢?各列屬性又代表著什么?一起往下看。

          各列屬性的簡(jiǎn)介


          各列屬性的簡(jiǎn)介如下:

          • id:SELECT 的查詢(xún)序列號(hào),體現(xiàn)執(zhí)行優(yōu)先級(jí),如果是子查詢(xún),id的序號(hào)會(huì)遞增,id 值越大優(yōu)先級(jí)越高,越先被執(zhí)行。

          • select_type:表示查詢(xún)的類(lèi)型。

          • table:輸出結(jié)果集的表,如設(shè)置了別名,也會(huì)顯示。

          • partitions:匹配的分區(qū)。

          • type:對(duì)表的訪問(wèn)方式。

          • possible_keys:表示查詢(xún)時(shí),可能使用的索引。

          • key:表示實(shí)際使用的索引。

          • key_len:索引字段的長(zhǎng)度。

          • ref:列與索引的比較。

          • rows:掃描出的行數(shù)(估算的行數(shù))。

          • filtered:按表?xiàng)l件過(guò)濾的行百分比。

          • Extra:執(zhí)行情況的描述和說(shuō)明。


          以上標(biāo)星的幾類(lèi)是我們優(yōu)化慢查詢(xún)時(shí)常用到的。


          慢查詢(xún)分析常用到的屬性


          ①type


          對(duì)表訪問(wèn)方式,表示 MySQL 在表中找到所需行的方式,又稱(chēng)“訪問(wèn)類(lèi)型”。


          存在的類(lèi)型有:ALL、index、range、ref、eq_ref、const、system、NULL(從左到右,性能從低到高)。


          介紹三個(gè)咱們天天見(jiàn)到的:
          • ALL:(Full Table Scan)MySQL 將遍歷全表以找到匹配的行,常說(shuō)的全表掃描。

          • Index:(Full Index Scan)Index 與 ALL 區(qū)別為 Index 類(lèi)型只遍歷索引樹(shù)。

          • Range:只檢索給定范圍的行,使用一個(gè)索引來(lái)選擇行。


          ②key


          key 列顯示了 SQL 實(shí)際使用索引,通常是 possible_keys 列中的索引之一,MySQL 優(yōu)化器一般會(huì)通過(guò)計(jì)算掃描行數(shù)來(lái)選擇更適合的索引,如果沒(méi)有選擇索引,則返回 NULL。


          當(dāng)然,MySQL 優(yōu)化器存在選擇索引錯(cuò)誤的情況,可以通過(guò)修改 SQL 強(qiáng)制MySQL“使用或忽視某個(gè)索引”:
          • 強(qiáng)制使用一個(gè)索引:FORCE INDEX (index_name)、USE INDEX (index_name)。

          • 強(qiáng)制忽略一個(gè)索引:IGNORE INDEX (index_name)。


          ③rows


          rows 是 MySQL 估計(jì)為了找到所需的行而要讀取(掃描)的行數(shù),可能不精確。

          ④Extra


          這一列顯示一些額外信息,很重要。

          Using index:查詢(xún)的列被索引覆蓋,并且 where 篩選條件是索引的是前導(dǎo)列,Extra 中為 Using index。意味著通過(guò)索引查找就能直接找到符合條件的數(shù)據(jù),無(wú)須回表。


          注:前導(dǎo)列一般指聯(lián)合索引中的第一列或“前幾列”,以及單列索引的情況;這里為了方便理解我統(tǒng)稱(chēng)為前導(dǎo)列。


          Using where:說(shuō)明 MySQL 服務(wù)器將在存儲(chǔ)引擎檢索行后再進(jìn)行過(guò)濾;即沒(méi)有用到索引,回表查詢(xún)。


          可能的原因:

          • 查詢(xún)的列未被索引覆蓋。

          • where 篩選條件非索引的前導(dǎo)列或無(wú)法正確使用到索引。


          Using temporary:這意味著 MySQL 在對(duì)查詢(xún)結(jié)果排序時(shí)會(huì)使用一個(gè)臨時(shí)表。


          Using filesort:說(shuō)明 MySQL 會(huì)對(duì)結(jié)果使用一個(gè)外部索引排序,而不是按索引次序從表里讀取行。


          Using index condition:查詢(xún)的列不全在索引中,where 條件中是一個(gè)前導(dǎo)列的范圍。


          Using where;Using index:查詢(xún)的列被索引覆蓋,并且 where 篩選條件是索引列之一,但不是索引的前導(dǎo)列或出現(xiàn)了其他影響直接使用索引的情況(如存在范圍篩選條件等),Extra 中為 Using where;Using index,意味著無(wú)法直接通過(guò)索引查找來(lái)查詢(xún)到符合條件的數(shù)據(jù),影響并不大。


          一些慢查詢(xún)優(yōu)化經(jīng)驗(yàn)分享


          優(yōu)化 LIMIT 分頁(yè)


          在系統(tǒng)中需要分頁(yè)的操作通常會(huì)使用 limit 加上偏移量的方法實(shí)現(xiàn),同時(shí)加上合適的 order by 子句。

          如果有對(duì)應(yīng)的索引,通常效率會(huì)不錯(cuò),否則 MySQL 需要做大量的文件排序操作。


          一個(gè)非常令人頭疼問(wèn)題就是當(dāng)偏移量非常大的時(shí)候,例如可能是 limit 1000000,10 這樣的查詢(xún)。


          這是 MySQL 需要查詢(xún) 1000000 條然后只返回最后 10 條,前面的 1000000 條記錄都將被舍棄,這樣的代價(jià)很高,會(huì)造成慢查詢(xún)。


          優(yōu)化此類(lèi)查詢(xún)的一個(gè)最簡(jiǎn)單的方法是盡可能的使用索引覆蓋掃描,而不是查詢(xún)所有的列。


          然后根據(jù)需要做一次關(guān)聯(lián)操作再返回所需的列。對(duì)于偏移量很大的時(shí)候這樣做的效率會(huì)得到很大提升。


          對(duì)于下面的查詢(xún):

          --?執(zhí)行耗時(shí):1.379s
          SELECT?*?from?vio_basic_domain_info?LIMIT?1000000,10;


          Explain 分析結(jié)果:

          mysql>?EXPLAIN?SELECT?*?from?vio_basic_domain_info?LIMIT?1000000,10;
          +----+-------------+-----------------------+------------+------+---------------+------+---------+------+---------+----------+-------+
          |?id?|?select_type?|?table?????????????????|?partitions?|?type?|?possible_keys?|?key??|?key_len?|?ref??|?rows????|?filtered?|?Extra?|
          +----+-------------+-----------------------+------------+------+---------------+------+---------+------+---------+----------+-------+
          |
          ??1?|?SIMPLE??????|?vio_basic_domain_info?|?NULL???????|?ALL??|?NULL??????????|?NULL?|?NULL????|?NULL?|?1377809?|???100.00?|?NULL??|
          +----+-------------+-----------------------+------------+------+---------------+------+---------+------+---------+----------+-------+
          1?row?in?set,?1?warning?(0.00?sec)


          該語(yǔ)句存在的最大問(wèn)題在于 limit M,N 中偏移量 M 太大,導(dǎo)致每次查詢(xún)都要先從整個(gè)表中找到滿(mǎn)足條件的前 M 條記錄,之后舍棄這 M 條記錄并從第 M+1 條記錄開(kāi)始再依次找到 N 條滿(mǎn)足條件的記錄。


          如果表非常大,且篩選字段沒(méi)有合適的索引,且 M 特別大那么這樣的代價(jià)是非常高的。

          那么如果我們下一次的查詢(xún)能從前一次查詢(xún)結(jié)束后標(biāo)記的位置開(kāi)始查找,找到滿(mǎn)足條件的 10 條記錄,并記下下一次查詢(xún)應(yīng)該開(kāi)始的位置,以便于下一次查詢(xún)能直接從該位置開(kāi)始。


          這樣就不必每次查詢(xún)都先從整個(gè)表中先找到滿(mǎn)足條件的前 M 條記錄,舍棄掉,再?gòu)?M+1 開(kāi)始再找到 10 條滿(mǎn)足條件的記錄了。


          處理分頁(yè)慢查詢(xún)的方式一般有以下幾種:

          思路一:構(gòu)造覆蓋索引


          通過(guò)修改 SQL,使用上覆蓋索引,比如我需要只查詢(xún)表中的 app_name、createTime 等少量字段,那么我秩序在 app_name、createTime 字段設(shè)置聯(lián)合索引,即可實(shí)現(xiàn)覆蓋索引,無(wú)需全表掃描。


          適用于查詢(xún)列較少的場(chǎng)景,查詢(xún)列數(shù)過(guò)多的不推薦,耗時(shí):0.390s。

          mysql>?EXPLAIN?SELECT?app_name,createTime?from?vio_basic_domain_info?LIMIT?1000000,10;
          +----+-------------+-----------------------+------------+-------+---------------+--------------+---------+------+---------+----------+-------------+
          |?id?|?select_type?|?table?????????????????|?partitions?|?type??|?possible_keys?|?key??????????|?key_len?|?ref??|?rows????|?filtered?|?Extra???????|
          +----+-------------+-----------------------+------------+-------+---------------+--------------+---------+------+---------+----------+-------------+
          |
          ??1?|?SIMPLE??????|?vio_basic_domain_info?|?NULL???????|?index?|?NULL??????????|?idx_app_name?|?515?????|?NULL?|?1377809?|???100.00?|?Using?index?|
          +----+-------------+-----------------------+------------+-------+---------------+--------------+---------+------+---------+----------+-------------+
          1?row?in?set,?1?warning?(0.00?sec)


          思路二:優(yōu)化 offset


          無(wú)法用上覆蓋索引,那么重點(diǎn)是想辦法快速過(guò)濾掉前 100w 條數(shù)據(jù)。我們可以利用自增主鍵有序的條件,先查詢(xún)出第 1000001 條數(shù)據(jù)的 id 值,再往后查 10 行。


          適用于主鍵 id 自增的場(chǎng)景,耗時(shí):0.471s。
          SELECT?*?from?vio_basic_domain_info?where?
          ??id?>=(SELECT?id?from?vio_basic_domain_info?ORDER?BY?id?limit?1000000,1)?limit?10;


          原理:先基于索引查詢(xún)出第 1000001 條數(shù)據(jù)對(duì)應(yīng)的主鍵 id 的值,然后直接通過(guò)該 id 的值直接查詢(xún)?cè)?id 后面的 10 條數(shù)據(jù)。


          下方 EXPLAIN 分析結(jié)果中大家可以看到這條 SQL 的兩步執(zhí)行流程:
          mysql>?EXPLAIN?SELECT?*?from?vio_basic_domain_info?where?id?>=(SELECT?id?from?vio_basic_domain_info?ORDER?BY?id?limit?1000000,1)?limit?10;
          +----+-------------+-----------------------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
          |?id?|?select_type?|?table?????????????????|?partitions?|?type??|?possible_keys?|?key?????|?key_len?|?ref??|?rows????|?filtered?|?Extra???????|
          +----+-------------+-----------------------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
          |
          ??1?|?PRIMARY?????|?vio_basic_domain_info?|?NULL???????|?range?|?PRIMARY???????|?PRIMARY?|?8???????|?NULL?|??????10?|???100.00?|?Using?where?|
          |??2?|?SUBQUERY????|?vio_basic_domain_info?|?NULL???????|?index?|?NULL??????????|?PRIMARY?|?8???????|?NULL?|?1000001?|???100.00?|?Using?index?|
          +----+-------------+-----------------------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
          2?rows?in?set,?1?warning?(0.40?sec)


          方法三:“延遲關(guān)聯(lián)”


          耗時(shí):0.439s,延遲關(guān)聯(lián)適用于數(shù)量級(jí)較大的表。


          SQL 如下:
          SELECT?*?from?vio_basic_domain_info?inner?join?(select?id?from?vio_basic_domain_info?order?by?id?limit?1000000,10)?as?myNew?using(id);

          這里我們利用到了覆蓋索引+延遲關(guān)聯(lián)查詢(xún),相當(dāng)于先只查詢(xún) id 列,利用覆蓋索引快速查到該頁(yè)的 10 條數(shù)據(jù) id,然后再把返回的 10 條 id 拿到表中通過(guò)主鍵索引二次查詢(xún)。(表數(shù)據(jù)增速快的情況對(duì)該方法影響較小)

          mysql>?EXPLAIN?SELECT?*?from?vio_basic_domain_info?inner?join?(select?id?from?vio_basic_domain_info?order?by?id?limit?1000000,10)?as?myNew?using(id);
          +----+-------------+-----------------------+------------+--------+---------------+---------+---------+----------+---------+----------+-------------+
          |?id?|?select_type?|?table?????????????????|?partitions?|?type???|?possible_keys?|?key?????|?key_len?|?ref??????|?rows????|?filtered?|?Extra???????|
          +----+-------------+-----------------------+------------+--------+---------------+---------+---------+----------+---------+----------+-------------+
          |
          ??1?|?PRIMARY?????|?????????????|?NULL???????|?ALL????|?NULL??????????|?NULL????|?NULL????|?NULL?????|?1000010?|???100.00?|?NULL????????|
          |??1?|?PRIMARY?????|?vio_basic_domain_info?|?NULL???????|?eq_ref?|?PRIMARY???????|?PRIMARY?|?8???????|?myNew.id?|???????1?|???100.00?|?NULL????????|
          |
          ??2?|?DERIVED?????|?vio_basic_domain_info?|?NULL???????|?index??|?NULL??????????|?PRIMARY?|?8???????|?NULL?????|?1000010?|???100.00?|?Using?index?|
          +----+-------------+-----------------------+------------+--------+---------------+---------+---------+----------+---------+----------+-------------+
          3?rows?in?set,?1?warning?(0.00?sec)

          排查索引沒(méi)起作用的情況


          ①模糊查詢(xún)盡量避免用通配符'%'開(kāi)頭,會(huì)導(dǎo)致數(shù)據(jù)庫(kù)引擎放棄索引進(jìn)行全表掃描

          如下:

          SELECT?*?FROM?t?WHERE?username?LIKE?'%陳%'


          優(yōu)化方式:盡量在字段后面使用模糊查詢(xún)。如下:

          SELECT?*?FROM?t?WHERE?username?LIKE?'陳%'


          如果需求是要在前面使用模糊查詢(xún):
          • 使用 MySQL 內(nèi)置函數(shù) INSTR(str,substr)來(lái)匹配,作用類(lèi)似于 Java 中的 indexOf(),查詢(xún)字符串出現(xiàn)的角標(biāo)位置。

          • 使用 FullText 全文索引,用 match against 檢索。

          • 數(shù)據(jù)量較大的情況,建議引用 ElasticSearch、Solr,億級(jí)數(shù)據(jù)量檢索速度秒級(jí)。

          • 當(dāng)表數(shù)據(jù)量較少(幾千條兒那種),別整花里胡哨的,直接用 like '%xx%'。


          ②盡量避免使用 not in,會(huì)導(dǎo)致引擎走全表掃描。建議用 not exists 代替


          如下:
          --?不走索引
          SELECT?*?FROM?t?WHERE?name?not?IN?('提莫','隊(duì)長(zhǎng)');
          --?走索引
          select?*?from?t?as?t1?where?not?exists?(select?*?from?t?as?t2?where?name?IN?('提莫','隊(duì)長(zhǎng)')?and?t1.id?=?t2.id);

          ③盡量避免使用 or,會(huì)導(dǎo)致數(shù)據(jù)庫(kù)引擎放棄索引進(jìn)行全表掃描


          如下:
          SELECT?*?FROM?t?WHERE?id?=?1?OR?id?=?3


          優(yōu)化方式:可以用 union 代替 or。如下:
          SELECT?*?FROM?t?WHERE?id?=?1
          ???UNION
          SELECT?*?FROM?t?WHERE?id?=?3


          ④盡量避免進(jìn)行 null 值的判斷,會(huì)導(dǎo)致數(shù)據(jù)庫(kù)引擎放棄索引進(jìn)行全表掃描


          如下:
          SELECT?*?FROM?t?WHERE?score?IS?NULL

          優(yōu)化方式:可以給字段添加默認(rèn)值 0,對(duì) 0 值進(jìn)行判斷。如下:
          SELECT?*?FROM?t?WHERE?score?=?0

          ⑤盡量避免在 where 條件中等號(hào)的左側(cè)進(jìn)行表達(dá)式、函數(shù)操作,會(huì)導(dǎo)致數(shù)據(jù)庫(kù)引擎放棄索引進(jìn)行全表掃描


          可以將表達(dá)式、函數(shù)操作移動(dòng)到等號(hào)右側(cè)。如下:

          --?全表掃描
          SELECT?*?FROM?T?WHERE?score/10?=?9
          --?走索引
          SELECT?*?FROM?T?WHERE?score?=?10*9

          ⑥當(dāng)數(shù)據(jù)量大時(shí),避免使用 where 1=1 的條件。通常為了方便拼裝查詢(xún)條件,我們會(huì)默認(rèn)使用該條件,數(shù)據(jù)庫(kù)引擎會(huì)放棄索引進(jìn)行全表掃描


          如下:
          SELECT?username,?age,?sex?FROM?T?WHERE?1=1

          優(yōu)化方式:用代碼拼裝 SQL 時(shí)進(jìn)行判斷,沒(méi) where 條件就去掉 where,有 where 條件就加 and。


          ⑦查詢(xún)條件不能用 <> 或者 !=


          使用索引列作為條件進(jìn)行查詢(xún)時(shí),需要避免使用<>或者!=等判斷條件。


          如確實(shí)業(yè)務(wù)需要,使用到不等于符號(hào),需要在重新評(píng)估索引建立,避免在此字段上建立索引,改由查詢(xún)條件中其他索引字段代替。


          ⑧where 條件僅包含復(fù)合索引非前導(dǎo)列


          如:復(fù)合(聯(lián)合)索引包含 key_part1,key_part2,key_part3 三列,但 SQL 語(yǔ)句沒(méi)有包含索引前置列"key_part1",按照 MySQL 聯(lián)合索引的最左匹配原則,不會(huì)走聯(lián)合索引。

          --?不走索引
          select?col1?from?table?where?key_part2=1?and?key_part3=2
          --?走索引
          select?col1?from?table?where?key_part1?=1?and?key_part2=1?and?key_part3=2

          ⑨隱式類(lèi)型轉(zhuǎn)換造成不使用索引


          如下 SQL 語(yǔ)句由于索引對(duì)列類(lèi)型為 varchar,但給定的值為數(shù)值,涉及隱式類(lèi)型轉(zhuǎn)換,造成不能正確走索引。
          select?col1?from?table?where?col_varchar=123; 

          結(jié)語(yǔ)


          好了,通過(guò)這篇文章,希望你 Get 到了一些分析 MySQL 慢查詢(xún)的方法和心得,如果你覺(jué)得這篇文章不錯(cuò),記得分享給朋友或同事,讓大家少踩點(diǎn)坑。


          作者:陳哈哈

          簡(jiǎn)介:MySQL 社區(qū)的非著名貢獻(xiàn)者,善于白嫖知識(shí);陪伴 MySQL 五年,致力于高性能 SQL、事務(wù)鎖優(yōu)化方面的研究;長(zhǎng)路漫漫,希望通過(guò)自己的分享讓大家少踩一些坑。我是陳哈哈,一個(gè)愛(ài)笑的程序員。

          編輯:陶家龍


          后臺(tái)回復(fù)?學(xué)習(xí)資料?領(lǐng)取學(xué)習(xí)視頻


          如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝

          瀏覽 31
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  台湾成人在线视频 | 日逼大片| 爆操欧美| 青娱乐极品视频vip | 日逼免费观看视频 |