字節跳動面試之---數據庫

文章來源:開源項目Github  https://github.com/wolverinn/Waking-Up  

作者:wolverinn 

如有侵權 請聯繫刪除

 

數據庫


事務的概念和特性?

概念:事務(Transaction)是一個操作序列,不可分割的工作單位,以BEGIN TRANSACTION開始,以ROLLBACK/COMMIT結束

特性(ACID):

  • 原子性(Atomicity):邏輯上是不可分割的操作單元,事務的所有操作要麼全部提交成功,要麼全部失敗回滾(用回滾日誌實現,反向執行日誌中的操作);
  • 一致性(Consistency):事務的執行必須使數據庫保持一致性狀態。在一致性狀態下,所有事務對一個數據的讀取結果都是相同的;
  • 隔離性(Isolation):一個事務所做的修改在最終提交以前,對其它事務是不可見的(併發執行的事務之間不能相互影響);
  • 持久性(Durability):一旦事務提交成功,對數據的修改是永久性的

會出現哪些併發一致性問題?

  • 丟失修改:一個事務對數據進行了修改,在事務提交之前,另一個事務對同一個數據進行了修改,覆蓋了之前的修改;
  • 髒讀(Dirty Read):一個事務讀取了被另一個事務修改、但未提交(進行了回滾)的數據,造成兩個事務得到的數據不一致;
  • 不可重複讀(Nonrepeatable Read):在同一個事務中,某查詢操作在一個時間讀取某一行數據和之後一個時間讀取該行數據,發現數據已經發生修改(可能被更新或刪除了);
  • 幻讀(Phantom Read):當同一查詢多次執行時,由於其它事務在這個數據範圍內執行了插入操作,會導致每次返回不同的結果集(和不可重複讀的區別:針對的是一個數據整體/範圍;並且需要是插入操作)

數據庫的四種隔離級別?

  • 未提交讀(Read Uncommited):在一個事務提交之前,它的執行結果對其它事務也是可見的。會導致髒讀、不可重複讀、幻讀;
  • 提交讀(Read Commited):一個事務只能看見已經提交的事務所作的改變。可避免髒讀問題;
  • 可重複讀(Repeatable Read):可以確保同一個事務在多次讀取同樣的數據時得到相同的結果。(MySQL的默認隔離級別)。可避免不可重複讀;
  • 可串行化(Serializable):強制事務串行執行,使之不可能相互衝突,從而解決幻讀問題。可能導致大量的超時現象和鎖競爭,實際很少使用。

什麼是樂觀鎖和悲觀鎖?

  • 悲觀鎖:認爲數據隨時會被修改,因此每次讀取數據之前都會上鎖,防止其它事務讀取或修改數據;應用於數據更新比較頻繁的場景;
  • 樂觀鎖:操作數據時不會上鎖,但是更新時會判斷在此期間有沒有別的事務更新這個數據,若被更新過,則失敗重試;適用於讀多寫少的場景。樂觀鎖的實現方式有:
    • 加一個版本號或者時間戳字段,每次數據更新時同時更新這個字段;
    • 先讀取想要更新的字段或者所有字段,更新的時候比較一下,只有字段沒有變化才進行更新

常見的封鎖類型?

  • 排它鎖(Exclusive Lock)/ X鎖:事務對數據加上X鎖時,只允許此事務讀取和修改此數據,並且其它事務不能對該數據加任何鎖;
  • 共享鎖(Shared Lock)/ S鎖:加了S鎖後,該事務只能對數據進行讀取而不能修改,並且其它事務只能加S鎖,不能加X鎖
  • 意向鎖(Intention Locks):
    • 一個事務在獲得某個數據行對象的 S 鎖之前,必須先獲得整個表的 IS 鎖或更強的鎖;
    • 一個事務在獲得某個數據行對象的 X 鎖之前,必須先獲得整個表的 IX 鎖;
    • IS/IX 鎖之間都是兼容的;
    • 好處:如果一個事務想要對整個表加X鎖,就需要先檢測是否有其它事務對該表或者該表中的某一行加了鎖,這種檢測非常耗時。有了意向鎖之後,只需要檢測整個表是否存在IX/IS/X/S鎖就行了

鎖的作用:用於管理對共享資源的併發訪問,保證數據庫的完整性和一致性

封鎖粒度的概念

MySQL 中提供了兩種封鎖粒度:行級鎖以及表級鎖

封鎖粒度小:

  • 好處:鎖定的數據量越少,發生鎖爭用的可能就越小,系統的併發程度就越高;
  • 壞處:系統開銷大(加鎖、釋放鎖、檢查鎖的狀態都需要消耗資源)

