打工四年總結的數據庫知識點

國慶在家無聊,我隨手翻了一下家裏數據庫相關的書籍,這一翻我就看上癮了,由於大學比較熟悉的一些數據庫範式我竟然都忘了,懷揣着好奇心我就看了一個小國慶。php

看的過程當中我也作了一些小筆記,可能沒我以前系統文章那麼有趣,可是絕對也是乾貨十足,適合你們去回顧或者面試突擊的適合看看,也很少說先放圖。html

存儲引擎

InnoDB

InnoDB 是 MySQL 默認的事務型存儲引擎,只要在須要它不支持的特性時,才考慮使用其餘存儲引擎。node

InnoDB 採用 MVCC 來支持高併發,而且實現了四個標準隔離級別(未提交讀、提交讀、可重複讀、可串行化)。其默認級別時可重複讀(REPEATABLE READ),在可重複讀級別下,經過 MVCC + Next-Key Locking 防止幻讀。mysql

主索引時聚簇索引,在索引中保存了數據,從而避免直接讀取磁盤,所以對主鍵查詢有很高的性能。git

InnoDB 內部作了不少優化,包括從磁盤讀取數據時採用的可預測性讀,可以自動在內存中建立 hash 索引以加速讀操做的自適應哈希索引,以及可以加速插入操做的插入緩衝區等。github

InnoDB 支持真正的在線熱備份,MySQL 其餘的存儲引擎不支持在線熱備份,要獲取一致性視圖須要中止對全部表的寫入,而在讀寫混合的場景中,中止寫入可能也意味着中止讀取。面試

MyISAM

設計簡單,數據以緊密格式存儲。對於只讀數據,或者表比較小、能夠容忍修復操做,則依然可使用它。redis

提供了大量的特性,包括壓縮表、空間數據索引等。算法

不支持事務。sql

不支持行級鎖,只能對整張表加鎖,讀取時會對須要讀到的全部表加共享鎖,寫入時則對錶加排它鎖。但在表有讀取操做的同時,也能夠往表中插入新的記錄,這被稱爲併發插入(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 數據類型,在查詢過程當中主要有兩種使用需求:

  1. 在 where 條件中有經過 json 中的某個字段去過濾返回結果的需求
  2. 查詢 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 實體,第一個是先修課程,後一個是後修課程,所以須要用兩條線來表示這種關係。

聯繫的多向性

雖然老師能夠開設多門課,而且能夠教授多名學生,可是對於特定的學生和課程,只有一個老師教授,這就構成了一個三元聯繫。

表示子類

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

參考資料

總結

這都是些基礎知識,我沒想到再次回顧大半我都已忘卻了,也慶幸有這樣的假期可以從新拾起來。

說實話作自媒體後我充電的時間少了不少,也少了不少時間研究技術棧深度,國慶假期我也思考反思了好久,後面準備繼續壓縮本身業餘時間,好比看手機看B站的時間壓縮一下,仍是得按時充電,目前做息還算規律早睡早起都作到了,咱們一塊兒加油喲。

我是敖丙,你知道的越多,你不知道的越多,感謝各位人才的:點贊收藏評論,咱們下期見!

相關文章
相關標籤/搜索