當(dāng)前位置:首頁 > 公眾號精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]哪個男孩不想完成一次快速的查詢?1.MySQL查詢慢是什么體驗?謝邀,利益相關(guān)。大多數(shù)互聯(lián)網(wǎng)應(yīng)用場景都是讀多寫少,業(yè)務(wù)邏輯更多分布在寫上。對讀的要求大概就是要快。那么都有什么原因會導(dǎo)致我們完成一次出色的慢查詢呢?1.1索引在數(shù)據(jù)量不是很大時,大多慢查詢可以用索引解決,大多慢查詢也...


哪個男孩不想完成一次快速的查詢?

1. MySQL查詢慢是什么體驗?

謝邀,利益相關(guān)。

大多數(shù)互聯(lián)網(wǎng)應(yīng)用場景都是讀多寫少,業(yè)務(wù)邏輯更多分布在寫上。對讀的要求大概就是要快。那么都有什么原因會導(dǎo)致我們完成一次出色的慢查詢呢?

1.1 索引

在數(shù)據(jù)量不是很大時,大多慢查詢可以用索引解決,大多慢查詢也因為索引不合理而產(chǎn)生。

MySQL 索引基于 B 樹,這句話相信面試都背爛了,接著就可以問最左前綴索引、 B 樹和各種樹了。

說到最左前綴,實際就是組合索引的使用規(guī)則,使用合理組合索引可以有效的提高查詢速度,為什么呢?

因為索引下推。如果查詢條件包含在了組合索引中,比如存在組合索引(a,b),查詢到滿足 a 的記錄后會直接在索引內(nèi)部判斷 b 是否滿足,減少回表次數(shù)。同時,如果查詢的列恰好包含在組合索引中,即為覆蓋索引,無需回表。索引規(guī)則估計都知道,實際開發(fā)中也會創(chuàng)建和使用。問題可能更多的是:為什么建了索引還慢?

1.1.1 什么原因?qū)е滤饕?/span>

建了索引還慢,多半是索引失效(未使用),可用 explain 分析。索引失效常見原因有 :

  1. where 中使用 != 或 <> 或 or 或表達式或函數(shù)(左側(cè))

  2. like 語句 % 開頭

  3. 字符串未加’’

  4. 索引字段區(qū)分度過低,如性別

  5. 未匹配最左前綴

(一張嘴就知道老面試題了) 為什么這些做法會導(dǎo)致失效,成熟的 MySQL 也有自己的想法。

1.1.2 這些原因為什么導(dǎo)致索引失效

如果要 MySQL 給一個理由,還是那棵 B 樹。

函數(shù)操作
當(dāng)在 查詢 where = 左側(cè)使用表達式或函數(shù)時,如字段 A 為字符串型且有索引, 有?where length(a) = 6查詢,這時傳遞一個 6 到 A 的索引樹,不難想象在樹的第一層就迷路了。

隱式轉(zhuǎn)換
隱式類型轉(zhuǎn)換和隱式字符編碼轉(zhuǎn)換也會導(dǎo)致這個問題。

  • 隱式類型轉(zhuǎn)換對于 JOOQ 這種框架來說一般倒不會出現(xiàn)。

  • 隱式字符編碼轉(zhuǎn)換在連表查詢時倒可能出現(xiàn),即連表字段的類型相同但字符編碼不同。

破壞了有序性
至于 Like 語句 % 開頭、字符串未加 ’’ 原因基本一致,MySQL 認為對索引字段的操作可能會破壞索引有序性就機智的優(yōu)化掉了。

不過,對于如性別這種區(qū)分度過低的字段,索引失效就不是因為這個原因。

1.1.3 性別字段為什么不要加索引

為什么索引區(qū)分度低的字段不要加索引。盲猜效率低,效率的確低,有時甚至?xí)扔跊]加。

對于非聚簇索引,是要回表的。假如有 100 條數(shù)據(jù),在 sex 字段建立索引,掃描到 51 個 male,需要再回表掃描 51 行。還不如直接來一次全表掃描呢。

所以,InnoDB 引擎對于這種場景就會放棄使用索引,至于區(qū)分度多低多少會放棄,大致是某類型的數(shù)據(jù)占到總的 30% 左右時,就會放棄使用該字段的索引,有興趣可以試一下。

