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