有情懷,有干貨,微信搜索【三太子敖丙】關注這個不一樣的程式員,
本文 GitHub https://github.com/JavaFamily 已收錄,有一線大廠面試完整考點、資料以及我的系列文章,
國慶在家無聊,我隨手翻了一下家里資料庫相關的書籍,這一翻我就看上癮了,因為大學比較熟悉的一些資料庫范式我居然都忘了,懷揣著好奇心我就看了一個小國慶,
看的程序中我也做了一些小筆記,可能沒我之前系統文章那么有趣,但是絕對也是干貨十足,適合大家去回顧或者面試突擊的適合看看,也不多說先放圖,

存盤引擎
InnoDB
InnoDB 是 MySQL 默認的事務型存盤引擎,只要在需要它不支持的特性時,才考慮使用其他存盤引擎,
InnoDB 采用 MVCC 來支持高并發,并且實作了四個標準隔離級別(未提交讀、提交讀、可重復讀、可串行化),其默認級別時可重復讀(REPEATABLE READ),在可重復讀級別下,通過 MVCC + Next-Key Locking 防止幻讀,
主索引時聚簇索引,在索引中保存了資料,從而避免直接讀取磁盤,因此對主鍵查詢有很高的性能,
InnoDB 內部做了很多優化,包括從磁盤讀取資料時采用的可預測性讀,能夠自動在記憶體中創建 hash 索引以加速讀操作的自適應哈希索引,以及能夠加速插入操作的插入緩沖區等,
InnoDB 支持真正的在線熱備份,MySQL 其他的存盤引擎不支持在線熱備份,要獲取一致性視圖需要停止對所有表的寫入,而在讀寫混合的場景中,停止寫入可能也意味著停止讀取,
MyISAM
設計簡單,資料以緊密格式存盤,對于只讀資料,或者表比較小、可以容忍修復操作,則依然可以使用它,
提供了大量的特性,包括壓縮表、空間資料索引等,
不支持事務,
不支持行級鎖,只能對整張表加鎖,讀取時會對需要讀到的所有表加共享鎖,寫入時則對表加排它鎖,但在表有讀取操作的同時,也可以往表中插入新的記錄,這被稱為并發插入(CONCURRENT INSERT),
可以手工或者自動執行檢查和修復操作,但是和事務恢復以及崩潰恢復不同,可能導致一些資料丟失,而且修復操作是非常慢的,
如果指定了 DELAY_KEY_WRITE 選項,在每次修改執行完成時,不會立即將修改的索引資料寫入磁盤,而是會寫到記憶體中的鍵緩沖區,只有在清理鍵緩沖區或者關閉表的時候才會將對應的索引塊寫入磁盤,這種方式可以極大的提升寫入性能,但是在資料庫或者主機崩潰時會造成索引損壞,需要執行修復操作,
InnoDB 和 MyISAM 的比較
事務:InnoDB 是事務型的,可以使用 Commit 和 Rollback 陳述句,
并發:MyISAM 只支持表級鎖,而 InnoDB 還支持行級鎖,
外鍵:InnoDB 支持外鍵,
備份:InnoDB 支持在線熱備份,
崩潰恢復:MyISAM 崩潰后發生損壞的概率比 InnoDB 高很多,而且恢復的速度也更慢,
其它特性:MyISAM 支持壓縮表和空間資料索引,
索引
B+ Tree 原理
資料結構
B Tree 指的是 Balance Tree,也就是平衡樹,平衡樹是一顆查找樹,并且所有葉子節點位于同一層,
B+ Tree 是 B 樹的一種變形,它是基于 B Tree 和葉子節點順序訪問指標進行實作,通常用于資料庫和作業系統的檔案系統中,
B+ 樹有兩種型別的節點:內部節點(也稱索引節點)和葉子節點,內部節點就是非葉子節點,內部節點不存盤資料,只存盤索引,資料都存在葉子節點,
內部節點中的 key 都按照從小到大的順序排列,對于內部節點中的一個 key,左子樹中的所有 key 都小于它,右子樹中的 key 都大于等于它,葉子節點的記錄也是按照從小到大排列的,
每個葉子節點都存有相鄰葉子節點的指標,