MySQL加鎖

SELECT ... LOCK In SHARE MODE;
SELECT ... FOR UPDATE;

什麼是三級封鎖協議?

  • 一級封鎖協議:事務在修改數據之前必須先對其加X鎖,直到事務結束才釋放。可以解決丟失修改問題(兩個事務不能同時對一個數據加X鎖,避免了修改被覆蓋);
  • 二級封鎖協議:在一級的基礎上,事務在讀取數據之前必須先加S鎖,讀完後釋放。可以解決髒讀問題(如果已經有事務在修改數據,就意味着已經加了X鎖,此時想要讀取數據的事務並不能加S鎖,也就無法進行讀取,避免了讀取髒數據);
  • 三級封鎖協議:在二級的基礎上,事務在讀取數據之前必須先加S鎖,直到事務結束才能釋放。可以解決不可重複讀問題(避免了在事務結束前其它事務對數據加X鎖進行修改,保證了事務期間數據不會被其它事務更新)

什麼是兩段鎖協議?

事務必須嚴格分爲兩個階段對數據進行加鎖和解鎖的操作,第一階段加鎖,第二階段解鎖。也就是說一個事務中一旦釋放了鎖,就不能再申請新鎖了。

可串行化調度是指,通過併發控制,使得併發執行的事務結果與某個串行執行的事務結果相同。事務遵循兩段鎖協議是保證可串行化調度的充分條件。

什麼是 MVCC?

多版本併發控制(Multi-Version Concurrency Control, MVCC),MVCC在每行記錄後面都保存有兩個隱藏的列,用來存儲創建版本號刪除版本號

  • 創建版本號:創建一個數據行時的事務版本號(事務版本號:事務開始時的系統版本號;系統版本號:每開始一個新的事務,系統版本號就會自動遞增);
  • 刪除版本號:刪除操作時的事務版本號;
  • 各種操作:
    • 插入操作時,記錄創建版本號;
    • 刪除操作時,記錄刪除版本號;
    • 更新操作時,先記錄刪除版本號,再新增一行記錄創建版本號;
    • 查詢操作時,要符合以下條件才能被查詢出來:刪除版本號未定義或大於當前事務版本號(刪除操作是在當前事務啓動之後做的);創建版本號小於或等於當前事務版本號(創建操作是事務完成或者在事務啓動之前完成)

通過版本號減少了鎖的爭用,提高了系統性能;可以實現提交讀可重複讀兩種隔離級別,未提交讀無需使用MVCC

快照讀與當前讀

使用 MVCC 讀取的是快照中的數據,這樣可以減少加鎖所帶來的開銷:

select * from table ...;

當前讀讀取的是最新的數據,需要加鎖。以下第一個語句需要加 S 鎖,其它都需要加 X 鎖:

select * from table where ? lock in share mode;
select * from table where ? for update;
insert;
update;
delete;

數據庫的範式?

  • 第一範式(1NF,Normal Form):屬性不應該是可分的。舉例:如果將“電話”作爲一個屬性(一列),是不符合1NF的,因爲電話這個屬性可以分解爲家庭電話和移動電話...如果將“移動電話”作爲一個屬性,就符合1NF;
  • 第二範式 2NF:每個非主屬性完全依賴於主屬性集(候選鍵集);
    • B完全依賴於A,就是說A中的所有屬性唯一決定B,屬性少了就不能唯一決定,屬性多了則有冗餘(叫依賴不叫完全依賴)。舉例:(學號,課程名)這個主屬性集可以唯一決定成績,但是對於學生姓名這個屬性,(學號,課程名)這個屬性集就是冗餘的,所以學生姓名不完全依賴於(學號,課程名)這一屬性集;
    • 主屬性集/候選碼集:某一組屬性能夠唯一確定其它的屬性(主鍵就是從候選鍵集中選的一個鍵),而其子集不能,這樣的屬性組中的屬性就是主屬性;不在候選碼集中的屬性成爲非主屬性;
    • 可以通過分解來滿足 2NF:將(學號,課程名,成績)做成一張表;(學號,學生姓名)做成另一張表,避免大量的數據冗餘;
  • 第三範式 3NF:在 2NF 的基礎上,非主屬性不傳遞依賴於主屬性
    • 傳遞依賴:如果C依賴於B,B依賴於A,那麼C傳遞依賴於A;
    • 3NF在2NF的基礎上,消除了非主屬性之間的依賴;比如一個表中,主屬性有(學號),非主屬性有(姓名,院系,院長名),可以看到院長名這個非主屬性依賴於院系,傳遞依賴於學號。消除的辦法是分解。