1.1.4 有什么好用且簡單的索引方法

前面說到大多慢查詢都源于索引,怎么建立并用好索引。這里有一些簡單的規(guī)則。

  • 索引下推:性別字段不適合建索引,但確實存在查詢場景怎么辦?如果是多條件查詢,可以建立聯(lián)合索引利用該特性優(yōu)化。

  • 覆蓋索引:也是聯(lián)合索引,查詢需要的信息在索引里已經(jīng)包含了,就不會再回表了。

  • 前綴索引:對于字符串,可以只在前 N 位添加索引,避免不必要的開支。假如的確需要如關(guān)鍵字查詢,那交給更合適的如 ES 或許更好。

  • 不要對索引字段做函數(shù)操作

  • 對于確定的、寫多讀少的表或者頻繁更新的字段都應(yīng)該考慮索引的維護成本。

1.1.5 如何評價 MySQL 選錯了索引

有時,建立了猛一看挺正確的索引,但事情卻沒按計劃發(fā)展。就像“為啥 XXX 有索引,根據(jù)它查詢還是慢查詢”。

此刻沒準(zhǔn)要自信點:我的代碼不可能有 BUG,肯定是 MySQL 出了問題。MySQL 的確可能有點問題。

這種情況常見于建了一大堆索引,查詢條件一大堆。沒使用你想讓它用的那一個,而是選了個區(qū)分度低的,導(dǎo)致過多的掃描。造成的原因基本有兩個:

  • 信息統(tǒng)計不準(zhǔn)確:可以使用?analyze table x重新分析。

  • 優(yōu)化器誤判:可以?force index強制指定?;蛐薷恼Z句引導(dǎo)優(yōu)化器,增加或刪除索引繞過。

但根據(jù)我淺薄的經(jīng)驗來看,更可能是因為你建了些沒必要的索引導(dǎo)致的。不會真有人以為 MySQL 沒自己機靈吧?

除了上面這些索引原因外,還有下面這些不常見或者說不好判斷的原因存在。

搜索公眾號架構(gòu)師后臺回復(fù)“架構(gòu)整潔”,獲取一份驚喜禮包。

1.2 等MDL鎖

在 MySQL 5.5 版本中引入了 MDL,對一個表做 CRUD 操作時,自動加 MDL 讀鎖;對表結(jié)構(gòu)做變更時,加 MDL 寫鎖。讀寫鎖、寫鎖間互斥。

當(dāng)某語句拿 MDL 寫鎖就會阻塞 MDL 讀鎖,可以使用show processlist命令查看處于Waiting for table metadata lock狀態(tài)的語句。

1.3 等 flush

flush 很快,大多是因為 flush 命令被別的語句堵住,它又堵住了 select 。通過show processlist命令查看時會發(fā)現(xiàn)處于Waiting for table flush狀態(tài)。

1.4 等行鎖

某事物持有寫鎖未提交。

1.5 當(dāng)前讀

InnoDB 默認級別是可重復(fù)讀。設(shè)想一個場景:事物 A 開始事務(wù),事務(wù) B 也開始執(zhí)行大量更新。B 率先提交, A 是當(dāng)前讀,就要依次執(zhí)行 undo log ,直到找到事務(wù) B 開始前的值。

1.6 大表場景

在未二次開發(fā)的 MYSQL 中,上億的表肯定算大表,這種情況即使在索引、查詢層面做到了較好實現(xiàn),面對頻繁聚合操作也可能會出現(xiàn) IO 或 CPU 瓶頸,即使是單純查詢,效率也會下降。

且 Innodb 每個 B 樹節(jié)點存儲容量是 16 KB,理論上可存儲 2kw 行左右,這時樹高為3層。我們知道,innodb_buffer_pool 用來緩存表及索引,如果索引數(shù)據(jù)較大,緩存命中率就堪憂,同時 innodb_buffer_pool 采用 LRU 算法進行頁面淘汰,如果數(shù)據(jù)量過大,對老或非熱點數(shù)據(jù)的查詢可能就會把熱點數(shù)據(jù)給擠出去。

所以對于大表常見優(yōu)化即是分庫分表和讀寫分離了。

1.6.1 分庫分表