操作
查找
查找以典型的方式進行,類似于二叉查找樹,起始于根節點,自頂向下遍歷樹,選擇其分離值在要查找值的任意一邊的子指標,在節點內部典型的使用是二分查找來確定這個位置,
插入
Perform a search to determine what bucket the new record should go into.
If the bucket is not full(a most b - 1 entries after the insertion,b 是節點中的元素個數,一般是頁的整數倍),add tht record.
Otherwise,before inserting the new record
split the bucket. original node has 「(L+1)/2」items new node has 「(L+1)/2」items
Move 「(L+1)/2」-th key to the parent,and insert the new node to the parent. Repeat until a parent is found that need not split.
If the root splits,treat it as if it has an empty parent ans split as outline above.
B-trees grow as the root and not at the leaves.
洗掉
和插入類似,只不過是自下而上的合并操作,
樹的常見特性
AVL 樹
平衡二叉樹,一般是用平衡因子差值決定并通過旋轉來實作,左右子樹樹高差不超過1,那么和紅黑樹比較它是嚴格的平衡二叉樹,平衡條件非常嚴格(樹高差只有1),只要插入或洗掉不滿足上面的條件就要通過旋轉來保持平衡,由于旋轉是非常耗費時間的,所以 AVL 樹適用于插入/洗掉次數比較少,但查找多的場景,
紅黑樹
通過對從根節點到葉子節點路徑上各個節點的顏色進行約束,確保沒有一條路徑會比其他路徑長2倍,因而是近似平衡的,所以相對于嚴格要求平衡的AVL樹來說,它的旋轉保持平衡次數較少,適合,查找少,插入/洗掉次數多的場景,(現在部分場景使用跳表來替換紅黑樹,可搜索“為啥 redis 使用跳表(skiplist)而不是使用 red-black?”)
B/B+ 樹
多路查找樹,出度高,磁盤IO低,一般用于資料庫系統中,
B + 樹與紅黑樹的比較
紅黑樹等平衡樹也可以用來實作索引,但是檔案系統及資料庫系統普遍采用 B+ Tree 作為索引結構,主要有以下兩個原因:
(一)磁盤 IO 次數
B+ 樹一個節點可以存盤多個元素,相對于紅黑樹的樹高更低,磁盤 IO 次數更少,
(二)磁盤預讀特性
為了減少磁盤 I/O 操作,磁盤往往不是嚴格按需讀取,而是每次都會預讀,預讀程序中,磁盤進行順序讀取,順序讀取不需要進行磁盤尋道,每次會讀取頁的整數倍,
作業系統一般將記憶體和磁盤分割成固定大小的塊,每一塊稱為一頁,記憶體與磁盤以頁為單位交換資料,資料庫系統將索引的一個節點的大小設定為頁的大小,使得一次 I/O 就能完全載入一個節點,
B + 樹與 B 樹的比較
B+ 樹的磁盤 IO 更低
B+ 樹的內部節點并沒有指向關鍵字具體資訊的指標,因此其內部節點相對 B 樹更小,如果把所有同一內部結點的關鍵字存放在同一盤塊中,那么盤塊所能容納的關鍵字數量也越多,一次性讀入記憶體中的需要查找的關鍵字也就越多,相對來說IO讀寫次數也就降低了,
B+ 樹的查詢效率更加穩定
由于非葉子結點并不是最終指向檔案內容的結點,而只是葉子結點中關鍵字的索引,所以任何關鍵字的查找必須走一條從根結點到葉子結點的路,所有關鍵字查詢的路徑長度相同,導致每一個資料的查詢效率相當,
B+ 樹元素遍歷效率高
B 樹在提高了磁盤IO性能的同時并沒有解決元素遍歷的效率低下的問題,正是為了解決這個問題,B+樹應運而生,B+樹只要遍歷葉子節點就可以實作整棵樹的遍歷,而且在資料庫中基于范圍的查詢是非常頻繁的,而 B 樹不支持這樣的操作(或者說效率太低),
MySQL 索引
索引是在存盤引擎層實作的,而不是在服務器層實作的,所以不同存盤引擎具有不同的索引型別和實作,
B+ Tree 索引
是大多數 MySQL 存盤引擎的默認索引型別,
因為不再需要進行全表掃描,只需要對樹進行搜索即可,所以查找速度快很多,
因為 B+ Tree 的有序性,所以除了用于查找,還可以用于排序和分組,
可以指定多個列作為索引列,多個索引列共同組成鍵,
適用于全鍵值、鍵值范圍和鍵前綴查找,其中鍵前綴查找只適用于最左前綴查找,如果不是按照索引列的順序進行查找,則無法使用索引,
InnoDB 的 B+Tree 索引分為主索引和輔助索引,主索引的葉子節點 data 域記錄著完整的資料記錄,這種索引方式被稱為聚簇索引,因為無法把資料行存放在兩個不同的地方,所以一個表只能有一個聚簇索引,

輔助索引的葉子節點的 data 域記錄著主鍵的值,因此在使用輔助索引進行查找時,需要先查找到主鍵值,然后再到主索引中進行查找,這個程序也被稱作回表,

