高級JAVA面試題詳解(四)——數據庫MySQL(MySQL優劣、存儲引擎、事務、索引、鎖、刪除重複數據)

MySQL詳解 上篇

主要寫一些除分庫分表、主從之外的其它問題

MySQL和MongoDB的區別

MySQL MongoDB
數據庫模型 非關係型 關係型
存儲方式 JSON的文檔的格式存儲
虛擬內存+持久化
不同引擎有不同的存儲方式
數據處理方式 基於內存,將熱數據存放在物理內存中,從而達到高速讀寫 不同引擎有自己的特點
事務性 僅支持單文檔事務操作,弱一致性 支持事務操作
查詢語句 MongoDB查詢方式(類似JavaScript的函數) SQL語句
根據各自優劣如何選擇使用MySQL還是MongoDB

MongoDB

  1. 側重高數據寫入的性能,而非事務安全,適合業務系統中有大量“低價值”數據的場景。例如做日誌系統。
  2. 因JSON格式存儲,不像MySQL建表之後需要把字段提前設計好,對數據格式不明確的需求支持較好。像第三方數據獲取,或授權信息返回數據存儲可以選用MongoDB

MySQL

  1. MongoDB不支持事務操作,需要用到事務的應用選擇MySQL例如訂單狀態流轉、支付、優惠券使用等等。
  2. MongoDB目前不支持join操作,需要複雜查詢的選用MySQL
  3. MySQL使用廣泛,SQL語句成熟,正常業務考慮到開發同事會選用MySQL

MySQL 存儲引擎

在mysql5之後,支持的存儲引擎有十幾個,但是常用的就那麼幾種,這裏簡單列舉三種說明InnoDBMyISAMMEMORY

InnoDB
  1. 5.5版本之後默認爲引擎
  2. 支持事務,默認的事務隔離級別爲可重複度,通過MVCC(併發版本控制)來實現的。
  3. 支持自增長列
  4. 支持外鍵
  5. 使用的鎖粒度爲行級鎖,可以支持更高的併發
  6. 存在着緩衝管理,通過緩衝池,將索引和數據全部緩存起來,加快查詢的速度;
  7. 支持兩種存儲方式,分別爲:共享表空間存儲獨立表空間存儲
    7.1) 共享表空間存儲:一個數據庫的所有表數據保存在一個單獨的表空間裏面,而這個表空間可以由很多個文件組成,一個表可以跨多個文件存在,所以其大小限制不再是文件大小的限制,而是其自身的限制,官方文檔上寫的是64TB。對於經常刪除操作的這類應用不適合用共享表空間,因爲大量刪除操作後表空間中將會有大量的空隙。
    7.2)獨立表空間存儲:每個表都有自已獨立的表空間,每個表的數據和索引都會存在自已的表空間中,可以實現單表在不同的數據庫中移動,空間可以回收。但受文件大小影響,當單表佔用空間過大時,存儲空間不足。

緩衝池簡介
       InnoDB存儲引擎是基於磁盤存儲的,並將其記錄按照頁的方式進行管理。在數據庫系統中由於CPU速度與磁盤速度之間的鴻溝,基於磁盤的數據庫系統通常使用緩衝技術來提高數據的整體性能。
       緩衝池簡單來說就是一塊內存區域.通過內存的速度來彌補磁盤速度較慢對數據庫性能的影響。
       讀取操作首先判斷該頁是不是在緩衝池中,未命中則讀取磁盤上的頁。
       修改操作首先修改在緩衝池中頁,然後再以一定的頻率刷新到磁盤。
       緩衝池配置可以通過INNODB_BUFFER_POOL_SZIE來設置,官方文檔建議,緩衝池的大小最多應設置爲物理內存的80%,正常使用可以設置爲(50%~80%)之間。