方案
是分庫還是分表呢?這要具體分析。

  • 如果磁盤或網(wǎng)絡(luò)有 IO 瓶頸,那就要分庫和垂直分表。

  • 如果是 CPU 瓶頸,即查詢效率偏低,水平分表。

水平即切分?jǐn)?shù)據(jù),分散原有數(shù)據(jù)到更多的庫表中。

垂直即按照業(yè)務(wù)對庫,按字段對表切分。

工具方面有 sharding-sphere、TDDL、Mycat。動起手來需要先評估分庫、表數(shù),制定分片規(guī)則選 key,再開發(fā)和數(shù)據(jù)遷移,還要考慮擴容問題。

問題
實際運行中,寫問題不大,主要問題在于唯一 ID 生成、非 partition key 查詢、擴容。

  • 唯一 ID 方法很多,DB 自增、Snowflake、號段、一大波GUID算法等。

  • 非 partition key 查詢常用映射法解決,映射表用到覆蓋索引的話還是很快的?;蛘呖梢院推渌?DB 組合。

  • 擴容要根據(jù)分片時的策略確定,范圍分片的話就很簡單,而隨機取模分片就要遷移數(shù)據(jù)了。也可以用范圍 取模的模式分片,先取模再范圍,可以避免一定程度的數(shù)據(jù)遷移。

當(dāng)然,如果分庫還會面臨事務(wù)一致性和跨庫 join 等問題。

1.6.2 讀寫分離

為什么要讀寫分離
分表針對大表解決 CPU 瓶頸,分庫解決 IO 瓶頸,二者將存儲壓力解決了。但查詢還不一定。

如果落到 DB 的 QPS 還是很高,且讀遠大于寫,就可以考慮讀寫分離,基于主從模式將讀的壓力分?jǐn)?,避免單機負載過高,同時也保證了高可用,實現(xiàn)了負載均衡。

問題
主要問題有過期讀和分配機制。

  • 過期讀,也就是主從延時問題,這個對于。

  • 分配機制,是走主還是從庫??梢灾苯哟a中根據(jù)語句類型切換或者使用中間件。

1.7 小結(jié)

以上列舉了 MySQL 常見慢查詢原因和處理方法,介紹了應(yīng)對較大數(shù)據(jù)場景的常用方法。

分庫分表和讀寫分離是針對大數(shù)據(jù)或并發(fā)場景的,同時也為了提高系統(tǒng)的穩(wěn)定和拓展性。但也不是所有的問題都最適合這么解決。

2. 如何評價 ElasticSearch

前文有提到對于關(guān)鍵字查詢可以使用 ES。那接著聊聊 ES 。

2.1 可以干什么

ES 是基于 Lucene 的近實時分布式搜索引擎。使用場景有全文搜索、NoSQL Json 文檔數(shù)據(jù)庫、監(jiān)控日志、數(shù)據(jù)采集分析等。

對非數(shù)據(jù)開發(fā)來說,常用的應(yīng)該就是全文檢索和日志了。ES 的使用中,常和 Logstash, Kibana 結(jié)合,也成為 ELK 。先來瞧瞧日志怎么用的。

下面是我司日志系統(tǒng)某檢索操作:打開 Kibana 在 Discover 頁面輸入格式如 “xxx” 查詢。

該操作可以在 Dev Tools 的控制臺替換為:

GET?yourIndex/_search??
{????
?"from"?:?0,?"size"?:?10,??
?"query"?:?{??
????????"match_phrase"?:?{??
????????????"log"?:?"xxx"???????
????????}??
????}??
}??
什么意思?Discover 中加上 “” 和 console 中的 match_phrase 都代表這是一個短語匹配,意味著只保留那些包含全部搜索詞項,且位置與搜索詞項相同的文檔。

2.2 ES 的結(jié)構(gòu)

在 ES 7.0 之前存儲結(jié)構(gòu)是 Index -> Type -> Document,按 MySQL 對比就是 database - table - id(實際這種對比不那么合理)。7.0 之后 Type 被廢棄了,就暫把 index 當(dāng)做 table 吧。

在 Dev Tools 的 Console 可以通過以下命令查看一些基本信息。也可以替換為 crul 命令。

  1. GET /_cat/health?v
本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
關(guān)閉
關(guān)閉