哈希索引
哈希索引能以 O(1) 時間進行查找,但是失去了有序性:
無法用于排序與分組; 只支持精確查找,無法用于部分查找和范圍查找,
InnoDB 存盤引擎有一個特殊的功能叫“自適應哈希索引”,當某個索引值被使用的非常頻繁時,會在 B+Tree 索引之上再創建一個哈希索引,這樣就讓 B+Tree 索引具有哈希索引的一些優點,比如快速的哈希查找,
全文索引
MyISAM 存盤引擎支持全文索引,用于查找文本中的關鍵詞,而不是直接比較是否相等,
查找條件使用 MATCH AGAINST,而不是普通的 WHERE,
全文索引使用倒排索引實作,它記錄著關鍵詞到其所在檔案的映射,
InnoDB 存盤引擎在 MySQL 5.6.4 版本中也開始支持全文索引,
空間資料索引
MyISAM 存盤引擎支持空間資料索引(R-Tree),可以用于地理資料存盤,空間資料索引會從所有維度來索引資料,可以有效地使用任意維度來進行組合查詢,
必須使用 GIS 相關的函式來維護資料,
索引優化
獨立的列
在進行查詢時,索引列不能是運算式的一部分,也不能是函式的引數,否則無法使用索引,
例如下面的查詢不能使用 actor_id 列的索引:
SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;
多列索引
在需要使用多個列作為條件進行查詢時,使用多列索引比使用多個單列索引性能更好,例如下面的陳述句中,最好把 actor_id 和 film_id 設定為多列索引,
SELECT film_id, actor_ id FROM sakila.film_actor
WHERE actor_id = 1 AND film_id = 1;
索引列的順序
讓選擇性最強的索引列放在前面,
索引的選擇性是指:不重復的索引值和記錄總數的比值,最大值為 1,此時每個記錄都有唯一的索引與其對應,選擇性越高,每個記錄的區分度越高,查詢效率也越高,
例如下面顯示的結果中 customer_id 的選擇性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面,
SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,
COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity,
COUNT(*)
FROM payment;
staff_id_selectivity: 0.0001
customer_id_selectivity: 0.0373
COUNT(*): 16049
前綴索引
對于 BLOB、TEXT 和 VARCHAR 型別的列,必須使用前綴索引,只索引開始的部分字符,
前綴長度的選取需要根據索引選擇性來確定,
覆寫索引
索引包含所有需要查詢的欄位的值,
具有以下優點:
索引通常遠小于資料行的大小,只讀取索引能大大減少資料訪問量, 一些存盤引擎(例如 MyISAM)在記憶體中只快取索引,而資料依賴于作業系統來快取,因此,只訪問索引可以不使用系統呼叫(通常比較費時), 對于 InnoDB 引擎,若輔助索引能夠覆寫查詢,則無需訪問主索引,
索引的優點
大大減少了服務器需要掃描的資料行數,
幫助服務器避免進行排序和分組,以及避免創建臨時表(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作,臨時表主要是在排序和分組程序中創建,不需要排序和分組,也就不需要創建臨時表),
將隨機 I/O 變為順序 I/O(B+Tree 索引是有序的,會將相鄰的資料都存盤在一起),
索引的使用條件
對于非常小的表、大部分情況下簡單的全表掃描比建立索引更高效;
對于中到大型的表,索引就非常有效;
但是對于特大型的表,建立和維護索引的代價將會隨之增長,這種情況下,需要用到一種技術可以直接區分出需要查詢的一組資料,而不是一條記錄一條記錄地匹配,例如可以使用磁區技術,
為什么對于非常小的表,大部分情況下簡單的全表掃描比建立索引更高效?
如果一個表比較小,那么顯然直接遍歷表比走索引要快(因為需要回表),
注:首先,要注意這個答案隱含的條件是查詢的資料不是索引的構成部分,否也不需要回表操作,其次,查詢條件也不是主鍵,否則可以直接從聚簇索引中拿到資料,
查詢性能優化
使用 explain 分析 select 查詢陳述句
explain 用來分析 SELECT 查詢陳述句,開發人員可以通過分析 Explain 結果來優化查詢陳述句,
select_type
常用的有 SIMPLE 簡單查詢,UNION 聯合查詢,SUBQUERY 子查詢等,
table
要查詢的表
possible_keys
The possible indexes to choose
可選擇的索引
key
The index actually chosen
實際使用的索引
rows
Estimate of rows to be examined
掃描的行數
type
索引查詢型別,經常用到的索引查詢型別:
const:使用主鍵或者唯一索引進行查詢的時候只有一行匹配 ref:使用非唯一索引 range:使用主鍵、單個欄位的輔助索引、多個欄位的輔助索引的最后一個欄位進行范圍查詢 index:和all的區別是掃描的是索引樹 all:掃描全表:
system
觸發條件:表只有一行,這是一個 const type 的特殊情況
const
觸發條件:在使用主鍵或者唯一索引進行查詢的時候只有一行匹配,
SELECT * FROM tbl_name WHERE primary_key=1;
SELECT * FROM tbl_name
WHERE primary_key_part1=1 AND primary_key_part2=2;

eq_ref
觸發條件:在進行聯接查詢的,使用主鍵或者唯一索引并且只匹配到一行記錄的時候
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column=other_table.column;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column_part1=other_table.column
AND ref_table.key_column_part2=1;
ref
觸發條件:使用非唯一索引
SELECT * FROM ref_table WHERE key_column=expr;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column=other_table.column;
SELECT * FROM ref_table,other_table
WHERE ref_table.key_column_part1=other_table.column
AND ref_table.key_column_part2=1;

range
觸發條件:只有在使用主鍵、單個欄位的輔助索引、多個欄位的輔助索引的最后一個欄位進行范圍查詢才是 range
SELECT * FROM tbl_name
WHERE key_column = 10;
SELECT * FROM tbl_name
WHERE key_column BETWEEN 10 and 20;
SELECT * FROM tbl_name
WHERE key_column IN (10,20,30);
SELECT * FROM tbl_name
WHERE key_part1 = 10 AND key_part2 IN (10,20,30);