不符合範式會出現哪些異常?

  • 冗餘數據:某些同樣的數據多次出現(如學生姓名);
  • 修改異常:修改了一個記錄中的信息,另一個記錄中相同的信息卻沒有修改;
  • 刪除異常:刪除一個信息,那麼也會丟失其它信息(刪除一個課程,丟失了一個學生的信息);
  • 插入異常:無法插入(插入一個還沒有課程信息的學生)

列舉幾種表連接方式?

20191207081711185_20242.pnguploading.4e448015.gif轉存失敗重新上傳取消20191207081711185_20242.pnguploading.4e448015.gif轉存失敗重新上傳取消20191207081711185_20242.pnguploading.4e448015.gif轉存失敗重新上傳取消SQL連接

  • 內連接(Inner Join):僅將兩個表中滿足連接條件的行組合起來作爲結果集
    • 自然連接:只考慮屬性相同的元組對;
    • 等值連接:給定條件進行查詢
  • 外連接(Outer Join)
    • 左連接:左邊表的所有數據都有顯示出來,右邊的表數據只顯示共同有的那部分,沒有對應的部分補NULL;
    • 右連接:和左連接相反;
    • 全外連接(Full Outer Join):查詢出左表和右表所有數據,但是去除兩表的重複數據
  • 交叉連接(Cross Join):返回兩表的笛卡爾積(對於所含數據分別爲m、n的表,返回m*n的結果)

什麼是存儲過程?有哪些優缺點?

存儲過程是事先經過編譯並存儲在數據庫中的一段SQL語句的集合。想要實現相應的功能時,只需要調用這個存儲過程就行了(類似於函數,輸入具有輸出參數)。

優點:

  • 預先編譯,而不需要每次運行時編譯,提高了數據庫執行效率
  • 封裝了一系列操作,對於一些數據交互比較多的操作,相比於單獨執行SQL語句,可以減少網絡通信量
  • 具有可複用性,減少了數據庫開發的工作量;
  • 安全性高,可以讓沒有權限的用戶通過存儲過程間接操作數據庫;
  • 易於維護

缺點:

  • 可移植性差,存儲過程將應用程序綁定到了數據庫上;
  • 開發調試複雜:沒有好的IDE;
  • 修改複雜,需要重新編譯,有時還需要更新程序中的代碼以更新調用

Drop/Delete/Truncate的區別?

  • Delete用來刪除表的全部或者部分數據,執行delete之後,用戶需要提交之後纔會執行,會觸發表上的DELETE觸發器(包含一個OLD的虛擬表,可以只讀訪問被刪除的數據),DELETE之後表結構還在,刪除很慢,一行一行地刪,因爲會記錄日誌,可以利用日誌還原數據;
  • Truncate刪除表中的所有數據,這個操作不能回滾,也不會觸發這個表上的觸發器。操作比DELETE快很多(直接把表drop掉,再創建一個新表,刪除的數據不能找回)。如果表中有自增(AUTO_INCREMENT)列,則重置爲1;
  • Drop命令從數據庫中刪除表,所有的數據行,索引和約束都會被刪除;不能回滾,不會觸發觸發器;

什麼是觸發器?

觸發器(TRIGGER)是由事件(比如INSERT/UPDATE/DELETE)來觸發運行的操作(不能被直接調用,不能接收參數)。在數據庫裏以獨立的對象存儲,用於保證數據完整性(比如可以檢驗或轉換數據)。

有哪些約束類型?

約束(Constraint)類型:主鍵(Primary Key)約束,唯一約束(Unique),檢查約束,非空約束,外鍵(Foreign Key)約束。

什麼是視圖?什麼是遊標?

  • 視圖:從數據庫的基本表中通過查詢選取出來的數據組成的虛擬表(數據庫中存放視圖的定義)。可以對其進行增/刪/改/查等操作。特別地,對視圖的修改不影響基本表。好處:
    • 通過只給用戶訪問視圖的權限,保證數據的安全性
    • 簡化複雜的SQL操作,隱藏數據的複雜性(比如複雜的連接);
  • 遊標(Cursor):用於定位在查詢返回的結果集的特定行,以對特定行進行操作。使用遊標可以方便地對結果集進行移動遍歷,根據需要滾動或對瀏覽/修改任意行中的數據。主要用於交互式應用。

