前言本文主要受眾為開發(fā)人員,所以不涉及到MySQL的服務部署等操作,且內(nèi)容較多,大家準備好耐心和瓜子礦泉水. 前一陣系統(tǒng)的學習了一下MySQL,也有一些實際操作經(jīng)驗,偶然看到一篇和MySQL相關的面試文章,發(fā)現(xiàn)其中的一些問題自己也回答不好,雖然知識點大部分都知道,但是無法將知識串聯(lián)起來. 因此決定搞一個MySQL靈魂100問,試著用回答問題的方式,讓自己對知識點的理解更加深入一點. 此文不會事無巨細的從select的用法開始講解mysql,主要針對的是開發(fā)人員需要知道的一些MySQL的知識點,主要包括索引,事務,優(yōu)化等方面,以在面試中高頻的問句形式給出答案. 1. 什么是索引? 索引是一種數(shù)據(jù)結(jié)構,可以幫助我們快速的進行數(shù)據(jù)的查找. 2. 索引是個什么樣的數(shù)據(jù)結(jié)構呢? 索引的數(shù)據(jù)結(jié)構和具體存儲引擎的實現(xiàn)有關, 在MySQL中使用較多的索引有Hash索引,B+樹索引等,而我們經(jīng)常使用的InnoDB存儲引擎的默認索引實現(xiàn)為:B+樹索引. 3. Hash索引和B+樹所有有什么區(qū)別或者說優(yōu)劣呢? 首先要知道Hash索引和B+樹索引的底層實現(xiàn)原理: hash索引底層就是hash表,進行查找時,調(diào)用一次hash函數(shù)就可以獲取到相應的鍵值,之后進行回表查詢獲得實際數(shù)據(jù).B+樹底層實現(xiàn)是多路平衡查找樹.對于每一次的查詢都是從根節(jié)點出發(fā),查找到葉子節(jié)點方可以獲得所查鍵值,然后根據(jù)查詢判斷是否需要回表查詢數(shù)據(jù). 掃碼關注“不才黃某” 回復“mysql” 獲取mysql優(yōu)化高級視頻 那么可以看出他們有以下的不同:
因為在hash索引中經(jīng)過hash函數(shù)建立索引之后,索引的順序與原順序無法保持一致,不能支持范圍查詢.而B+樹的的所有節(jié)點皆遵循(左節(jié)點小于父節(jié)點,右節(jié)點大于父節(jié)點,多叉樹也類似),天然支持范圍.
因此,在大多數(shù)情況下,直接選擇B+樹索引可以獲得穩(wěn)定且較好的查詢速度.而不需要使用hash索引. 4. 上面提到了B+樹在滿足聚簇索引和覆蓋索引的時候不需要回表查詢數(shù)據(jù),什么是聚簇索引? 在B+樹的索引中,葉子節(jié)點可能存儲了當前的key值,也可能存儲了當前的key值以及整行的數(shù)據(jù),這就是聚簇索引和非聚簇索引. 在InnoDB中,只有主鍵索引是聚簇索引,如果沒有主鍵,則挑選一個唯一鍵建立聚簇索引.如果沒有唯一鍵,則隱式的生成一個鍵來建立聚簇索引. 當查詢使用聚簇索引時,在對應的葉子節(jié)點,可以獲取到整行數(shù)據(jù),因此不用再次進行回表查詢. 5. 非聚簇索引一定會回表查詢嗎? 不一定,這涉及到查詢語句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再進行回表查詢. 舉個簡單的例子,假設我們在員工表的年齡上建立了索引,那么當進行 6. 在建立索引的時候,都有哪些需要考慮的因素呢? 建立索引的時候一般要考慮到字段的使用頻率,經(jīng)常作為條件進行查詢的字段比較適合.如果需要建立聯(lián)合索引的話,還需要考慮聯(lián)合索引中的順序.此外也要考慮其他方面,比如防止過多的所有對表造成太大的壓力.這些都和實際的表結(jié)構以及查詢方式有關. 7. 聯(lián)合索引是什么?為什么需要注意聯(lián)合索引中的順序? MySQL可以使用多個字段同時建立一個索引,叫做聯(lián)合索引.在聯(lián)合索引中,如果想要命中索引,需要按照建立索引時的字段順序挨個使用,否則無法命中索引. 具體原因為: MySQL使用索引時需要索引有序,假設現(xiàn)在建立了'name,age,school'的聯(lián)合索引,那么索引的排序為: 先按照name排序,如果name相同,則按照age排序,如果age的值也相等,則按照school進行排序. 當進行查詢時,此時索引僅僅按照name嚴格有序,因此必須首先使用name字段進行等值查詢,之后對于匹配到的列而言,其按照age字段嚴格有序,此時可以使用age字段用做索引查找,,,以此類推.因此在建立聯(lián)合索引的時候應該注意索引列的順序,一般情況下,將查詢需求頻繁或者字段選擇性高的列放在前面.此外可以根據(jù)特例的查詢或者表結(jié)構進行單獨的調(diào)整. 8. 創(chuàng)建的索引有沒有被使用到?或者說怎么才可以知道這條語句運行很慢的原因? MySQL提供了explain命令來查看語句的執(zhí)行計劃,MySQL在執(zhí)行某個語句之前,會將該語句過一遍查詢優(yōu)化器,之后會拿到對語句的分析,也就是執(zhí)行計劃,其中包含了許多信息.可以通過其中和索引有關的信息來分析是否命中了索引,例如possilbe_key,key,key_len等字段,分別說明了此語句可能會使用的索引,實際使用的索引以及使用的索引長度. 9. 那么在哪些情況下會發(fā)生針對該列創(chuàng)建了索引但是在查詢的時候并沒有使用呢?
以上情況,MySQL無法使用索引. 事務相關1. 什么是事務? 理解什么是事務最經(jīng)典的就是轉(zhuǎn)賬的栗子,相信大家也都了解,這里就不再說一邊了. 事務是一系列的操作,他們要符合ACID特性.最常見的理解就是:事務中的操作要么全部成功,要么全部失敗.但是只是這樣還不夠的. 2. ACID是什么?可以詳細說一下嗎? A=Atomicity 原子性,就是上面說的,要么全部成功,要么全部失敗.不可能只執(zhí)行一部分操作. C=Consistency 系統(tǒng)(數(shù)據(jù)庫)總是從一個一致性的狀態(tài)轉(zhuǎn)移到另一個一致性的狀態(tài),不會存在中間狀態(tài). I=Isolation 隔離性: 通常來說:一個事務在完全提交之前,對其他事務是不可見的.注意前面的通常來說加了紅色,意味著有例外情況. D=Durability 持久性,一旦事務提交,那么就永遠是這樣子了,哪怕系統(tǒng)崩潰也不會影響到這個事務的結(jié)果. 3. 同時有多個事務在進行會怎么樣呢? 多事務的并發(fā)進行一般會造成以下幾個問題:
4. 怎么解決這些問題呢?MySQL的事務隔離級別了解嗎? MySQL的四種隔離級別如下:
這就是上面所說的例外情況了,這個隔離級別下,其他事務可以看到本事務沒有提交的部分修改.因此會造成臟讀的問題(讀取到了其他事務未提交的部分,而之后該事務進行了回滾). 這個級別的性能沒有足夠大的優(yōu)勢,但是又有很多的問題,因此很少使用.
其他事務只能讀取到本事務已經(jīng)提交的部分.這個隔離級別有 不可重復讀的問題,在同一個事務內(nèi)的兩次讀取,拿到的結(jié)果竟然不一樣,因為另外一個事務對數(shù)據(jù)進行了修改.
可重復讀隔離級別解決了上面不可重復讀的問題(看名字也知道),但是仍然有一個新問題,就是 幻讀,當你讀取id> 10 的數(shù)據(jù)行時,對涉及到的所有行加上了讀鎖,此時例外一個事務新插入了一條id=11的數(shù)據(jù),因為是新插入的,所以不會觸發(fā)上面的鎖的排斥,那么進行本事務進行下一次的查詢時會發(fā)現(xiàn)有一條id=11的數(shù)據(jù),而上次的查詢操作并沒有獲取到,再進行插入就會有主鍵沖突的問題.
這是最高的隔離級別,可以解決上面提到的所有問題,因為他強制將所以的操作串行執(zhí)行,這會導致并發(fā)性能極速下降,因此也不是很常用. 5. Innodb使用的是哪種隔離級別呢? InnoDB默認使用的是可重復讀隔離級別. 6. 對MySQL的鎖了解嗎? 當數(shù)據(jù)庫有并發(fā)事務的時候,可能會產(chǎn)生數(shù)據(jù)的不一致,這時候需要一些機制來保證訪問的次序,鎖機制就是這樣的一個機制. 就像酒店的房間,如果大家隨意進出,就會出現(xiàn)多人搶奪同一個房間的情況,而在房間上裝上鎖,申請到鑰匙的人才可以入住并且將房間鎖起來,其他人只有等他使用完畢才可以再次使用. 7. MySQL都有哪些鎖呢?像上面那樣子進行鎖定豈不是有點阻礙并發(fā)效率了? 從鎖的類別上來講,有共享鎖和排他鎖. 共享鎖: 又叫做讀鎖. 當用戶要進行數(shù)據(jù)的讀取時,對數(shù)據(jù)加上共享鎖.共享鎖可以同時加上多個. 排他鎖: 又叫做寫鎖. 當用戶要進行數(shù)據(jù)的寫入時,對數(shù)據(jù)加上排他鎖.排他鎖只可以加一個,他和其他的排他鎖,共享鎖都相斥. 用上面的例子來說就是用戶的行為有兩種,一種是來看房,多個用戶一起看房是可以接受的. 一種是真正的入住一晚,在這期間,無論是想入住的還是想看房的都不可以. 鎖的粒度取決于具體的存儲引擎,InnoDB實現(xiàn)了行級鎖,頁級鎖,表級鎖. 他們的加鎖開銷從大大小,并發(fā)能力也是從大到小. 表結(jié)構設計1. 為什么要盡量設定一個主鍵? 主鍵是數(shù)據(jù)庫確保數(shù)據(jù)行在整張表唯一性的保障,即使業(yè)務上本張表沒有主鍵,也建議添加一個自增長的ID列作為主鍵.設定了主鍵之后,在后續(xù)的刪改查的時候可能更加快速以及確保操作數(shù)據(jù)范圍安全. 2. 主鍵使用自增ID還是UUID? 推薦使用自增ID,不要使用UUID. 因為在InnoDB存儲引擎中,主鍵索引是作為聚簇索引存在的,也就是說,主鍵索引的B+樹葉子節(jié)點上存儲了主鍵索引以及全部的數(shù)據(jù)(按照順序),如果主鍵索引是自增ID,那么只需要不斷向后排列即可,如果是UUID,由于到來的ID與原來的大小不確定,會造成非常多的數(shù)據(jù)插入,數(shù)據(jù)移動,然后導致產(chǎn)生很多的內(nèi)存碎片,進而造成插入性能的下降. 總之,在數(shù)據(jù)量大一些的情況下,用自增主鍵性能會好一些. 圖片來源于《高性能MySQL》: 其中默認后綴為使用自增ID, 關于主鍵是聚簇索引,如果沒有主鍵,InnoDB會選擇一個唯一鍵來作為聚簇索引,如果沒有唯一鍵,會生成一個隱式的主鍵.
3. 字段為什么要求定義為not null? MySQL官網(wǎng)這樣介紹:
null值會占用更多的字節(jié),且會在程序中造成很多與預期不符的情況. 4. 如果要存儲用戶的密碼散列,應該使用什么字段進行存儲? 密碼散列,鹽,用戶身份證號等固定長度的字符串應該使用char而不是varchar來存儲,這樣可以節(jié)省空間且提高檢索效率. 存儲引擎相關1. MySQL支持哪些存儲引擎? MySQL支持多種存儲引擎,比如InnoDB,MyISAM,Memory,Archive等等.在大多數(shù)的情況下,直接選擇使用InnoDB引擎都是最合適的,InnoDB也是MySQL的默認存儲引擎.
零散問題1. MySQL中的varchar和char有什么區(qū)別. char是一個定長字段,假如申請了 在檢索效率上來講,char > varchar,因此在使用中,如果確定某個字段的值的長度,可以使用char,否則應該盡量使用varchar.例如存儲用戶MD5加密后的密碼,則應該使用char. 2. varchar(10)和int(10)代表什么含義? varchar的10代表了申請的空間長度,也是可以存儲的數(shù)據(jù)的最大長度,而int的10只是代表了展示的長度,不足10位以0填充.也就是說,int(1)和int(10)所能存儲的數(shù)字大小以及占用的空間都是相同的,只是在展示時按照長度展示. 3. MySQL的binlog有有幾種錄入格式?分別有什么區(qū)別? 有三種格式,statement,row和mixed.
此外,新版的MySQL中對row級別也做了一些優(yōu)化,當表結(jié)構發(fā)生變化的時候,會記錄語句而不是逐行記錄. 4. 超大分頁怎么處理? 超大的分頁一般從兩個方向上來解決.
解決超大分頁,其實主要是靠緩存,可預測性的提前查到內(nèi)容,緩存至redis等k-V數(shù)據(jù)庫中,直接返回即可. 在阿里巴巴《Java開發(fā)手冊》中,對超大分頁的解決辦法是類似于上面提到的第一種. 5. 關心過業(yè)務系統(tǒng)里面的sql耗時嗎?統(tǒng)計過慢查詢嗎?對慢查詢都怎么優(yōu)化過? 在業(yè)務系統(tǒng)中,除了使用主鍵進行的查詢,其他的我都會在測試庫上測試其耗時,慢查詢的統(tǒng)計主要由運維在做,會定期將業(yè)務中的慢查詢反饋給我們. 慢查詢的優(yōu)化首先要搞明白慢的原因是什么? 是查詢條件沒有命中索引?是load了不需要的數(shù)據(jù)列?還是數(shù)據(jù)量太大? 所以優(yōu)化也是針對這三個方向來的,
6. 上面提到橫向分表和縱向分表,可以分別舉一個適合他們的例子嗎? 橫向分表是按行分表.假設我們有一張用戶表,主鍵是自增ID且同時是用戶的ID.數(shù)據(jù)量較大,有1億多條,那么此時放在一張表里的查詢效果就不太理想.我們可以根據(jù)主鍵ID進行分表,無論是按尾號分,或者按ID的區(qū)間分都是可以的. 假設按照尾號0-99分為100個表,那么每張表中的數(shù)據(jù)就僅有100w.這時的查詢效率無疑是可以滿足要求的. 縱向分表是按列分表.假設我們現(xiàn)在有一張文章表.包含字段 當然,分表其實和業(yè)務的關聯(lián)度很高,在分表之前一定要做好調(diào)研以及benchmark.不要按照自己的猜想盲目操作. 7. 什么是存儲過程?有哪些優(yōu)缺點? 存儲過程是一些預編譯的SQL語句。1、更加直白的理解:存儲過程可以說是一個記錄集,它是由一些T-SQL語句組成的代碼塊,這些T-SQL語句代碼像一個方法一樣實現(xiàn)一些功能(對單表或多表的增刪改查),然后再給這個代碼塊取一個名字,在用到這個功能的時候調(diào)用他就行了。2、存儲過程是一個預編譯的代碼塊,執(zhí)行效率比較高,一個存儲過程替代大量T_SQL語句 ,可以降低網(wǎng)絡通信量,提高通信速率,可以一定程度上確保數(shù)據(jù)安全 但是,在互聯(lián)網(wǎng)項目中,其實是不太推薦存儲過程的,比較出名的就是阿里的《Java開發(fā)手冊》中禁止使用存儲過程,我個人的理解是,在互聯(lián)網(wǎng)項目中,迭代太快,項目的生命周期也比較短,人員流動相比于傳統(tǒng)的項目也更加頻繁,在這樣的情況下,存儲過程的管理確實是沒有那么方便,同時,復用性也沒有寫在服務層那么好. 8. 說一說三個范式 第一范式: 每個列都不可以再拆分.第二范式: 非主鍵列完全依賴于主鍵,而不能是依賴于主鍵的一部分.第三范式: 非主鍵列只依賴于主鍵,不依賴于其他非主鍵. 在設計數(shù)據(jù)庫結(jié)構的時候,要盡量遵守三范式,如果不遵守,必須有足夠的理由.比如性能. 事實上我們經(jīng)常會為了性能而妥協(xié)數(shù)據(jù)庫的設計. 9. MyBatis中的#和$有什么區(qū)別? 亂入了一個奇怪的問題.....我只是想單獨記錄一下這個問題,因為出現(xiàn)頻率太高了. # 會將傳入的內(nèi)容當做字符串,而$會直接將傳入值拼接在sql語句中. 所以#可以在一定程度上預防sql注入攻擊.
|
|