index
The index join type is the same as ALL, except that the index tree is scanned. This occurs two ways:
觸發條件:
只掃描索引樹
1)查詢的欄位是索引的一部分,覆寫索引, 2)使用主鍵進行排序

all
觸發條件:全表掃描,不走索引
優化資料訪問
減少請求的資料量
只回傳必要的列:最好不要使用 SELECT * 陳述句, 只回傳必要的行:使用 LIMIT 陳述句來限制回傳的資料, 快取重復查詢的資料:使用快取可以避免在資料庫中進行查詢,特別在要查詢的資料經常被重復查詢時,快取帶來的查詢性能提升將會是非常明顯的,
減少服務器端掃描的行數
最有效的方式是使用索引來覆寫查詢,
重構查詢方式
切分大查詢
一個大查詢如果一次性執行的話,可能一次鎖住很多資料、占滿整個事務日志、耗盡系統資源、阻塞很多小的但重要的查詢,
DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);
rows_affected = 0
do {
rows_affected = do_query(
"DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH) LIMIT 10000")
} while rows_affected > 0
分解大連接查詢
將一個大連接查詢分解成對每一個表進行一次單表查詢,然后在應用程式中進行關聯,這樣做的好處有:
讓快取更高效,對于連接查詢,如果其中一個表發生變化,那么整個查詢快取就無法使用,而分解后的多個查詢,即使其中一個表發生變化,對其它表的查詢快取依然可以使用, 分解成多個單表查詢,這些單表查詢的快取結果更可能被其它查詢使用到,從而減少冗余記錄的查詢, 減少鎖競爭; 在應用層進行連接,可以更容易對資料庫進行拆分,從而更容易做到高性能和可伸縮, 查詢本身效率也可能會有所提升,例如下面的例子中,使用 IN() 代替連接查詢,可以讓 MySQL 按照 ID 順序進行查詢,這可能比隨機的連接要更高效,
SELECT * FROM tag
JOIN tag_post ON tag_post.tag_id=tag.id
JOIN post ON tag_post.post_id=post.id
WHERE tag.tag='mysql';
SELECT * FROM tag WHERE tag='mysql';
SELECT * FROM tag_post WHERE tag_id=1234;
SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904);
事務
事務是指滿足 ACID 特性的一組操作,可以通過 Commit 提交一個事務,也可以使用 Rollback 進行回滾,
ACID
事務最基本的莫過于 ACID 四個特性了,這四個特性分別是:
Atomicity:原子性 Consistency:一致性 Isolation:隔離性 Durability:持久性
原子性
事務被視為不可分割的最小單元,事務的所有操作要么全部成功,要么全部失敗回滾,
一致性
資料庫在事務執行前后都保持一致性狀態,在一致性狀態下,所有事務對一個資料的讀取結果都是相同的,
隔離性
一個事務所做的修改在最終提交以前,對其他事務是不可見的,
持久性
一旦事務提交,則其所做的修改將會永遠保存到資料庫中,即使系統發生崩潰,事務執行的結果也不能丟,
ACID 之間的關系
事務的 ACID 特性概念很簡單,但不好理解,主要是因為這幾個特性不是一種平級關系:
只有滿足一致性,事務的結果才是正確的, 在無并發的情況下,事務串行執行,隔離性一定能夠滿足,此時只要能滿足原子性,就一定能滿足一致性,在并發的情況下,多個事務并行執行,事務不僅要滿足原子性,還需要滿足隔離性,才能滿足一致性, 事務滿足持久化是為了能應對資料庫崩潰的情況,

隔離級別
未提交讀(READ UNCOMMITTED)
事務中的修改,即使沒有提交,對其他事務也是可見的,
提交讀(READ COMMITTED)
一個事務只能讀取已經提交的事務所做的修改,換句話說,一個事務所做的修改在提交之前對其他事務是不可見的,
可重復讀(REPEATABLE READ)
保證在同一個事務中多次讀取同樣資料的結果是一樣的,
可串行化(SERIALIZABLE)
強制事務串行執行,
需要加鎖實作,而其它隔離級別通常不需要,
| 隔離級別 | 臟讀 | 不可重復讀 | 幻影讀 |
|---|---|---|---|
| 未提交讀 | √ | √ | √ |
| 提交讀 | × | √ | √ |
| 可重復讀 | × | × | √ |
| 可串行化 | × | × | × |
鎖
鎖是資料庫系統區別于檔案系統的一個關鍵特性,鎖機制用于管理對共享資源的并發訪問,
鎖型別
共享鎖(S Lock)
允許事務讀一行資料
排他鎖(X Lock)
允許事務洗掉或者更新一行資料
意向共享鎖(IS Lock)
事務想要獲得一張表中某幾行的共享鎖
意向排他鎖
事務想要獲得一張表中某幾行的排他鎖
MVCC
多版本并發控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存盤引擎實作隔離級別的一種具體方式,用于實作提交讀和可重復讀這兩種隔離級別,而未提交讀隔離級別總是讀取最新的資料行,無需使用 MVCC,可串行化隔離級別需要對所有讀取的行都加鎖,單純使用 MVCC 無法實作,
基礎概念
版本號
系統版本號:是一個遞增的數字,每開始一個新的事務,系統版本號就會自動遞增, 事務版本號:事務開始時的系統版本號,
隱藏的列
MVCC 在每行記錄后面都保存著兩個隱藏的列,用來存盤兩個版本號:
創建版本號:指示創建一個資料行的快照時的系統版本號; 洗掉版本號:如果該快照的洗掉版本號大于當前事務版本號表示該快照有效,否則表示該快照已經被洗掉了,
Undo 日志
MVCC 使用到的快照存盤在 Undo 日志中,該日志通過回滾指標把一個資料行(Record)的所有快照連接起來,

