1. 課程簡介
本課程的核心內(nèi)容可以分為三個部分,分別是需要理解記憶的計算機底層基礎(chǔ),后端通用組件以及需要不斷編碼練習(xí)的數(shù)據(jù)結(jié)構(gòu)和算法。
計算機底層基礎(chǔ)可以包含計算機網(wǎng)絡(luò)、操作系統(tǒng)、編譯原理、計算機組成原理,后兩者在面試中出現(xiàn)的頻率很少,課程主要關(guān)注網(wǎng)絡(luò)和系統(tǒng)兩個模塊,計算機網(wǎng)絡(luò)模塊主要介紹了常見的 TCP 協(xié)議、HTTP/HTTPS 協(xié)議,操作系統(tǒng)模塊主要介紹了操作系統(tǒng)的進(jìn)程和線程、內(nèi)存管理的頁面置換算法等高頻題。
后端通用組件主要分為存儲持久化數(shù)據(jù)的數(shù)據(jù)庫,存儲臨時數(shù)據(jù)的緩存以及通信中間件。常用的支持持久化存儲的數(shù)據(jù)庫有 Oracle Database、SQLite 以及 MySQL,其中 MysQL 已經(jīng)是后端必備的數(shù)據(jù)庫技能。常用的緩存方案有 memcached、guava cache、Redis,其中 Redis 是目前大型應(yīng)用系統(tǒng)首選的緩存組件,課程介紹了 Redis 的數(shù)據(jù)結(jié)構(gòu)和應(yīng)用問題。通信中間件則介紹了 RabbitMQ 的常見應(yīng)用問題。
數(shù)據(jù)結(jié)構(gòu)和算法部分最能提現(xiàn)候選人的編程基本功以及邏輯思考能力,課程主要介紹了最常見的算法案例,例如快排、鏈表、二叉樹、動態(tài)規(guī)劃。
課程總體上是面向工作 3 年內(nèi)的初中級程序員以及準(zhǔn)備面試后端崗位的應(yīng)屆生,因為篇幅原因并不能涵蓋所有的面試題,候選人需要做到舉一反三,例如能夠通過對于二叉樹的各種遍歷操作總結(jié)得到解決二叉樹問題的遞歸算法模板,這也是本課程的最終目的。
2. 為什么要做這門面試教程?提升面試能力與了解市場現(xiàn)狀
就業(yè)市場和所有的市場相同,影響就業(yè)指標(biāo)的關(guān)鍵就是供需關(guān)系。在互聯(lián)網(wǎng)行業(yè),供需主要分為兩類,需求方是作為招聘方的互聯(lián)網(wǎng)公司,供給方則是作為候選人的程序員。如果互聯(lián)網(wǎng)公司對于程序員的需求大于市場上流動的人員數(shù)量,此時求職的程序員就會有更大的議價能力,這種情況一般發(fā)生在每年的金三銀四時間段。如果程序員數(shù)量過剩,互聯(lián)網(wǎng)公司則會普遍提高選拔指標(biāo),嘗試篩選出更符合條件的候選人。
從供給側(cè)來看,根據(jù) 2021 年高考填報志愿的統(tǒng)計數(shù)據(jù),計算機和金融已經(jīng)明顯成為兩大金磚專業(yè),越來越多的高水平學(xué)生流入到計算機專業(yè),以及觀察考研數(shù)據(jù),可以發(fā)現(xiàn)計算機已經(jīng)是最受歡迎的目標(biāo)專業(yè)。
從需求側(cè)來看,國內(nèi)互聯(lián)網(wǎng)經(jīng)歷了 2010 年之后的移動互聯(lián)網(wǎng)流量大爆炸時期,增速已經(jīng)逐漸放緩,各種大廠的競爭也從尋找增量的用戶轉(zhuǎn)為到存量市場博弈,例如在電商領(lǐng)域繼淘寶和京東之后出現(xiàn)的拼多多、唯品會等各種細(xì)分應(yīng)用,或者從企業(yè)微信、阿里釘釘以及字節(jié)跳動飛書的辦公軟件領(lǐng)域的競爭都可以看出存量市場競爭的激烈。蛋糕已經(jīng)很難做大,所以大家都開始花精力研究如何分到更多的蛋糕。
所以未來的趨勢很明顯,互聯(lián)網(wǎng)的供給增速飛快,但是需求增速放緩,作為找工作的一方,不管是應(yīng)屆在校生還是工作時間不長的程序員,都需要提高自己的核心專業(yè)素養(yǎng)。
3. 校園招聘簡介
在開始介紹校招題庫之前,我們首先需要了解關(guān)于校招的基礎(chǔ)常識。之前遇到過一些同學(xué),在畢業(yè)前兩個月甚至領(lǐng)到畢業(yè)證之后,才開始了解關(guān)于校園招聘的事情,這時候才發(fā)現(xiàn)自己已經(jīng)錯過了大部分的企業(yè)招聘機會,只能撿漏或者直接參加難度較高的社招。這種情況非??上В悄昴甓紩l(fā)生,可見確實有一些同學(xué)對校園招聘了解甚少。所以本小節(jié),我們首先給大家介紹下互聯(lián)網(wǎng)技術(shù)校園招聘的情況。
一般來說,互聯(lián)網(wǎng)校招分為秋招和春招,為了能夠更清楚的說明時間節(jié)點,我們以下圖為例講個故事。
校招時間線
2019年9月,小明本科入學(xué),開始了為期四年的大學(xué)生涯,2020年9月,小紅碩士入學(xué),迎接了自己3年時長的學(xué)碩生活。時間飛逝,到了2022年7月,各大互聯(lián)網(wǎng)陸續(xù)開展了秋季校園招聘(簡稱秋招)提前批,提前批是各大廠的搶人大戰(zhàn),目標(biāo)是提前錨定學(xué)校中最優(yōu)秀的那批學(xué)生。到了當(dāng)年9月,所有的互聯(lián)網(wǎng)公司都開放了秋招正式批招聘流程,目標(biāo)人群就是小明和小紅這兩類2023年畢業(yè)的同學(xué),到了11月,大小廠都陸陸續(xù)續(xù)的發(fā)放了所有的offer。
小明由于準(zhǔn)備不充分,整個秋招"顆粒無收",沒有找到合適的工作,但是在畢業(yè)前他還有一個機會,就是春季校園招聘(簡稱春招),因為在秋招中,一些比較優(yōu)秀的同學(xué)會同時拿到多個互聯(lián)網(wǎng)公司的offer,但是在秋招后期會毀約大部分公司,這些公司就會空余出一些HC(HeadCount,互聯(lián)網(wǎng)公司每年招聘時預(yù)計的招聘人數(shù)),所以在2023年的春季,一般是3月到4月這段時間,會有少部分公司重新放開招聘,這時候就是小明的第二次機會。
好了,故事講完了,給大家總結(jié)一下,秋招一般是在每年的的7月開始,11月進(jìn)入尾聲,參與秋招的公司數(shù)量很多,崗位以及HC也比較富余,找到工作的機會很多。春招一般是針對前年秋招的補充招聘,招聘公司以及HC都較少,應(yīng)屆生找到好工作的難度較大。綜上,作為應(yīng)屆生,應(yīng)該盡可能抓住秋招的機會。
4. 為什么需要掌握題庫
秋招開始時,本科生還處于大三階段,一般還需要完成一些專業(yè)課,研究生則是在研二階段,可能還在幫導(dǎo)師做項目。兩者的時間都談不上充裕,所以如何利用有限的時間收獲最好的 Offer 就需要技巧。
同時,校招時 BAT(百度、阿里、騰訊)等大廠都會對參與校招的候選人進(jìn)行評級,一般分為三檔,也就是口口相傳的白菜 Offer(評級普通),sp(Special Offer,評級優(yōu)秀)、ssp(Super Special Offer,評級優(yōu)秀),每檔之間在年薪上可能會有3~10w的差距,而決定評級的,除了候選人的學(xué)歷等硬件條件,最重要的就是在面試過程中的表現(xiàn),也就是對于面試題目的剖析能力。
拋開簡歷書寫、簡歷投遞、Offer 談薪這些模塊,在本節(jié)內(nèi)容中,我們的重點在于講解后端校招中最經(jīng)典、最高頻的面試題,深入分析題目中最需要關(guān)注的知識點。在熟練掌握這些題解之后,對于面試中遇到的同類題目,我們能夠給面試官良好的技術(shù)印象,以此助力同學(xué)們在校招中拿到心怡的 Offer。
1. 前言
在校招或者社招面試中,無論你是 Java 后端、Cpp 后端、Python 后端,面試官都會詳細(xì)地考察各種語法細(xì)節(jié)、框架知識,但是大多數(shù)候選人入職之后,都會體會到 "面試造火箭,上班擰螺絲"。面試時我們熟悉各種知識細(xì)節(jié),入職后卻發(fā)現(xiàn)大部分工作都是重復(fù)的 CRUD(Create - 增加,Retrieve - 查詢,Update - 更新,Delete - 刪除),這種現(xiàn)象其實很正常。后端開發(fā)的核心職責(zé)就是倒騰數(shù)據(jù),面臨的基礎(chǔ)問題例如如何設(shè)計數(shù)據(jù)的存儲結(jié)構(gòu)并且選擇合適的數(shù)據(jù)庫進(jìn)行存儲,如何在各種限制條件下查詢數(shù)據(jù)。當(dāng)我們負(fù)責(zé)的系統(tǒng)非常龐大之后,還會面臨如何解決分布式系統(tǒng)的數(shù)據(jù)一致性這類更復(fù)雜的問題。
總結(jié)來說,操作數(shù)據(jù)庫是后端程序員的必備技能。數(shù)據(jù)庫按照存儲數(shù)據(jù)結(jié)構(gòu)的方式,可以分為關(guān)系型數(shù)據(jù)庫以及非關(guān)系型數(shù)據(jù)庫。關(guān)系型數(shù)據(jù)庫中數(shù)據(jù)都是以二維表的形式存儲,最常用的數(shù)據(jù)庫有 Oracle、MySQL,非關(guān)系型數(shù)據(jù)庫中數(shù)據(jù)都是以結(jié)構(gòu)化的形式存儲,最常用的數(shù)據(jù)庫有 Redis、MongoDB、Hbase 等。目前互聯(lián)網(wǎng)一二線大廠的校招要求,MySQL 基本是必備技能,非關(guān)系性數(shù)據(jù)庫例如 Redis 這類不是必考內(nèi)容,當(dāng)然掌握了會是錦上添花。
本小節(jié)中會介紹關(guān)于 MySQL 常見高頻面試題,以及核心回答思路解析。
2. MyISAM 和 InnoDB
面試官提問: 介紹下 InnoDB 和 MyISAM 存儲引擎的區(qū)別,以及具體的應(yīng)用場景?
題目解析:
首先要明確存儲引擎的定義:MySQL 提供不同的技術(shù)存儲數(shù)據(jù),這些技術(shù)使用不同的數(shù)據(jù)存儲機制、索引建立方式、鎖方式來完成數(shù)據(jù)的構(gòu)建,這些技術(shù)統(tǒng)稱為存儲引擎。
MySQL Client 終端輸入 show engines 可查看支持的存儲引擎類型。
MySQL 5.7 實驗結(jié)果
如圖可見 MySQL 至少支持 9 種存儲引擎,而面試中最受關(guān)注的是 InnoDB 和 MyISAM 存儲引擎,一般的面試場景是這樣的:
在上圖的實驗結(jié)果中,我們會發(fā)現(xiàn)在 MySQL 的所有存儲引擎中,只有 InnoDB 支持 Transaction(事務(wù))、XA(分布式事務(wù)),其他引擎均不支持,關(guān)于事務(wù)的支持能力是需要強調(diào)的點。關(guān)于兩種引擎之間的區(qū)別,可以按下表進(jìn)行分點闡述。
InnoDBMyISAM
事務(wù)支持,強調(diào)的是保持?jǐn)?shù)據(jù)一致性的高級功能不支持,強調(diào)的是性能,查詢速度比 InnoDB 快
外鍵支持不支持
索引使用聚集索引,索引文件和數(shù)據(jù)文件綁定使用非聚集索引,索引文件和數(shù)據(jù)文件分開存儲,索引中保存的是數(shù)據(jù)文件的指針
鎖支持表級鎖、行級鎖;行級鎖粒度小,處理并發(fā)的能力更強支持表級鎖,用戶在執(zhí)行 insert、update、select、delete 時都會給表自動加鎖,效率低
主鍵表必須有唯一索引(例如用戶規(guī)定 id 作為主鍵),沒有的話會用默認(rèn)隱藏列 row_id 作為唯一索引沒有要求
存儲文件在操作系統(tǒng)中的存儲文件:.frm:表定義文件 .ibd:數(shù)據(jù)文件在操作系統(tǒng)中的存儲文件:.frm:表定義文件 .myd:數(shù)據(jù)文件 .myi:索引文件
在闡述完兩種存儲引擎的區(qū)別之后,再根據(jù)兩者的特點,枚舉一些使用場景:
MyISAM 對于不支持事務(wù)并且存在大量 SELECT 的讀場景比較合適;
如果業(yè)務(wù)代碼中要支持事務(wù),必須選擇 InnoDB 存儲引擎;
如果業(yè)務(wù)代碼中要支持外鍵,必須選擇 InnoDB 存儲引擎;
例如對電商公司來說,大部分的業(yè)務(wù)情況是要支持事務(wù)回滾的,例如下單流程失敗之后要回滾已有的 Insert 語句,并且數(shù)據(jù)并發(fā)量高,這種場景肯定都是選擇 InnoDB,筆者在生產(chǎn)環(huán)境中從未看到使用 MyISAM 存儲引擎,兩者對比更側(cè)重考察候選人的理論知識。
3. 小結(jié)
本章節(jié)介紹了 MySQL 中兩種不同的數(shù)據(jù)存儲引擎,面試官針對回答可能會涉及到一些擴展性的問題,例如當(dāng)談到 InooDB 對事務(wù)的支持時,面試官很可能轉(zhuǎn)為考察候選人對事務(wù)的理解程度。上述提到了聚集索引和非聚集索引,面試官可能會特意考察這兩種索引的實現(xiàn)區(qū)別。MySQL 的外鍵定義,枚舉使用場景和案例。這些問題也是候選人需要掌握的知識點。
1. 前言
在 MySQL 中使用 select 查詢語句的時候,一般都會加上 where 語句或者 limit 語句限定查詢結(jié)果的范圍,兩種子句都是過濾的作用。另外還有和 group by 語句配合使用的 having 限制條件。區(qū)分 where 和 having 語句的作用也是比較基礎(chǔ)的題目。
2. where 和 having
面試官提問: 請闡述下在 MySQL 中 where 和 having 關(guān)鍵詞有什么區(qū)別?
題目解析: 為了更加清楚地闡述兩個關(guān)鍵用法的不同,我們還是從實際例子出發(fā)。
進(jìn)入上一節(jié)創(chuàng)建的測試數(shù)據(jù)庫:
USE mooc_demo;
然后進(jìn)入測試數(shù)據(jù)庫,創(chuàng)建 user 表:
USE `mooc_demo`;DROP TABLE IF EXISTS `user`;CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '數(shù)據(jù)庫主鍵', `username` varchar(32) DEFAULT NULL COMMENT '用戶名', `password` varchar(32) DEFAULT NULL COMMENT '密碼', `gender` int(2) DEFAULT NULL COMMENT '1:男性,2:女性', `age` int(10) DEFAULT NULL COMMENT '年齡', PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
然后再插入一些測試數(shù)據(jù):
insert into user (username,password,gender,age) values ('小明','123456',1,20);insert into user (username,password,gender,age) values ('小紅','123457',2,22);insert into user (username,password,gender,age) values ('小王','123458',1,24);insert into user (username,password,gender,age) values ('小劉','123459',2,26);
2.1 where 和 having 都能使用的場景
select username,password,gender,age from user where age >= 20;select username,password,gender,age from user having age >= 20;
上述兩條 sql 都能執(zhí)行成功,如果 select 之后的字段包含 having 修飾的字段,這種情況下 where 和 having 是等效的。
2.2 只能用 where,不能用 having 的場景
select username,password,gender from user where age >= 20;select username,password,gender from user having age >= 20;
第一條 sql 執(zhí)行成功,執(zhí)行第二條 sql 會報錯:ERROR 1054 (42S22): Unknown column 'age' in 'having clause',翻譯過來就是 "對于 having 語句,age 字段是未知的"。
having 執(zhí)行的前置條件是:select xxx,... 篩選出的字段包含 having 修飾的關(guān)鍵詞。
兩者的執(zhí)行順序不同:因為 having 是從前面篩選出來的字段中再進(jìn)行二次篩選,where 則是針對全表先進(jìn)行篩選。
2.3 只能用 having,不能用 where 的場景
select count(*),gender,avg(age) as avg_age from user group by gender where avg_age > 20;select count(*),gender,avg(age) as avg_age from user group by gender having avg_age > 20;
我們的目的是 "按照性別進(jìn)行分組,統(tǒng)計平均年齡大于 20 的人數(shù)、性別和平均年齡"。
但是第一條 sql 執(zhí)行會報錯:ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where avg_age > 20' at line 1,即對于 where 語句,全表并沒有 avg_age 平均年齡這個字段,所以會搜索失敗。
從上述的實驗結(jié)果總結(jié)來看,where 和 having 的核心區(qū)別有三點:
① 使用范圍的不同:
where:能夠用于 select、update、delete 語句;
having:只能用于 select 查詢語句。
② 執(zhí)行順序不同:
where 修飾的搜索條件是在分組完成之前執(zhí)行;
having 修飾的搜索條件是在分組完成之后執(zhí)行。如果一條 sql 語句同時包含 where 和 having 關(guān)鍵詞,會先執(zhí)行 where 搜索,再執(zhí)行 having 搜索條件。
③ 聚合函數(shù)的聯(lián)合使用:
where:不能聯(lián)合聚合函數(shù)(sum、avg、count、max、min 這類)使用;
having:能夠聯(lián)合聚合函數(shù)使用。
3. 小結(jié)
本章節(jié)介紹了 where 和 having 語句的用法和不同點,候選人在闡述的時候需要抓住最核心的一點,where 語句在聚合之前篩選數(shù)據(jù),having 語句在聚合之后對數(shù)據(jù)進(jìn)行篩選,作用在 group by 語句之后。
1. 前言
MySQL 中支持的數(shù)據(jù)類型從整體上可以分為數(shù)值類型和日期時間類型,其中數(shù)值類型可以分為整數(shù)類型、浮點數(shù)類型、定點數(shù)類型和位類型。整數(shù)類型包含常見的 SMALLINT、MEDIUMINT、INT、BIGINT,浮點數(shù)類型主要是 FLOAT 單精度浮點數(shù)類型和 DOUBLE 雙精度浮點數(shù)類型。日期類型也有 DATE、TIME、YEAR、DATETIME、TIMESTAMP 類型。關(guān)于整數(shù)類型和浮點數(shù)類型存在一些比較常見的誤區(qū),經(jīng)常被面試官考察。
2. int (3) 和 int (11)
面試官提問: MySQL 中 int (3) 和 int (11) 這兩種用法有什么區(qū)別呢?
題目解析:
這道題非常常見,但是沒有仔細(xì)了解過 MySQL 中 int 數(shù)據(jù)類型用法的同學(xué),很容易掉進(jìn)誤區(qū)。
我們知道 varchar(m) 用于修飾變長字符,其中 m 表示能夠存儲的字符上限。
例如 username varchar(2) 在 MySQL 5.0 之后的版本表示最多接受 2 個漢字的字符作為用戶名存儲,如果長度超限會報錯:ERROR 1406 (22001): Data too long for column 'username' at row 1 。所以候選人可能會想當(dāng)然的認(rèn)為 int(m) 中的 m 表示存儲數(shù)字的長度,int(3) 和 int(11) 分別表示最多存儲 3 位數(shù)和 11 位數(shù),這種觀點是完全錯誤的!
2.1 int (3) 和 int (11) 占用的硬件存儲空間完全相同
首先,我們在申明某個字段數(shù)據(jù)類型為 int 的時候,不管是 int(3) 還是 int(11),在 MySQL 中存儲時都占用 4 個字節(jié)的長度。
1 個字節(jié)(Byte) = 8 個二進(jìn)制位(bit),所以 1 個 int = 4 Byte = 4 * 8 bit = 32 bit,計算機中使用首個比特位存儲數(shù)字符號(參考補碼的定義),所以可以算出 int(m) 的存儲范圍在 [-2147483648,2147483647] 之間。
2.2 int (3) 和 int (11) 在 zerofill 關(guān)鍵詞修飾時展示不同
我們在之前創(chuàng)建的 mooc_demo 數(shù)據(jù)庫中創(chuàng)建一張測試表:
DROP TABLE IF EXISTS `test_int`;CREATE TABLE `test_int` ( `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '數(shù)據(jù)庫主鍵', `num1` int(3) zerofill, `num2` int(11) zerofill) ENGINE=InnoDB DEFAULT CHARSET=utf8;代碼塊1234567
再插入一條測試數(shù)據(jù):
insert into test_int (num1, num2) values (1,1);代碼塊1
現(xiàn)在執(zhí)行 select * from test_int; 查詢語句,查詢結(jié)果如圖:
查詢結(jié)果如上圖所示,存儲相同的數(shù)字 1,num1 前補全了 2 個 0,num2 前補全了 10 個 0,
所以可以得出結(jié)論:int(m) 中的 m 表示在 zerofill 修飾時,數(shù)字長度不足 m 時前綴補充的 0 的個數(shù),除此之外,兩者使用時沒有任何區(qū)別。
3. double(m,n)
面試官: MySQL 中 double (m,n) 中的 m 和 n 有什么含義?
題目解析: 這道題容易和上題一起出現(xiàn),混淆視聽,但是難度相對就簡單多了。
double(m,n)、float(m,n) 以及 decimal(m,n) 中的 m 和 n 定義均相同,而且比較清晰:
m:數(shù)據(jù)精度,即數(shù)據(jù)的總長度;
n:小數(shù)點精度,即浮點數(shù)小數(shù)點后的長度。
舉例說明:float(6,2) 表示最多能存儲 6 位長度的浮點數(shù),并且小數(shù)點精度為 2。
實戰(zhàn)驗證下上述結(jié)論, 還是在之前創(chuàng)建的 mooc_demo 數(shù)據(jù)庫中創(chuàng)建一張測試表:
DROP TABLE IF EXISTS `test_float`;CREATE TABLE `test_float` ( `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '數(shù)據(jù)庫主鍵', `num1` float(6,2) zerofill, `num2` double(6,2) zerofill) ENGINE=InnoDB DEFAULT CHARSET=utf8;代碼塊1234567
繼續(xù)插入測試數(shù)據(jù):
insert into test_float (num1, num2) values (1234.5678,1234.5678);代碼塊1
執(zhí)行 select * from test_float; 查詢語句,查詢結(jié)果如圖:
查詢結(jié)果
如上圖所示,小數(shù)點 2 位之后的數(shù)據(jù)被截斷,符合 SQL 定義時的預(yù)期。
4. 小結(jié)
MySQL 基礎(chǔ)數(shù)據(jù)類型的知識學(xué)習(xí)可以從兩個方面入手,一點是基本語法,學(xué)習(xí)基礎(chǔ)語法的目的是能夠上手使用這些數(shù)據(jù)類型,另一點是如何選擇在合適的場景使用合適的數(shù)據(jù)類型,需要明確這種數(shù)據(jù)類型會占用多少的字節(jié)空間,數(shù)據(jù)類型的最小值和最大值是什么,選擇不同數(shù)據(jù)類型可能會存在什么樣的潛在問題。
1. 前言
在面向?qū)ο笳Z言中涉及到諸多的設(shè)計模式,例如單例模式、適配器模式,設(shè)計模式的存在是為了讓系統(tǒng)中的代碼邏輯更加清晰,幫助開發(fā)者建立更加健壯的系統(tǒng),同時滿足易修改特性和易擴展特性。數(shù)據(jù)庫設(shè)計時也存在類似設(shè)計模式的通用規(guī)范,被稱為數(shù)據(jù)庫范式。滿足范式的數(shù)據(jù)庫是簡潔的,表與表之間的關(guān)系也清晰且明確,不會存儲過多的冗余信息,在增刪改查的時候也可以避免冗余的操作。
2. 數(shù)據(jù)庫設(shè)計三大范式
面試官提問: 請描述下數(shù)據(jù)庫設(shè)計的三大范式?
題目解析: 回答本題時,可以從總分的結(jié)構(gòu)來闡述,即先闡述數(shù)據(jù)庫范式的定義,再挨個解釋每種范式的設(shè)計原則。
數(shù)據(jù)庫范式定義:為了建立邏輯結(jié)構(gòu)合理、冗余較小的數(shù)據(jù)庫,在設(shè)計數(shù)據(jù)表時必須要遵循的設(shè)計規(guī)范。
接下來可以分點闡述第一、第二、第三范式的定義和案例。
2.1 數(shù)據(jù)庫第一范式(1NF)
數(shù)據(jù)庫第一范式是設(shè)計數(shù)據(jù)庫時需要滿足的最基本范式:
① 定義:第一范式(First Normal Form)要求數(shù)據(jù)庫表中的所有字段都是不可拆分的原子字段,換句話說,每個字段不可以再進(jìn)行拆分。
② 案例解釋:對于一張最簡單的用戶信息表,定義了用戶編號、姓名、年齡、電話這三個字段,user_info 表如下:
用戶編號 (user_id)姓名 (username)年齡 (age)電話 (phone)
1小明2010086
2小紅2110087
3小王2210088
其中電話 (phone) 這個字段可能存儲的是座機電話號碼、也可能是手機電話號碼,定義上并不明確,這就違背了第一范式的原子性。所以為了滿足第一范式,我們可以將電話字段拆分為座機電話 (fixed_phone) 和手機電話 (cell_phone) 兩個字段,拆分后的 user_info 表如下:
用戶編號 (user_id)姓名 (username)年齡 (age)座機電話 (fixed_phone)手機電話 (cell_phone)
1小明201008618010002000
2小紅211008718010002001
3小王221008818010002002
③ 范式優(yōu)點:拆分之后,字段定義定義清晰。在查詢數(shù)據(jù)庫時我們可以明確過濾的是座機號碼還是手機號碼,方便業(yè)務(wù)層邏輯開發(fā),而且后續(xù)維護(hù)也方便。
2.2 數(shù)據(jù)庫第二范式(2NF)
在滿足第一范式的基礎(chǔ)上,數(shù)據(jù)庫第二范式對字段定義進(jìn)行了更嚴(yán)格的約束:
① 定義:第二范式(Second Normal Form)要求數(shù)據(jù)庫中的每一列都和主鍵相關(guān),不能和主鍵的一部分相關(guān)。
② 案例解釋:在電商環(huán)境下,我們需要設(shè)計一個訂單表,因為訂單和商品綁定, 所以將商品編號和訂單編號作為訂單表的聯(lián)合主鍵,初始設(shè)計的訂單(order)表如下:
訂單編號 (order_id)商品編號 (good_id)購買數(shù)量 (order_num)單位 (unit)商品單價 (good_price)購買時間 (purchase_time)
1000188881千克1002020-10-11
1000288881千克1002020-10-12
1000388903克3002020-10-13
仔細(xì)觀察,我們就能發(fā)現(xiàn)這種設(shè)計的問題在于:good_id = 8888 的商品,對于 order_id = 10001 和 10002 記錄都存儲了相同的單位和商品價格,這種冗余存儲在數(shù)據(jù)量大的場景下是不能接收的,并且違反了第二范式設(shè)計原則,商品價格只和商品編號有關(guān),和訂單編號無關(guān),我們將這張表進(jìn)行拆分:
拆分的原則是:將屬于商品的信息單獨提煉為一張商品表,在原有的訂單表只保留商品編號作為聯(lián)合查詢時的查詢依據(jù),優(yōu)化后的訂單(order)表如下:
訂單編號 (order_id)商品編號 (good_id)購買數(shù)量 (order_num)購買時間 (purchase_time)
10001888812020-10-11
10002888812020-10-12
10003888932020-10-13
單獨拆分出的商品(good)表如下:
商品編號 (good_id)單位 (unit)商品單價 (good_price)
8888千克100
8889克300
③ 范式優(yōu)點:拆分之后,降低了數(shù)據(jù)庫的冗余存儲,并且邏輯清晰,要查詢商品信息即走 good 表,要查詢訂單信息即走 order 表。
2.3 數(shù)據(jù)庫第三范式(3NF)
① 定義:第三范式(Third Normal Form)要求數(shù)據(jù)庫表中的每個字段和主鍵都直接相關(guān),不能間接相關(guān)。
② 案例解釋:還是以第一范式中的 user_info 表作為案例,如果要存儲每個用戶的省份和省會城市,我們可能會設(shè)計出下面這樣一張表:
用戶編號 (user_id)姓名 (username)年齡 (age)座機電話 (fixed_phone)手機電話 (cell_phone)省份 (province)省會城市 (city)
1小明201008618010002000北京市北京市
2小紅211008718010002001黑龍江省哈爾濱市
3小王221008818010002002貴州省貴陽市
我們將用戶編號 (user_id) 作為主鍵,則姓名、年齡、座機電話、手機電話都和 "用戶" 這個主體強相關(guān),和主鍵直接相關(guān),而省份和省會城市則和 "用戶" 這個主體是弱相關(guān),和主鍵間接相關(guān),并且存在依賴關(guān)系:用戶編號 -> 姓名,姓名 -> 省份,省份 -> 省會城市,這樣構(gòu)建了用戶編號 -> 省會城市的間接傳遞關(guān)系,這種關(guān)系會導(dǎo)致數(shù)據(jù)冗余,而且在執(zhí)行刪除 / 修改 / 增加操作的時候,會產(chǎn)生異常情況:刪除所有 "貴州省" 下的用戶信息(即 user_id = 3 的記錄),"貴州省" 和 "貴陽市" 的信息也被刪除了(顯然不合理,因為省份這個定義和省份下的人員記錄并沒有關(guān)系)。
所以我們需要將 user_info 表拆分,我們通過省份構(gòu)建數(shù)據(jù)關(guān)系,優(yōu)化后的用戶(user_info)表如下:
用戶編號 (user_id)姓名 (username)年齡 (age)座機電話 (fixed_phone)手機電話 (cell_phone)省份 (province)
1小明201008618010002000北京市
2小紅211008718010002001黑龍江省
3小王221008818010002002貴州省
獨立拆分出的省份(province)表如下:
省份 (province)省會城市 (city)
北京市北京市
黑龍江省哈爾濱市
貴州省貴陽市
③ 范式優(yōu)點:提高了表的獨立性,降低數(shù)據(jù)存儲冗余。
3. 小結(jié)
作為開發(fā),在日常設(shè)計數(shù)據(jù)庫表的時候可能不會特意注意使用數(shù)據(jù)庫范式,但是細(xì)心關(guān)注大部分企業(yè)項目的表結(jié)構(gòu),就會發(fā)現(xiàn)大部分表都是遵循數(shù)據(jù)庫范式設(shè)計的,第二范式和第三范式可能會混淆概念,第二范式的核心是關(guān)注非主鍵列是否依賴主鍵或者主鍵的一部分,地三藩市的核心是關(guān)注非主鍵列是否依賴主鍵,還是依賴其他的非主鍵列。
1. 前言
在之前的章節(jié)談到了數(shù)據(jù)庫設(shè)計范式,遵循范式之后,數(shù)據(jù)會被組織成不同的結(jié)構(gòu)分散存儲在不同的表內(nèi),例如所有學(xué)生會被存儲在一張學(xué)生表,所有學(xué)生的成績會被存儲在一張成績表,如果我們同時需要兩張表的數(shù)據(jù),就需要計算兩張表間數(shù)據(jù)的映射關(guān)系,MySQL 數(shù)據(jù)庫中最常用的方法就是連接。
2. 左連接、右連接、全連接
面試官: 請闡述下 MySQL 中左連接、右連接、全連接的定義和區(qū)別?
題目解析:
① 定義:MySQL 的連接表示多表(一般就是兩張表)之間聯(lián)合查詢的操作。
② 分類:根據(jù)操作性質(zhì)的不同,分為內(nèi)連接和外連接,外連接又可以細(xì)分為左外連接和右外連接。除此之外,還有一種全連接操作,不過 MySQL 數(shù)據(jù)庫并不支持。
定義解釋比較抽象,下面我們通過實戰(zhàn)來講解這幾種連接的區(qū)別,首先進(jìn)入 MySQL 終端,首先創(chuàng)建一個測試數(shù)據(jù)庫:
CREATE DATABASE mooc_demo;
創(chuàng)建一張測試表 test_a:
DROP TABLE IF EXISTS `test_a`;
CREATE TABLE `test_a` ( `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '數(shù)據(jù)庫主鍵', `name` varchar(32) DEFAULT NULL COMMENT '姓名', `part` varchar(32) DEFAULT NULL COMMENT '部門') ENGINE=InnoDB DEFAULT CHARSET=utf8;
然后插入一些測試數(shù)據(jù):
insert into test_a (`name`, `part`) values ('小明','文藝部');insert into test_a (`name`, `part`) values ('小紅','學(xué)習(xí)部');insert into test_a (`name`, `part`) values ('小王','體育部');
繼續(xù)創(chuàng)建另外一張測試表 test_b:
DROP TABLE IF EXISTS `test_b`;
CREATE TABLE `test_b` ( `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '數(shù)據(jù)庫主鍵', `name` varchar(32) DEFAULT NULL COMMENT '姓名', `group` varchar(32) DEFAULT NULL COMMENT '小組') ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入一些測試數(shù)據(jù):
insert into test_b (`name`, `group`) values ('小明', '1號小組');insert into test_b (`name`, `group`) values ('小紅', '2號小組');insert into test_b (`name`, `group`) values ('小李', '3號小組');
執(zhí)行完成之后,兩張表的數(shù)據(jù)如下:
兩張表數(shù)據(jù)2.1 內(nèi)連接
SQL 語法:inner join table_name on table_name
構(gòu)建一條測試 SQL:select * from test_a a inner join test_b b on a.name = b.name;,執(zhí)行結(jié)果如下圖:
內(nèi)連接結(jié)果
執(zhí)行結(jié)果解釋:組裝兩張表滿足 a.name = b.name 的查詢結(jié)果。
我們以數(shù)據(jù)中的集合類比,表 test_a 和表 test_b 是兩個數(shù)據(jù)集合,內(nèi)連接則表示查詢兩個表都符合條件的數(shù)據(jù),即集合的交集操作。
集合交集2.2 左連接
SQL 語法:...left join table_name on table_name
構(gòu)建一條測試 SQL:select * from test_a a left join test_b b on a.name = b.name; ,執(zhí)行結(jié)果如下圖:
左連接執(zhí)行結(jié)果
執(zhí)行結(jié)果解釋:左連接(left join)是左外連接(left outer join)的簡寫,左連接會將左表(test_a)的所有記錄都展示出來,而右表(test_b)只會展示符合搜索條件(上圖中的 on condition)的搜索記錄,其他記錄以 NULL 作為補全。
即展示兩個集合的交集以及左邊集合的剩余部分?jǐn)?shù)據(jù):
集合左交集2.3 右連接
SQL 語法:right join table_name on table_name
構(gòu)建一條測試 SQL:select * from test_a a right join test_b b on a.name = b.name; ,執(zhí)行結(jié)果如下圖:
右連接執(zhí)行結(jié)果
執(zhí)行結(jié)果解釋:右連接(right join)是右外連接(right outer join)的簡寫,右連接會將右表(test_b)的所有記錄都展示出來,而左表(test_a)只展示符合后置條件(on condition)的記錄展示,其他記錄以 NULL 作為補全。
即展示兩個集合的交集以及右邊集合的剩余部分:
集合右交集
3. 小結(jié)
SQL 查詢可以拆分為兩種情況,一種是單表查詢,即根據(jù) where 條件語句查詢得到中間表,然后執(zhí)行 select 語句選擇需要的列返回給控制臺。另一種是多表查詢,對多張表求笛卡爾積,使用 on 語句作為連接條件得到中間表,之后還是通過 where 語句過濾中間表的記錄,選擇需要的列返回給控制臺,本章節(jié)介紹的就是第二種查詢方式。
1. 前言
對于常見的應(yīng)用系統(tǒng),讀的流量遠(yuǎn)遠(yuǎn)高于寫的流量,比如電商網(wǎng)站,商家在數(shù)據(jù)庫中寫入商品的價格和庫存之后,訪問頁面的顧客會產(chǎn)生大部分的讀流量。所以常見的現(xiàn)象是當(dāng)應(yīng)用系統(tǒng)的流量逐漸增加時,寫操作不會成為數(shù)據(jù)庫的性能瓶頸,但是復(fù)雜查詢語句消耗的查詢時間會越來越長,讀操作更容易觸碰數(shù)據(jù)庫的查詢性能瓶頸。MySQL 自身為了優(yōu)化查詢效率,更快的查詢目標(biāo)集合,定義了索引,也就是常用的 "鍵"(Key),MySQL 中的索引是單獨存儲在磁盤上的數(shù)據(jù)結(jié)構(gòu),使用索引可以快速查詢滿足特定條件的記錄。
2. 談一談 InnoDB 存儲引擎的索引數(shù)據(jù)結(jié)構(gòu)
2.1 不同引擎的數(shù)據(jù)結(jié)構(gòu)
面試官提問: MySQL 中 InnoDB 存儲引擎底層的數(shù)據(jù)結(jié)構(gòu)是什么?
題目解析:
以 MySQL 5.7 為例,首先查詢官方文檔,可以發(fā)現(xiàn)存儲引擎和索引數(shù)據(jù)結(jié)構(gòu)的對應(yīng)關(guān)系,例如 InnoDB 對應(yīng) BTREE 索引,MEMORY 存儲引擎對應(yīng)哈希索引和 BTREE 索引,注意這里的 BTREE 實際指代的是 B+ 樹,我們重點關(guān)注樹的數(shù)據(jù)結(jié)構(gòu)。
存儲引擎和索引類型的對應(yīng)關(guān)系,表格來自 MySQL 5.7 官網(wǎng)
2.2 B 樹和 B + 樹
如果能正確回答 InnoDB 索引的底層數(shù)據(jù)結(jié)構(gòu)是 B+ 樹,面試官接下來可能會先考察候選人對數(shù)據(jù)結(jié)構(gòu)本身的理解程度。
面試官:學(xué)過數(shù)據(jù)結(jié)構(gòu)課程的同學(xué),應(yīng)該都聽過 B 樹和 B+ 樹吧,這兩種樹有什么區(qū)別呢?
題目解析:我們盡量需要通過在白紙上畫出 B 樹和 B+ 樹,畫圖的同時給面試官解釋兩種樹的區(qū)別,需要從數(shù)據(jù)結(jié)構(gòu)、優(yōu)缺點方面分析(一般來說不需要深入到節(jié)點的插入和刪除流程,因為比較復(fù)雜)首先我們畫出一個簡化后的 B 樹,如下圖:
B 樹,圖中綠色節(jié)點表示具體數(shù)據(jù),藍(lán)色節(jié)點表示指針,黃色表示鍵值,整個節(jié)點指代一個磁盤塊
參考上圖,我們定義一個 m 階的 B 樹的數(shù)據(jù)結(jié)構(gòu):
① 根結(jié)點至少有兩個子節(jié)點;② 除了根節(jié)點外,每個子節(jié)點都包含 n-1 個元素(數(shù)據(jù))和 n 個子節(jié)點指針,其中 m/2 <= n <= m;③ 所有的葉子結(jié)點都位于同一層;④ 有序性:每個節(jié)點中的元素從小到大排列,節(jié)點當(dāng)中 k-1 個元素正好是 k 個孩子包含的元素的值域分劃。
畫出 B 樹只是為了襯托 B+ 樹,B 樹不會是面試的重點,接下來我們在白紙上畫出一個典型的 B+ 樹結(jié)構(gòu):
B + 樹,圖中綠色節(jié)點表示具體數(shù)據(jù),藍(lán)色節(jié)點表示指針,黃色表示鍵值,整個節(jié)點指代一個磁盤塊
對于一個 m 階的 B+ 樹,基本定義同 B 樹相同:① 除了根之外的每個節(jié)點都包含最少 m/2 個元素最多 m-1 個元素,對于任意的結(jié)點有最多 m 個子指針;② 所有的葉子節(jié)點都在同一層;
除此之外,B+ 樹相對于 B 樹,需要特別區(qū)分的不同點有:① 數(shù)據(jù)存儲方式不同:B+ 樹中間節(jié)點并不存儲真正的數(shù)據(jù),而是保存其葉子節(jié)點中最小值作為索引。例如上圖中磁盤塊 2 和磁盤塊 3 中并沒有黃色的 data 節(jié)點;② 數(shù)據(jù)查找方式不同:每個葉子節(jié)點存在一個 next 指針,指向下一個葉子節(jié)點,形成了有序雙向鏈表,從圖中能明顯看出來。所以 B 樹只能由根節(jié)點往下二分查找,B+ 樹除了這種查找方式,還支持在葉子節(jié)點中直接順序遍歷查找。
2.3 為什么使用 B + 樹
在給面試官闡述清楚了 B 樹和 B+ 樹數(shù)據(jù)結(jié)構(gòu)的區(qū)別之后,接下來面試官大概率會追根溯源,引出更深一步的問題:
** 面試官:** 你能說說為什么 MySQL 要選擇 B+ 樹作為索引的數(shù)據(jù)結(jié)構(gòu)實現(xiàn)嗎?
題目解析:
結(jié)合我們畫出的數(shù)據(jù)結(jié)構(gòu)圖示,這個問題翻譯過來其實是相對于 B 樹,為什么要選擇 B+ 樹?首先要分析數(shù)據(jù)庫的讀寫瓶頸受限原因:計算機的存儲是分層次的,CPU 里的 Cache 訪問速度最快,速度更慢的是內(nèi)存(容量小,斷電情況下數(shù)據(jù)會丟失),讀寫最慢的是硬盤(容量大,數(shù)據(jù)可長期存儲)。MySQL 的數(shù)據(jù)是持久化存儲在硬盤中的,硬盤訪問慢是因為:CPU 訪問硬盤時會經(jīng)過三個耗時步驟:
(1)尋道耗時:磁臂移動到磁道;(2)旋轉(zhuǎn)耗時:例如一個 7200 轉(zhuǎn)的磁盤,表示每分鐘能轉(zhuǎn) 7200 次;(3)數(shù)據(jù)傳輸耗時:從磁盤讀出數(shù)據(jù)或者寫入數(shù)據(jù)的過程。前兩者都依賴于磁盤的機械運動,耗時非常久。所以磁盤 I/O 是一種非常昂貴的操作,數(shù)據(jù)庫的讀寫操作需要經(jīng)過盡可能少的磁盤 I/O。
我們在這個硬件基礎(chǔ)上,我們依據(jù) B 樹的圖示,模擬查詢關(guān)鍵詞 6 的一次查詢過程:
(1)磁盤第一次 I/O 操作:找到磁盤塊 1,讀入內(nèi)存;(2)磁盤第二次 I/O 操作:比較關(guān)鍵詞 6 在區(qū)間(0,7)之間,找到磁盤塊 1 的左指針,根據(jù)指針找到磁盤塊 2,讀入內(nèi)存;(3) 磁盤第三次 I/O 操作:比較關(guān)鍵詞 6 在區(qū)間(5,7)之間,找到磁盤塊 2 的最右側(cè)指針,根據(jù)指針找到磁盤塊 6,讀入內(nèi)存;(4) 尋找關(guān)鍵詞:在磁盤塊 6 中找到關(guān)鍵詞 6,以及對應(yīng) data 數(shù)據(jù)。
可以發(fā)現(xiàn),樹的深度越深,查找需要的磁盤 I/O 次數(shù)就越多?,F(xiàn)在我們再來分析 B + 樹的優(yōu)勢:
(1) B + 樹的磁盤 I/O 次數(shù)相對更少:利用 B 樹 / B+ 樹的有序性,從根節(jié)點每往子節(jié)點每查找一次,都要經(jīng)過一次磁盤 I/O。相對 B 樹,B+ 樹的內(nèi)部節(jié)點只包含下級指針,并不存放數(shù)據(jù)信息,所以對于同樣大小的磁盤塊,能夠存儲的記錄個數(shù)更多(樹的階 m 更大),B+ 樹的深度更低,所以磁盤 I/O 次數(shù)更少。(2) B+ 樹更適合范圍查找:在進(jìn)行范圍查詢時,B 樹查詢只能通過從根節(jié)點開始的遞歸查詢,因為相鄰節(jié)點在硬盤中不一定連續(xù),緩存命中率差,而 B+ 樹因為葉子節(jié)點形成有序鏈表,可以直接進(jìn)行線性遍歷。
綜上,回答本題的核心點要抓住:(1)明確磁盤 I/O 是數(shù)據(jù)庫讀寫的硬件瓶頸。(2)能夠結(jié)合兩種樹不同的數(shù)據(jù)結(jié)構(gòu),分析得到 B+ 樹的優(yōu)勢。
3. 小結(jié)
本章節(jié)介紹了 MySQL 中 InnoDB 存儲引擎的底層 B+ 樹數(shù)據(jù)結(jié)構(gòu),候選人需要區(qū)分 B 樹和 B+ 樹,以及從查詢效率和硬件成本角度分析為什么在 MySQL 中會優(yōu)選使用 B+ 樹作為支撐。
1. 前言
MySQL 中事務(wù)(Transaction)的定義是對于一個或者多個 SQL 語句,要么全部執(zhí)行成功,要么一個都不執(zhí)行成功。在實際應(yīng)用場景中,有很多需要事務(wù)的場景,例如在電商網(wǎng)站,顧客下單、付款以及商品扣減庫存就應(yīng)該在一個事務(wù)中執(zhí)行,如果不能保證事務(wù)特性,就可能出現(xiàn)用戶已經(jīng)下單并且成功付款,但是在扣減庫存邏輯出現(xiàn)異常,發(fā)貨失敗的情況。所以事務(wù)中的某個環(huán)節(jié)出現(xiàn)異常,之前執(zhí)行的所有 SQL 語句都應(yīng)該回滾。
2. 事務(wù)
2.1 事務(wù) ACID 特性
面試官提問: MySQL 中事務(wù)的特性是什么?
題目解析:
ACID 是衡量事務(wù)的 4 個維度,分別的定義是:
(1)原子性(Atomic,簡寫 A):原子性要求事務(wù)是一個不可分割的執(zhí)行單位,如果一個事務(wù)包含多條 SQL 語句,要么所有的 SQL 都執(zhí)行成功,要么所有的 SQL 都執(zhí)行失敗,不存在兩者之間的中間狀態(tài)。如果事務(wù)中的任意一條 SQL 執(zhí)行失敗,那么已執(zhí)行成功的也需要回滾。MySQL 中 InnoDB 引擎利用 undo log 實現(xiàn)原子性,undo log 記錄了所有已執(zhí)行的 SQL 記錄,如果事務(wù)執(zhí)行失敗調(diào)用了 rollback 語句,那么使用 undo log 的記錄回滾已執(zhí)行的 SQL。(2)持久性(Durability,簡寫 D):持久性要求事務(wù)一旦提交(commit),對數(shù)據(jù)庫的改變就應(yīng)該是永久的,其他的操作不會對已提交的事務(wù)有影響。InnoDB 引擎中使用 redo log 實現(xiàn)持久性,如果 MySQl 服務(wù)器宕機,那么在重啟時可以讀取 redo log 中的記錄恢復(fù)數(shù)據(jù)庫。(3)隔離性(Isolation,簡寫 I):要求一個事務(wù)的執(zhí)行不受到其他并發(fā)執(zhí)行事務(wù)的影響。(4)一致性(Consistency,簡寫 C):事務(wù)將數(shù)據(jù)庫從一種狀態(tài)轉(zhuǎn)換到另一種狀態(tài),但是兩種狀態(tài)從數(shù)據(jù)上是一致的。例如用戶下單扣庫存讓庫存減少了一個單位,那么在訂單中就會增加一個單位的商品,庫存和訂單中的商品數(shù)量和是不會改變的。
2.2 事務(wù)隔離級別
面試官提問:ACID 特性中的隔離性在 MySQL 中的具體定義是什么?
題目解析:
MySQL 提供了 4 種事務(wù)隔離級別,分別是:
(1)讀未提交(Read Uncommitted):所有事務(wù)可以看到其他事務(wù)未提交的執(zhí)行結(jié)果;(2)讀已提交(Read Committed):所有事務(wù)只能看到其他事務(wù)已提交的執(zhí)行結(jié)果;(3)可重復(fù)讀(Repeatable Read):MySQL 默認(rèn)的隔離級別,所有事務(wù)能看到其他事務(wù)已提交后的修改后數(shù)據(jù),但是如果第一次讀取到這個修改后的數(shù)據(jù),如果其他事務(wù)繼續(xù)修改了數(shù)據(jù)并且提交,這個事務(wù)讀到的也是第一次讀到的值,不會讀到修改后的新值。(4)串行化(Serializable):最高隔離級別,可以理解為讓所有并發(fā)執(zhí)行的事務(wù)都進(jìn)入隊列,挨個串行執(zhí)行,永遠(yuǎn)不可能發(fā)生沖突。
我們關(guān)注事務(wù),關(guān)注點在于不同事務(wù)的并發(fā)沖突,而且重點在于讀寫操作。對于同一條數(shù)據(jù),在執(zhí)行并發(fā)事務(wù)時可能會產(chǎn)生讀寫上的問題,有三種:
(1)臟讀(Dirty Read):如果事務(wù) A 更新了一份數(shù)據(jù),比如將記錄 a 更新為記錄 b,那么在事務(wù) B 中讀取到的記錄是 b,此時事務(wù) A 進(jìn)行了回滾操作,記錄 b 回滾為記錄 a,那么事務(wù) B 讀到的記錄 b 則是非法數(shù)據(jù)。(2)不可重復(fù)讀(Non-Repeatable Read):如果事務(wù) A 更新了一份數(shù)據(jù),比如將記錄 a 更新為記錄 b,那么在事務(wù) B 中讀取到的記錄是 b,此時事務(wù) A 繼續(xù)將記錄 b 更新為記錄 c,那么事務(wù) B 第二次讀到的記錄是 c,兩次讀取的結(jié)果不同。(3)幻讀(Phantom Read):如果事務(wù) B 查詢到了幾行數(shù)據(jù),此時事務(wù) A 又插入了幾行新數(shù)據(jù),那么事務(wù) B 會讀到多出來的幾行數(shù)據(jù),讀到了上次讀取沒出現(xiàn)的數(shù)據(jù)。
4 種隔離級別對應(yīng)的問題應(yīng)對能力如下表:
隔離級別臟讀不可重復(fù)讀幻讀
讀未提交???
讀已提交???
可重復(fù)讀???
串行化???
從解決問題的能力上看,串行化能解決所有的并發(fā)讀寫問題,但是串行執(zhí)行效率太低,比如在電商網(wǎng)站的秒殺商品下單流程,就會導(dǎo)致所有的用戶需要等某一個用戶執(zhí)行完下單操作后才能繼續(xù)搶購,不具有實戰(zhàn)意義。MySQL 默認(rèn)的隔離級別是可重復(fù)讀,這個級別能解決臟讀和不可重復(fù)讀的問題,效率上相對比較快。讀未提交的執(zhí)行效率最高,但是數(shù)據(jù)的一致性保障最差, 一般不會在實戰(zhàn)中使用。
在 MySQL 客戶端執(zhí)行 show variables like 'transaction_isolation'; 語句可查看隔離級別:
MySQL 默認(rèn)隔離級別3. 小結(jié)
本小結(jié)概括了事務(wù)的 ACID 特性以及 4 種隔離級別的定義,候選人可以自行使用小樣本數(shù)據(jù)測試 MySQL 不同隔離級別下事務(wù)的讀寫區(qū)別。