MyISAM
  1. 5.5版本前的默認引擎
  2. 不支持事務,也不支持外鍵
  3. 鎖粒度爲表級鎖 ,併發支持差
  4. 存儲格式:靜態表(Fixed)動態表(Dynamic)壓縮表
    3.1) 靜態表:靜態表中的字段都是非變長字段,每個記錄都是固定的長度,當表不包含變量長度列(VARCHAR, BLOB, 或TEXT)時,使用這個格式。存儲迅速,出現故障容易恢復,佔用空間比動態表大,靜態表在進行數據存儲時會按照事先定義的列寬度補足空格,但在訪問的時候會去掉這些空格;
    3.2)動態表:如果表包含任何可變長度的字段(varchar、blob、text),或者該表創建時用row_format=dynamic指定,則該表使用動態格式存儲。佔用空間小,頻繁的更新和刪除操作會產生碎片。
    3.3)壓縮表:由myisampack工具創建,佔據非常小的磁盤空間,因爲每個記錄都是被單獨壓縮的
MEMORY
  1. 數據存儲在內存中、訪問速度快,但是在服務器重啓之後,所有數據都會丟失
  2. 支持的數據類型有限制,比如:不支持TEXT和BLOB類型,對於字符串類型的數據,只支持固定長度的行,VARCHAR會被自動存儲爲CHAR類型;
  3. 支持的鎖粒度爲表級鎖。所以,在訪問量比較大時,表級鎖會成爲MEMORY存儲引擎的瓶頸

MySQL 事務隔離級別與傳播方式

事務的特性(ACID)
  • 原子性(Atomicity):組成一個事務的多個數據庫操作是一個不可分割的原子單元;只有所有操作執行成功,整個事務才提交,其中一個操作失敗,都必須回滾到初始狀態。
  • 一致性(Consistency):事務執行的前後,數據完整性保持一致。
  • 隔離性(Isolation):事務執行不應該受到其他事務的干擾。(根據數據庫的隔離級別,會產生不同程度的干擾)。
  • 持久性(Durubility):事務一旦結束,數據就持久化到數據庫中。
隔離級別(TRANSACTION)

髒讀: 一個事務讀取到另一個事務未提交的數據。
不可重複讀:一個事務讀取到另一個事務提交的更新的數據,導致多次查詢結果不一致。
幻讀:一個事務讀取到另一個事務提交的新增的數據,導致多次查詢的結果不一致。

事務隔離級別 髒讀 不可重複讀 幻讀
讀未提交(read-uncommitted)
不可重複讀(read-committed)
可重複讀(repeatable-read)
串行化(serializable)

MySQL默認隔離級別爲:可重複度

事務的傳播方式(PROPAGATION)
  • PROPAGATION_REQUIRED:支持當前事務,如果當前沒有事務,就新建一個事務。(默認傳播屬性)
  • PROPAGATION_SUPPORTS:支持當前事務,如果當前沒有事務,就以非事務方式執行。
  • PROPAGATION_MANDATORY:支持當前事務,如果當前沒有事務,就拋出異常。
  • PROPAGATION_REQUIRESNEW:新建事務,如果當前存在事務,把當前事務掛起。
  • PROPAGATION_NOT_SUPPORTED:以非事務方式執行,如果當前存在事務,就把當前事務掛起。
  • PROPAGATION_NEVER:以非事務方式執行,如果當前存在事務,則拋出異常。
  • PROPAGATION_NESTED:如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則進行與PROPAGATION_REQUIRED類似的操作。

索引

mysql有哪些索引,分別是什麼數據結構

Hash索引

  • 數組+鏈表的結構,通過hash函數計算出key在數組中的位置,然後如果出現hash衝突就通過鏈表來解決。
  • hash索引存儲的是計算得到的hash值和行指針,而不存儲具體的行值,所以通過hash索引查詢數據需要進行兩次查詢(首先查詢行的位置,然後找到具體的數據)

B+Tree索引

  • 是最常用的mysql數據庫索引算法,採用B+樹的結構。
  • 葉子節點存儲了整行數據的是聚簇索引(主鍵索引)
  • 葉子節點存儲了主鍵的值的是非聚簇索引(非主鍵索引),非主鍵索引的葉子節點是主鍵的值,需要回表查詢