實作程序
以下實作程序針對可重復讀隔離級別,
當開始一個事務時,該事務的版本號肯定大于當前所有資料行快照的創建版本號,理解這一點很關鍵,資料行快照的創建版本號是創建資料行快照時的系統版本號,系統版本號隨著創建事務而遞增,因此新創建一個事務時,這個事務的系統版本號比之前的系統版本號都大,也就是比所有資料行快照的創建版本號都大,
SELECT
多個事務必須讀取到同一個資料行的快照,并且這個快照是距離現在最近的一個有效快照,但是也有例外,如果有一個事務正在修改該資料行,那么它可以讀取事務本身所做的修改,而不用和其它事務的讀取結果一致,
把沒有對一個資料行做修改的事務稱為 T,T 所要讀取的資料行快照的創建版本號必須小于等于 T 的版本號,因為如果大于 T 的版本號,那么表示該資料行快照是其它事務的最新修改,因此不能去讀取它,除此之外,T 所要讀取的資料行快照的洗掉版本號必須是未定義或者大于 T 的版本號,因為如果小于等于 T 的版本號,那么表示該資料行快照是已經被洗掉的,不應該去讀取它,
INSERT
將當前系統版本號作為資料行快照的創建版本號,
DELETE
將當前系統版本號作為資料行快照的洗掉版本號,
UPDATE
將當前系統版本號作為更新前的資料行快照的洗掉版本號,并將當前系統版本號作為更新后的資料行快照的創建版本號,可以理解為先執行 DELETE 后執行 INSERT,
快照讀與當前讀
在可重復讀級別中,通過MVCC機制,雖然讓資料變得可重復讀,但我們讀到的資料可能是歷史資料,是不及時的資料,不是資料庫當前的資料!這在一些對于資料的時效特別敏感的業務中,就很可能出問題,
對于這種讀取歷史資料的方式,我們叫它快照讀 (snapshot read),而讀取資料庫當前版本資料的方式,叫當前讀 (current read),很顯然,在MVCC中:
快照讀
MVCC 的 SELECT 操作是快照中的資料,不需要進行加鎖操作,
select * from table ….;
當前讀
MVCC 其它會對資料庫進行修改的操作(INSERT、UPDATE、DELETE)需要進行加鎖操作,從而讀取最新的資料,可以看到 MVCC 并不是完全不用加鎖,而只是避免了 SELECT 的加鎖操作,
INSERT;
UPDATE;
DELETE;
在進行 SELECT 操作時,可以強制指定進行加鎖操作,以下第一個陳述句需要加 S 鎖,第二個需要加 X 鎖,
- select * from table where ? lock in share mode;
- select * from table where ? for update;
事務的隔離級別實際上都是定義的當前讀的級別,MySQL為了減少鎖處理(包括等待其它鎖)的時間,提升并發能力,引入了快照讀的概念,使得select不用加鎖,而update、insert這些“當前讀”的隔離性,就需要通過加鎖來實作了,
鎖演算法
Record Lock
鎖定一個記錄上的索引,而不是記錄本身,
如果表沒有設定索引,InnoDB 會自動在主鍵上創建隱藏的聚簇索引,因此 Record Locks 依然可以使用,
Gap Lock
鎖定索引之間的間隙,但是不包含索引本身,例如當一個事務執行以下陳述句,其它事務就不能在 t.c 中插入 15,
SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
Next-Key Lock
它是 Record Locks 和 Gap Locks 的結合,不僅鎖定一個記錄上的索引,也鎖定索引之間的間隙,例如一個索引包含以下值:10, 11, 13, and 20,那么就需要鎖定以下區間:
(-∞, 10]
(10, 11]
(11, 13]
(13, 20]
(20, +∞)
在 InnoDB 存盤引擎中,SELECT 操作的不可重復讀問題通過 MVCC 得到了解決,而 UPDATE、DELETE 的不可重復讀問題通過 Record Lock 解決,INSERT 的不可重復讀問題是通過 Next-Key Lock(Record Lock + Gap Lock)解決的,
鎖問題
臟讀
臟讀指的是不同事務下,當前事務可以讀取到另外事務未提交的資料,
例如:
T1 修改一個資料,T2 隨后讀取這個資料,如果 T1 撤銷了這次修改,那么 T2 讀取的資料是臟資料,