數據庫索引的實現原理(B+樹)

數據結構部分:B樹,B+樹

使用B樹和B+樹的比較

InnoDB的索引使用的是B+樹實現,B+樹對比B樹的好處:

  • IO次數少:B+樹的中間結點只存放索引,數據都存在葉結點中,因此中間結點可以存更多的數據,讓索引樹更加矮胖;
  • 範圍查詢效率更高:B樹需要中序遍歷整個樹,只B+樹需要遍歷葉結點中的鏈表;
  • 查詢效率更加穩定:每次查詢都需要從根結點到葉結點,路徑長度相同,所以每次查詢的效率都差不多

使用B樹索引和哈希索引的比較

哈希索引能以 O(1) 時間進行查找,但是隻支持精確查找,無法用於部分查找和範圍查找,無法用於排序與分組;B樹索引支持大於小於等於查找,範圍查找。哈希索引遇到大量哈希值相等的情況後查找效率會降低。哈希索引不支持數據的排序。

使用索引的優點

  • 大大加快了數據的檢索速度
  • 可以顯著減少查詢中分組和排序的時間;
  • 通過創建唯一性索引,可以保證數據庫表中每一行數據的唯一性;
  • 將隨機 I/O 變爲順序 I/O(B+Tree 索引是有序的,會將相鄰的數據都存儲在一起)

缺點:建立和維護索引耗費時間空間,更新索引很慢。

哪些情況下索引會失效?

  • 以“%(表示任意0個或多個字符)”開頭的LIKE語句;
  • OR語句前後沒有同時使用索引;
  • 數據類型出現隱式轉化(如varchar不加單引號的話可能會自動轉換爲int型);
  • 對於多列索引,必須滿足 最左匹配原則/最左前綴原則 (最左優先,eg:多列索引col1、col2和col3,則 索引生效的情形包括 col1或col1,col2或col1,col2,col3);
  • 如果MySQL估計全表掃描比索引快,則不使用索引(比如非常小的表)

在哪些地方適合創建索引?

  • 某列經常作爲最大最小值;
  • 經常被查詢的字段;
  • 經常用作表連接的字段;
  • 經常出現在ORDER BY/GROUP BY/DISDINCT後面的字段

創建索引時需要注意什麼?

展開

  • 只應建立在小字段上,而不要對大文本或圖片建立索引(一頁存儲的數據越多一次IO操作獲取的數據越大效率越高);
  • 建立索引的字段應該非空,在MySQL中,含有空值的列很難進行查詢優化,因爲它們使得索引、索引的統計信息以及比較運算更加複雜。應該用0、一個特殊的值或者一個空串代替NULL;
  • 選擇數據密度大(唯一值佔總數的百分比很大)的字段作索引

索引的分類?

  • 普通索引
  • 唯一索引 UNIQUE:索引列的值必須唯一,但允許有空值;
  • 主鍵索引 PRIMARY KEY:必須唯一,不允許空值(是一種特殊的唯一索引;MySQL創建主鍵時默認爲聚集索引,但主鍵也可以是非聚集索引);
  • 單列索引和多列索引/複合索引(Composite):索引的列數;
  • 覆蓋(Covering)索引:索引包含了所有滿足查詢所需要的數據,查詢的時候只需要讀取索引而不需要回表讀取數據;
  • 聚集(Clustered)索引/非聚集索引:對磁盤上存放數據的物理地址重新組織以使這些數據按照指定規則排序的一種索引(數據的物理排列順序和索引排列順序一致)。因此每張表只能創建一個聚集索引(因爲要改變物理存儲順序)。優點是查詢速度快,因爲可以直接按照順序得到需要數據的物理地址。缺點是進行修改的速度較慢。對於需要經常搜索範圍的值很有效。非聚集索引只記錄邏輯順序,並不改變物理順序;
  • 分區索引(?)
  • 虛擬索引(Virtual):模擬索引的存在而不用真正創建一個索引,用於快速測試創建索引對執行計劃的影響。沒有相關的索引段,不增加存儲空間的使用

MySQL的兩種存儲引擎 InnoDB 和 MyISAM 的區別?

  • InnoDB支持事務,可以進行Commit和Rollback;
  • MyISAM 只支持表級鎖,而 InnoDB 還支持行級鎖,提高了併發操作的性能;
  • InnoDB 支持外鍵
  • MyISAM 崩潰後發生損壞的概率比 InnoDB 高很多,而且恢復的速度也更慢;
  • MyISAM 支持壓縮表和空間數據索引,InnoDB需要更多的內存和存儲;
  • InnoDB 支持在線熱備份