韭菜課堂開課啦:

非主鍵索引一定會查詢多次嗎(發生回表查詢)
覆蓋索引 指一個查詢語句的執行只用從索引中就能夠取得,不必從數據表中讀取。
例如我建了一個聯合索引 A、B字段 然後通過where條件A字段去查詢B字段。
那假如我沒有建主鍵咋辦?
Innodb表中在沒有默認主鍵的情況下會生成一個6byte空間的自動增長主鍵,可以用select _rowid from table來查詢

B+Tree索引與Hash索引對比優劣

  • Hash索引,其檢索效率非常高,索引的檢索可以一次定位,但是隻支持“=”和“in”,如存在大量Hash值相等的情況後性能並不一定就會比B+Tree索引高。
  • B+Tree索引支持範圍查詢,Hash索引不支持
  • Hash索引不支持多列聯合索引的最左匹配規則
  • Hash索引不支持索引排序,索引值和計算出來的hash值大小並不一定一致。

MySQL爲什麼要選擇B+tree來做索引的結構

  • 普通的二叉樹可能因爲插入的數據最後變成一個很長的鏈表,哪怕採用紅黑樹保證平衡,數據量較大情況下依然會出現節點長度過長的問題。(特別是訪問深層數據時,需要索引多次,數據結構設計的更爲‘矮胖’一點就可以減少訪問的層數)
  • B-樹每個節點 key 和 data 在一起,無法區間查找。(範圍查詢在數據庫中是很常用的)
  • B+樹只需要去遍歷葉子節點就可以實現整棵樹的遍歷
  • B+樹的內部節點並沒有指向關鍵字具體信息的指針,內部節點相對B樹更小,如果把所有同一內部節點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多,一次性讀入內存的需要查找的關鍵字也就越多,相對IO讀寫次數就降低了

MySQL鎖

鎖的機制

共享鎖排他鎖
共享鎖(讀鎖):其他事務可以讀,但不能寫。
排他鎖(寫鎖) :止其他事務取得相同數據集的共享讀鎖和排他寫鎖。

爲了允許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB 還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖,且取得意向共享鎖/意向排他鎖是取得共享鎖/排他鎖的前置條件。:
意向共享鎖(IS):事務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的 IS 鎖。
意向排他鎖(IX):事務打算給數據行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的 IX 鎖。

鎖的粒度
  • 表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。可避免死鎖。
  • 行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。
    • 在 InnoDB 中,除單個 SQL 組成的事務外,鎖是逐步獲得的,這就決定了在 InnoDB 中發生死鎖是可能的。
    • 行級鎖只在存儲引擎層實現,而Mysql服務器層沒有實現。
    • InnoDB 行鎖是通過給索引上的索引項加鎖來實現的,所以只有通過索引條件檢索數據,InnoDB 才使用行級鎖,否則,InnoDB 將使用表鎖!
  • 頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。
InnoDB的間隙鎖:

當我們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫做“間隙(GAP)”,InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖(Next-Key鎖)。
很顯然,在使用範圍條件檢索並鎖定記錄時,InnoDB這種加鎖機制會阻塞符合條件範圍內鍵值的併發插入,這往往會造成嚴重的鎖等待。因此,在實際應用開發中,尤其是併發插入比較多的應用,我們要儘量優化業務邏輯,儘量使用相等條件來訪問更新數據,避免使用範圍條件。
InnoDB使用間隙鎖的目的

  • 防止幻讀,以滿足相關隔離級別的要求;
  • 滿足恢復和複製的需要:
死鎖