不可重復讀
不可重復讀指的是同一事務內多次讀取同一資料集合,讀取到的資料是不一樣的情況,
例如:
T2 讀取一個資料,T1 對該資料做了修改,如果 T2 再次讀取這個資料,此時讀取的結果和第一次讀取的結果不同,

在 InnoDB 存盤引擎中,SELECT 操作的不可重復讀問題通過 MVCC 得到了解決,而 UPDATE、DELETE 的不可重復讀問題是通過 Record Lock 解決的,INSERT 的不可重復讀問題是通過 Next-Key Lock(Record Lock + Gap Lock)解決的,
Phantom Proble(幻影讀)
The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.
Phantom Proble 是指在同一事務下,連續執行兩次同樣的 sql 陳述句可能回傳不同的結果,第二次的 sql 陳述句可能會回傳之前不存在的行,
幻影讀是一種特殊的不可重復讀問題,
丟失更新
一個事務的更新操作會被另一個事務的更新操作所覆寫,
例如:
T1 和 T2 兩個事務都對一個資料進行修改,T1 先修改,T2 隨后修改,T2 的修改覆寫了 T1 的修改,

這型別問題可以通過給 SELECT 操作加上排他鎖來解決,不過這可能會引入性能問題,具體使用要視業務場景而定,
分庫分表資料切分
水平切分
水平切分又稱為 Sharding,它是將同一個表中的記錄拆分到多個結構相同的表中,
當一個表的資料不斷增多時,Sharding 是必然的選擇,它可以將資料分布到集群的不同節點上,從而快取單個資料庫的壓力,

垂直切分
垂直切分是將一張表按列分成多個表,通常是按照列的關系密集程度進行切分,也可以利用垂直氣氛將經常被使用的列喝不經常被使用的列切分到不同的表中,
在資料庫的層面使用垂直切分將按資料庫中表的密集程度部署到不通的庫中,例如將原來電商資料部署庫垂直切分稱商品資料庫、用戶資料庫等,

Sharding 策略
哈希取模:hash(key)%N 范圍:可以是 ID 范圍也可以是時間范圍 映射表:使用單獨的一個資料庫來存盤映射關系
Sharding 存在的問題
事務問題
使用分布式事務來解決,比如 XA 介面
連接
可以將原來的連接分解成多個單表查詢,然后在用戶程式中進行連接,
唯一性
使用全域唯一 ID (GUID) 為每個分片指定一個 ID 范圍 分布式 ID 生成器(如 Twitter 的 Snowflake 演算法)
復制
主從復制
主要涉及三個執行緒:binlog 執行緒、I/O 執行緒和 SQL 執行緒,
binlog 執行緒 :負責將主服務器上的資料更改寫入二進制日志(Binary log)中, I/O 執行緒 :負責從主服務器上讀取- 二進制日志,并寫入從服務器的中繼日志(Relay log), SQL 執行緒 :負責讀取中繼日志,決議出主服務器已經執行的資料更改并在從服務器中重放(Replay),

讀寫分離
主服務器處理寫操作以及實時性要求比較高的讀操作,而從服務器處理讀操作,
讀寫分離能提高性能的原因在于:
主從服務器負責各自的讀和寫,極大程度緩解了鎖的爭用; 從服務器可以使用 MyISAM,提升查詢性能以及節約系統開銷; 增加冗余,提高可用性,
讀寫分離常用代理方式來實作,代理服務器接收應用層傳來的讀寫請求,然后決定轉發到哪個服務器,