應用場景

  • MyISAM 管理非事務表。它提供高速存儲和檢索(MyISAM強調的是性能,每次查詢具有原子性,其執行速度比InnoDB更快),以及全文搜索能力。如果表比較小,或者是隻讀數據(有大量的SELECT),還是可以使用MyISAM;
  • InnoDB 支持事務,併發情況下有很好的性能,基本可以替代MyISAM

熱備份和冷備份

  • 熱備份:在數據庫運行的情況下備份的方法。優點:可按表或用戶備份,備份時數據庫仍可使用,可恢復至任一時間點。但是不能出錯
  • 冷備份:數據庫正常關閉後,將關鍵性文件複製到另一位置的備份方式。優點:操作簡單快速,恢復簡單

如何優化數據庫?

SQL 語句的優化

分析慢查詢日誌:記錄了在MySQL中響應時間超過閥值long_query_time的SQL語句,通過日誌去找出IO大的SQL以及發現未命中索引的SQL

使用 Explain 進行分析:通過explain命令可以得到表的讀取順序、數據讀取操作的操作類型、哪些索引可以使用、哪些索引被實際使用、表之間的引用以及被掃描的行數等問題;

  • 應儘量避免在 where 子句中使用!=<>操作符或對字段進行null值判斷,否則將引擎放棄使用索引而進行全表掃描;
  • 只返回必要的列:最好不要使用 SELECT * 語句;
  • 只返回必要的行:使用 LIMIT 語句來限制返回的數據;
  • 將一個大連接查詢分解成對每一個表進行一次單表查詢,然後在應用程序中進行關聯,這樣做的好處有:
    • 讓緩存更高效。對於連接查詢,如果其中一個表發生變化,那麼整個查詢緩存就無法使用。而分解後的多個查詢,即使其中一個表發生變化,對其它表的查詢緩存依然可以使用;
    • 分解成多個單表查詢,這些單表查詢的緩存結果更可能被其它查詢使用到,從而減少冗餘的查詢;
    • 減少鎖競爭

索引的優化

注意會引起索引失效的情況,以及在適合的地方建立索引

數據庫表結構的優化

  • 設計表時遵循三大範式
  • 選擇合適的數據類型:儘可能不要存儲NULL字段;使用簡單的數據類型(int, varchar/ text);
  • 表的水平切分(Sharding):將同一個表中的記錄拆分到多個結構相同的表中(策略:哈希取模;根據ID範圍來分)。當一個表的數據不斷增多時,Sharding 是必然的選擇,它可以將數據分佈到集羣的不同節點上,從而緩解單個數據庫的壓力;
  • 表的垂直切分:將一張表按列切分成多個表。可以將不常用的字段單獨放在同一個表中;把大字段獨立放入一個表中;或者把經常使用的字段(關係密切的)放在一張表中。垂直切分之後業務更加清晰,系統之間整合或擴展容易,數據維護簡單

系統配置的優化

  • 操作系統:增加TCP支持的隊列數;
  • MySQL配置文件優化:緩存池大小和個數設置

硬件的優化

  • 磁盤性能:固態硬盤;
  • CPU:多核且高頻;
  • 內存:增大內存

什麼是主從複製?實現原理是什麼?

主從複製(Replication)是指數據可以從一個MySQL數據庫主服務器複製到一個或多個從服務器,從服務器可以複製主服務器中的所有數據庫或者特定的數據庫,或者特定的表。默認採用異步模式。

實現原理:

  • 主服務器 binary log dump 線程:將主服務器中的數據更改(增刪改)日誌寫入 Binary log 中;
  • 從服務器 I/O 線程:負責從主服務器讀取binary log,並寫入本地的 Relay log;
  • 從服務器 SQL 線程:負責讀取 Relay log,解析出主服務器已經執行的數據更改,並在從服務器中重新執行(Replay),保證主從數據的一致性

爲什麼要主從複製?

展開

  • 讀寫分離:主服務器負責寫,從服務器負責讀
    • 緩解了鎖的爭用,即使主服務器中加了鎖,依然可以進行讀操作;
    • 從服務器可以使用 MyISAM,提升查詢性能以及節約系統開銷;
    • 增加冗餘,提高可用性
  • 數據實時備份,當系統中某個節點發生故障時,可以方便的故障切換
  • 降低單個服務器磁盤I/O訪問的頻率,提高單個機器的I/O性能

關係型數據庫和非關係型數據庫的區別?

參考

待完成

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章