死鎖的產生

  • 死鎖是指兩個或多個事務在同一資源上相互佔用,並請求鎖定對方佔用的資源,從而導致惡性循環。
  • 當事務試圖以不同的順序鎖定資源時,就可能產生死鎖。多個事務同時鎖定同一個資源時也可能會產生死鎖。
    死鎖後InnoDB如何處理
    死鎖發生以後,只有部分或完全回滾其中一個事務,才能打破死鎖,InnoDB目前處理死鎖的方法是,將持有最少行級排他鎖的事務進行回滾。所以事務型應用程序在設計時必須考慮如何處理死鎖,多數情況下只需要重新執行因死鎖回滾的事務即可。但在涉及外部鎖,或涉及表鎖的情況下,InnoDB 並不能完全自動檢測到死鎖。
    如何避免死鎖
  • 在應用中,如果不同的程序會併發存取多個表,應儘量約定以相同的順序來訪問表,這樣可以大大降低產生死鎖的機會。
  • 在程序以批量方式處理數據的時候,如果事先對數據排序,保證每個線程按固定的順序來處理記錄,也可以大大降低出現死鎖的可能。
  • 在事務中,如果要更新記錄,應該直接申請足夠級別的鎖,即排他鎖,而不應先申請共享鎖,更新時再申請排他鎖,因爲當用戶申請排他鎖時,其他事務可能又已經獲得了相同記錄的共享鎖,從而造成鎖衝突,甚至死鎖。
  • 在REPEATABLE-READ隔離級別下,如果兩個線程同時對相同條件記錄用SELECT…FOR UPDATE加排他鎖,在沒有符合該條件記錄情況下,兩個線程都會加鎖成功。程序發現記錄尚不存在,就試圖插入一條新記錄,如果兩個線程都這麼做,就會出現死鎖。這種情況下,將隔離級別改成READ COMMITTED,就可避免問題。
  • 當隔離級別爲READ COMMITTED時,如果兩個線程都先執行SELECT…FOR UPDATE,判斷是否存在符合條件的記錄,如果沒有,就插入記錄。此時,只有一個線程能插入成功,另一個線程會出現鎖等待,當第1個線程提交後,第2個線程會因主鍵重出錯,但雖然這個線程出錯了,卻會獲得一個排他鎖。這時如果有第3個線程又來申請排他鎖,也會出現死鎖。對於這種情況,可以直接做插入操作,然後再捕獲主鍵重異常,或者在遇到主鍵重錯誤時,總是執行ROLLBACK釋放獲得的排他鎖。
樂觀鎖、悲觀鎖

樂觀鎖
樂觀鎖, 顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量。如果發現版本號被修改則從新查詢後再更新,但是爲了避免極端情況下一直循環查詢會去限制次數,當到達次數上限後,採用悲觀鎖。
悲觀鎖
悲觀鎖,顧名思義,就是很悲觀,每次去拿數據的時候都認爲別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想修改這個數據就會等待直到它拿到鎖。

課外習題:
表沒有主鍵和唯一索引的情況下,怎麼刪除重複的數據?
在有主鍵的情況下刪除重複數據太簡單了,這裏就不做描述了,咱們來點升級版本的。

方案1:(表中存在大量重複數據)

-- 新建臨時表將目標表的不重複的數據插入保存起來
create table tmp as (select distinct * from table_name);
-- 清空目標表的數據
delete from table_name;
-- 將臨時表tmp的數據插入
insert into table_nameselect * from tmp;
-- 刪除臨時表
Drop table tmp;

方案2:(表中數據量大,但重複數據少)

-- 假設表中只有兩個字段 name 和 value,取出所有重複的數據放入臨時表
create table tmp as (select * from table_name GROUP BY demo_table.`name`,demo_table.`value` HAVING COUNT(1) > 1);
-- 根據臨時表的數據刪除現有表中的重複數據, 注意delete的語法
DELETE demo_table from demo_table,tmp WHERE demo_table.`value` = tmp.`value` AND demo_table.name = tmp.name
-- 將臨時表的數據插入原表
INSERT into demo_table select * from tmp;
-- 刪除臨時表
Drop table tmp;

方案3:存儲過程通過 limit刪除
這裏就不貼代碼了,給你們一個思路就好,循環遍歷查詢出的所有重複數據結果,將每行數據當做查詢條件查詢,判斷結果是否大於1,如果大於1執行一次delete操作,delete語句末尾加上 limit 1(只刪除1行數據),然後繼續循環遍歷。

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