JSON
在實際業務中經常會使用到 JSON 資料型別,在查詢程序中主要有兩種使用需求:
在 where 條件中有通過 json 中的某個欄位去過濾回傳結果的需求 查詢 json 欄位中的部分欄位作為回傳結果(減少記憶體占用)
JSON_CONTAINS
JSON_CONTAINS(target, candidate[, path])
如果在 json 欄位 target 指定的位置 path,找到了目標值 condidate,回傳 1,否則回傳 0
如果只是檢查在指定的路徑是否存在資料,使用JSON_CONTAINS_PATH()
mysql> SET @j = '{"a": 1, "b": 2, "c": {"d": 4}}';
mysql> SET @j2 = '1';
mysql> SELECT JSON_CONTAINS(@j, @j2, '$.a');
+-------------------------------+
| JSON_CONTAINS(@j, @j2, '$.a') |
+-------------------------------+
| 1 |
+-------------------------------+
mysql> SELECT JSON_CONTAINS(@j, @j2, '$.b');
+-------------------------------+
| JSON_CONTAINS(@j, @j2, '$.b') |
+-------------------------------+
| 0 |
+-------------------------------+
mysql> SET @j2 = '{"d": 4}';
mysql> SELECT JSON_CONTAINS(@j, @j2, '$.a');
+-------------------------------+
| JSON_CONTAINS(@j, @j2, '$.a') |
+-------------------------------+
| 0 |
+-------------------------------+
mysql> SELECT JSON_CONTAINS(@j, @j2, '$.c');
+-------------------------------+
| JSON_CONTAINS(@j, @j2, '$.c') |
+-------------------------------+
| 1 |
+-------------------------------+
JSON_CONTAINS_PATH
JSON_CONTAINS_PATH(json_doc, one_or_all, path[, path] ...)
如果在指定的路徑存在資料回傳 1,否則回傳 0
mysql> SET @j = '{"a": 1, "b": 2, "c": {"d": 4}}';
mysql> SELECT JSON_CONTAINS_PATH(@j, 'one', '$.a', '$.e');
+---------------------------------------------+
| JSON_CONTAINS_PATH(@j, 'one', '$.a', '$.e') |
+---------------------------------------------+
| 1 |
+---------------------------------------------+
mysql> SELECT JSON_CONTAINS_PATH(@j, 'all', '$.a', '$.e');
+---------------------------------------------+
| JSON_CONTAINS_PATH(@j, 'all', '$.a', '$.e') |
+---------------------------------------------+
| 0 |
+---------------------------------------------+
mysql> SELECT JSON_CONTAINS_PATH(@j, 'one', '$.c.d');
+----------------------------------------+
| JSON_CONTAINS_PATH(@j, 'one', '$.c.d') |
+----------------------------------------+
| 1 |
+----------------------------------------+
mysql> SELECT JSON_CONTAINS_PATH(@j, 'one', '$.a.d');
+----------------------------------------+
| JSON_CONTAINS_PATH(@j, 'one', '$.a.d') |
+----------------------------------------+
| 0 |
+----------------------------------------+
實際使用:
$conds = new Criteria();
$conds->andWhere('dept_code', 'in', $deptCodes);
if (!empty($aoiAreaId)) {
$aoiAreaIdCond = new Criteria();
$aoiAreaIdCond->orWhere("JSON_CONTAINS_PATH(new_aoi_area_ids,'one', '$.\"$aoiAreaId\"')", '=', 1);
$aoiAreaIdCond->orWhere("JSON_CONTAINS_PATH(old_aoi_area_ids,'one', '$.\"$aoiAreaId\"')", '=', 1);
$conds->andWhere($aoiAreaIdCond);
}
column->path、column->>path
獲取指定路徑的值
-> vs ->>
Whereas the -> operator simply extracts a value, the ->> operator in addition unquotes the extracted result.
mysql> SELECT * FROM jemp WHERE g > 2;
+-------------------------------+------+
| c | g |
+-------------------------------+------+
| {"id": "3", "name": "Barney"} | 3 |
| {"id": "4", "name": "Betty"} | 4 |
+-------------------------------+------+
2 rows in set (0.01 sec)
mysql> SELECT c->'$.name' AS name
-> FROM jemp WHERE g > 2;
+----------+
| name |
+----------+
| "Barney" |
| "Betty" |
+----------+
2 rows in set (0.00 sec)
mysql> SELECT JSON_UNQUOTE(c->'$.name') AS name
-> FROM jemp WHERE g > 2;
+--------+
| name |
+--------+
| Barney |
| Betty |
+--------+
2 rows in set (0.00 sec)
mysql> SELECT c->>'$.name' AS name
-> FROM jemp WHERE g > 2;
+--------+
| name |
+--------+
| Barney |
| Betty |
+--------+
2 rows in set (0.00 sec)
實際使用:
$retTask = AoiAreaTaskOrm::findRows(['status', 'extra_info->>"$.new_aoi_area_infos" as new_aoi_area_infos', 'extra_info->>"$.old_aoi_area_infos" as old_aoi_area_infos'], $cond);
關系資料庫設計理論
函式依賴
記 A->B 表示 A 函式決定 B,也可以說 B 函式依賴于 A,
如果 {A1,A2,... ,An} 是關系的一個或多個屬性的集合,該集合函式決定了關系的其它所有屬性并且是最小的,那么該集合就稱為鍵碼,
對于 A->B,如果能找到 A 的真子集 A',使得 A'-> B,那么 A->B 就是部分函式依賴,否則就是完全函式依賴,
對于 A->B,B->C,則 A->C 是一個傳遞函式依賴
例外
以下的學生課程關系的函式依賴為 {Sno, Cname} -> {Sname, Sdept, Mname, Grade},鍵碼為 {Sno, Cname},也就是說,確定學生和課程之后,就能確定其它資訊,
| Sno | Sname | Sdept | Mname | Cname | Grade |
|---|---|---|---|---|---|
| 1 | 學生-1 | 學院-1 | 院長-1 | 課程-1 | 90 |
| 2 | 學生-2 | 學院-2 | 院長-2 | 課程-2 | 80 |
| 2 | 學生-2 | 學院-2 | 院長-2 | 課程-1 | 100 |
| 3 | 學生-3 | 學院-2 | 院長-2 | 課程-2 | 95 |
不符合范式的關系,會產生很多例外,主要有以下四種例外:
冗余資料:例如 學生-2出現了兩次,修改例外:修改了一個記錄中的資訊,但是另一個記錄中相同的資訊卻沒有被修改, 洗掉例外:洗掉一個資訊,那么也會丟失其它資訊,例如洗掉了 課程-1需要洗掉第一行和第三行,那么學生-1的資訊就會丟失,插入例外:例如想要插入一個學生的資訊,如果這個學生還沒選課,那么就無法插入,
范式
范式理論是為了解決以上提到四種例外,
高級別范式的依賴于低級別的范式,1NF 是最低級別的范式,
第一范式 (1NF)
屬性不可分,
第二范式 (2NF)
每個非主屬性完全函式依賴于鍵碼,
可以通過分解來滿足,
分解前
| Sno | Sname | Sdept | Mname | Cname | Grade |
|---|---|---|---|---|---|
| 1 | 學生-1 | 學院-1 | 院長-1 | 課程-1 | 90 |
| 2 | 學生-2 | 學院-2 | 院長-2 | 課程-2 | 80 |
| 2 | 學生-2 | 學院-2 | 院長-2 | 課程-1 | 100 |
| 3 | 學生-3 | 學院-2 | 院長-2 | 課程-2 | 95 |
以上學生課程關系中,{Sno, Cname} 為鍵碼,有如下函式依賴:
Sno -> Sname, Sdept Sdept -> Mname Sno, Cname-> Grade
Grade 完全函式依賴于鍵碼,它沒有任何冗余資料,每個學生的每門課都有特定的成績,
Sname, Sdept 和 Mname 都部分依賴于鍵碼,當一個學生選修了多門課時,這些資料就會出現多次,造成大量冗余資料,
分解后
關系-1
| Sno | Sname | Sdept | Mname |
|---|---|---|---|
| 1 | 學生-1 | 學院-1 | 院長-1 |
| 2 | 學生-2 | 學院-2 | 院長-2 |
| 3 | 學生-3 | 學院-2 | 院長-2 |
有以下函式依賴:
Sno -> Sname, Sdept Sdept -> Mname
關系-2
| Sno | Cname | Grade |
|---|---|---|
| 1 | 課程-1 | 90 |
| 2 | 課程-2 | 80 |
| 2 | 課程-1 | 100 |
| 3 | 課程-2 | 95 |
有以下函式依賴:
Sno, Cname -> Grade
第三范式 (3NF)
非主屬性不傳遞函式依賴于鍵碼,
上面的 關系-1 中存在以下傳遞函式依賴:
Sno -> Sdept -> Mname
可以進行以下分解:
關系-11
| Sno | Sname | Sdept |
|---|---|---|
| 1 | 學生-1 | 學院-1 |
| 2 | 學生-2 | 學院-2 |
| 3 | 學生-3 | 學院-2 |
關系-12
| Sdept | Mname |
|---|---|
| 學院-1 | 院長-1 |
| 學院-2 | 院長-2 |
ER 圖
Entity-Relationship,有三個組成部分:物體、屬性、聯系,
用來進行關系型資料庫系統的概念設計,
物體的三種聯系
包含一對一,一對多,多對多三種,
如果 A 到 B 是一對多關系,那么畫個帶箭頭的線段指向 B; 如果是一對一,畫兩個帶箭頭的線段; 如果是多對多,畫兩個不帶箭頭的線段,
下圖的 Course 和 Student 是一對多的關系,

表示出現多次的關系
一個物體在聯系出現幾次,就要用幾條線連接,
下圖表示一個課程的先修關系,先修關系出現兩個 Course 物體,第一個是先修課程,后一個是后修課程,因此需要用兩條線來表示這種關系,

聯系的多向性
雖然老師可以開設多門課,并且可以教授多名學生,但是對于特定的學生和課程,只有一個老師教授,這就構成了一個三元聯系,

表示子類
用一個三角形和兩條線來連接類和子類,與子類有關的屬性和聯系都連到子類上,而與父類和子類都有關的連到父類上,

參考資料
姜承堯. MySQL 技術內幕: InnoDB 存盤引擎 [M]. 機械工業出版社, 2011. CS-Notes-MySQL B+ tree 紅黑樹、B(+)樹、跳表、AVL等資料結構,應用場景及分析,以及一些英文縮寫 B樹、B+樹、紅黑樹、AVL樹比較 8.8.2 EXPLAIN Output Format 最官方的 mysql explain type 欄位解讀 12.18.3 Functions That Search JSON Values
總結
這都是些基礎知識,我沒想到再次回顧大半我都已忘卻了,也慶幸有這樣的假期能夠重新拾起來,
說實話做自媒體后我充電的時間少了很多,也少了很多時間研究技術堆疊深度,國慶假期我也思考反思了很久,后面準備繼續壓縮自己業余時間,比如看手機看B站的時間壓縮一下,還是得按時充電,目前作息還算規律早睡早起都做到了,我們一起加油喲,
我是敖丙,你知道的越多,你不知道的越多,感謝各位人才的:點贊、收藏和評論,我們下期見!
文章持續更新,可以微信搜一搜「 三太子敖丙 」第一時間閱讀,回復【資料】有我準備的一線大廠面試資料和簡歷模板,本文 GitHub https://github.com/JavaFamily 已經收錄,有大廠面試完整考點,歡迎Star,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/172117.html
標籤:Java
上一篇:Java 合并Word檔案
