tags: database

段考速記

架構

請求層 (Query Interface)

存儲層 (Storage Interface)

RecordFile 和存儲單位

  • Block: OS 讀寫的最小單位
  • Page: 將資料從檔案載入記憶體的記憶體最小單位

Metadata

  • 以 Catalog table 儲存

伺服器與執行緒

Thread or Process

伺服器管理的最小可執行單元可能為 thread 或 process,各有優缺,考量兩者的差異:

  • Open files, heap mem
  • Process 內至少一個 thread

通常使用 Thread,這是因為管理 global/share resource (open file, heap mem) 交由 DBMS 來處理較方便、高效,且 thread 的建置較 Process 快。

Kernel & user thread

Thread mapping 模型的選擇

  • 多對一
    • 簡單
    • 一個 blocking system call 就大家一起等
  • 一對一
    • 無 blocking problem
    • 建置 User thread 可能成本高
    • 可善用 thread pool 解決每次 user thread 請求就建 kernel thread 的必要
    • 當今個人電腦的 OS 常用
  • 多對多
    • 善用 kernel thread 資源
    • Mapping 複雜
    • 當今大型電腦 OS 可能會用
    • 負荷過大時效率可能會降級至一對一 (加上 mapping 會更慘)

處理的請求步驟

收到 SQL query 時

Parsing (syntax-based)

  • 根據語法,是否合乎 SQL 的文法
  • 步驟
    • 用 lexical analyzer (詞彙分析器) 將 SQL tokenize
    • 根據文法分類內容
    • 用 parsing algo 建構 parse tree
      • algo 分為遞迴與否

Verification (semantic-based)

根據語意,語意是否合理

Plan

  • 以定義好的運算子規劃如何完成請求
  • 實際 Scan 前先預估某運算決策的效率,選擇最優決策
    • 根據輸出 record 數與 value histogram
  • 由於都訪問 metadata,都暫存於記憶體而沒有 I/O,故 Plan 實際上比起 Scan 很快

Scan

  • 完成請求
  • 分為兩種模式
    • Pipelined
      • 容易實作
      • 部分情況低效
        • 如 Sort 時,每尋找一個元素需走遍所有子節點
    • Materialized
      • 預先將經過運算後的表格存入暫存檔,需要時取用
      • 額外時間空間消耗
      • 但可使用 External Sorting 等功能

存儲層

三種資料

  • File: table info, record file
  • Metadata
  • Index

資料庫的 ACID 分別由 concurrency manager 和 recovery manager 實現。

硬碟三時間

  • seek (沿直徑方向移動資料臂)
  • rotational (平均轉半圈)
  • transfer (根據資料量而不同)

OS 提供的資料訪問方法

Block level

  • Sector 是資料最原始的單位
  • OS 將 Sector 的概念隱藏於 Block
  • Block size 由 OS 決定
  • 使用時將 Block 映射到 Memory Page

File level

  • OS 將 Block 的概念隱藏
  • 一個檔案為一系列 logical block 組成
  • 管理這些 logical block 的 schema 多多,比如 contigous, linked, indexed, multi-level
  • 將 Page 的概念隱藏為 I/O Buffer

File v.s. block

  • Block 難實作,File 簡單方便
  • 可控程度
    • Block 可根據資料庫的使用需求客製化要緩存的資料
    • File 對 page replacement 難以控制
    • 比如某些 Query 可能會在一段時間後訪問 File 之前訪問過的 Page,OS 可能會因其他因素將其 swap 掉,但 DBMS 可預測未來存取資料的行為,選擇性的將其留在記憶體更久一點
  • Block 無 OS 的限制,比如限制檔案大小
  • 面對 OS 提供的 Block 層服務大多與 OS 本身掛鉤,影響移植性
  • 根據 File System 的實作可能影響正確性 (e.g. WAL),因為 File System 沒有義務實現 ACID

最後 Vanilla DB 使用妥協策略:在 File Level 下直接訪問 logical block

  • 優點
    • 簡單
    • Block 內的資料存放邏輯可客製化
    • Flush 與否自己控制,滿足 ACID
    • 需注意不要讓 OS 主動幫我們管理緩存
  • 缺點
    • 只能操縱 logical block 層級
    • 即使將資料循序放也無法確保 disk 能循序放資料

記憶體管理

記憶體的使用有

  • 空間局部性 (Spatial locality)
  • 時序局部性 (Temporal locality)

對於 DBMS,不應該依賴 VM,swap 與否應自行管理。其好在

  • 可管理 swap
  • meta-write for logs

另外,Log 不需用 buffer pool,因為其只會恆定的使用一個 buffer,且永遠只有 append 和反向讀取兩種操作。

使用 Buffer pool 減少 I/O

Vanilla DB 將 page 以 buffer 包裝,buffer 由 buffer manager 管理。

使用 Block 時透過 pin 一個 page buffer,pin 有以下幾種可能:

  • Hit
  • 有剩餘 buffer 就拿來用
  • 沒有且存在 unpinned 的 buffer,用 replacement algo 找候選 buffer
    • 需 flush dirty page
    • 商用可能會為 buffer 分類以幫助 replacement algo
  • 所有 buffer 都被 pin,等待至有 buffer

注意到 buffer 和 page 都應該是 thread safe 的。

Replacement algo

常見算法

  • linear search (naïve)
  • FIFO
  • LRU: 換掉最久沒用的
  • Clock: 從上次掃描到的地方掃描,取第一個沒在用的

也可採用混合算法,比如 Oracle DBMS 使用 cold 時 LRU、hot 時 FIFO。

Deadlock

  • 遇死結時放所有 pinned 後回去睡,之後一個個重新 pin
  • Self-Deadlock 沒救

資料管理

Table records 於 file 中的存法

  • homogeneous: 整個 table 的所有 record 都放同個 file
    • 單表格的請求很高效
  • heterogeneous: table 放在不同檔案
    • 可進行 cluster,適合 schema 有階層關係的情況
    • 沒有 cluster 的請求可能變慢

Spanned v.s. unspanned records

  • Spanned: 如果某筆資料塞不下當前 block,將該筆資料切分放在不同 block
  • 優點
    • 硬碟空間不浪費
    • block size 不會限制 record size
  • 缺點
    • 讀一筆資料可能需要讀不只一個 block

Field 索引方法

  • Variable length: record 長度可變
    • 最善用空間
    • 如果長度超過當前 block 大小,會移至 overflow block
  • Indexed: 資料長度可變,以指標指向資料本體
    • 頗善用空間,不過指標佔部分額外空間
    • 訪問資料要訪問兩次
    • 適合 text 這種肥大且不定長的資料
  • Fixed length: 配置空間固定大小
    • Random access 的實作簡單
    • 內部碎裂嚴重,浪費空間

Column/row oriented store

要以 column 還是 row 為基礎存資料?

  • Column
    • 對同 column 讀取較快
    • 加速同 column 的運算 (比如 group by 和一些 aggregation fn)
    • 比較速度較快
    • 適合 OLAP (線上分析處理)
  • Row
    • 加速各筆資料的訪問速度
    • 對寫資料友善
    • 適合 OLTP (線上交易)

綜合判斷

  • OLTP 情境常用
    • Homogeneous files
    • Unspanned record
    • Fixed length record
    • row oriented store
  • Page layout
  • 考量刪除 record 的情境
    • 需額外資料,比如一個 bit 決定一筆資料是否被刪掉
    • free space 高速查找的實作
      • chaining
      • meta page
      • meta file

並行管理

確保

  • C (consistency)
    • 資料庫的資料符合 integrity constraints,也就是一些限制
    • 資料庫與使用者共同維護
  • I (isolation)
    • 對每筆交易都像是只有那個使用者在使用系統一樣

對於一次交易,其生命週期為:

  • Begin
  • Statement ends (如果有多個 statement)
  • End
    • Commit
    • Rollback

交易排程

並行交易希望能達到

  • Serializable schedule: 同時有 \(n\) 次交易,最終結果為該 \(n\) 次交易的某個排列組合
  • Recoverable schedule: 當一筆交易 abort 不影響其他筆交易;一筆交易 committed 後不應該被牽連而失去該筆交易

Anomalies

  • 發生資料衝突 (Conflict) (R: read, W: write)
  • WR conflict 一個先寫另一個後讀
    • Dirty reads: 讀還沒 committed 的資料
    • Unrecoverable schedule 或 Cascading aborts
  • RW conflict 一個先讀另一個後寫
    • Unrepeatable reads: 讀兩次讀的東西不同
  • WW conflict 一個先寫另一個後寫
    • Lost update: 先寫的會失去

解決資料衝突: Locking protocol

Two-phase locking (2PL)

  • 鎖種類
    • shared lock (R lock)
    • exclusive lock (W lock)
  • 階段
    1. Growing phase: 只取鎖
    2. Shrinking phase: 只放鎖

鎖以 Lock table (像是 hash table) 的方式管理。

2PL 解決序列化問題,但可能發生

  • Cascading rollback
  • Deadlock
  • Starvation (根據不良的 waiting 隊列規則可能發生)

S2PL (S: strict)

與 2PL 唯一差別: 等到一個交易結束時一次將所有鎖放掉,其他時候不放鎖,等同於 shrinking phase 只有一瞬間。

Strict 保證不會發生 cascading abort。

死結處理

  • Detection: timeout + rollback
  • Prevention: Wait die,放棄執行時間較短者
  • Conservative lock: S2PL 之上,連 growing phase 也只有一瞬間
    • 需知道 r/w working set
    • 適合 stored procedure
    • 不適合 ad-hoc (隨當前資料情況決定交易內容)

鎖的顆粒度 (Granularity) 與 Multiple-granularity locking (MGL)

可以鎖的物件有階層關係。

以此為基礎的鎖: MGL,基於 2PL 鎖的種類做出調整 (多了 intention lock 意向鎖): 鎖某物件時,連同其上層的所有親代物件都使用對應的意向鎖鎖起來。

SIX 的白話文: 為 Share ( R ) 鎖,不過讀的下層存在 X 鎖。

上解鎖的順序有講究:

  • 上鎖由根往葉
    • 若改為由葉往根,上鎖時如果上層被鎖就無法繼續往下鎖了。
  • 解鎖由葉往根
    • 若給為由根往葉,根一解鎖馬上被鎖,可能在葉的鎖還沒釋放的情況下就被拿上層的鎖。

實時動態資料庫的 Phantoms

資料可能實時 insert, update 和 delete,有機會發生讀兩次資料不同的情況。

兩招解法:

  • EOF lock 或 MGL
    • 如果使用 MGL 實作 phantom 避免,要去 X lock 目標上層 (高一層 Granularity) 的物件,比如要寫 block 的話去鎖 file
    • 常拿去避免 insert phantom
    • 但不會想去避免 update phantom,因為太損效能
  • Index/predicate lock

另外,Conservative lock 依然可能遇到 phantom,要看鎖了什麼東西。

Isolation model

  • Access model: 告知 read only/rw
  • Isolation level: 可放棄 serializable 的部分性質換取效能
    上表為使用者角度,下表為實作角度。

實務上考慮 Meta structure

由於 insert 和 delete 都需要先調整 meta structure 後調整 file,如果直接把 meta structure 鎖起來,insert 和 delete 就沒有並行性了。

要避免此瓶頸,可以不用完全遵照 S2PL,而是先 lock meta 後 lock file,在 lock 到 file 後可以放掉 meta 的 lock。

對 insert/delete 來說,資料不會錯誤是因為改 meta file 完對其他交易而言,相當於已經完成 insert/delete (只是還沒真的寫上去,但看起來是)。

資料復原

可能需要復原的時機:

  • 以交易為單位
    • logical: 資料不存在、overflow、輸入錯誤等
    • System: deadlock
  • 以系統為單位
    • 軟硬體 bug

Rollback 成功的依據:Write-Ahead-Logging (WAL),在所有寫資料操作前先寫一 log,commit/abort 操作完成後再寫一個 log。

相當於搞一個寫進檔案版本的 CS

Physical logging (段考範圍末)

Log 內容有以下幾種:

  • start
  • commit
  • rollback
  • update
  • checkpoint

Rollback 操作從 log 末尾開始蒐集

且需確保在 flushing 的 buffer 不會被修改,修改中的 buffer 也不該被 flush,可由 latch 保證。

至此我們有 rollback undo 的演算法如下。

一個例子:

Undo-redo recovery

每完成一筆交易都需要 flush dirty buffer 至 disk 的效率實在不怎麼樣。透過紀錄修改新舊值訊息的 log,我們可以延緩 flush 的時機,如果在還沒 flush 之前系統出事,可透過 log 把還沒 flush 進 disk 的資料都 redo 一遍。

概念澄清

  • Force 與否
    • Force: committing 時,當 buffer 還沒 flush 完之前不會 return 至 user
    • No-force: committing 時只把 log 寫好,flush 可以晚點做
  • Steal 與否: 也就是允許 swap + flush 與否
    • Steal: commit 之前 buffer 可以 swap,期間須把中間資料 flush 回 disk
    • No steal: commit 之前 buffer 不能 swap,需保持被 pin 著,也不能 flush

有趣的想法: Force + No steal

Redo 本身是因為採用 no force 導致 commit 後寫的資料不會即時 flush 回 disk,如果 force 的話就規避了 redo 操作。

那麼 undo 本身會存在即因為 steal: flush 了中間結果而非最終結果,若採用 no steal,是否就規避了 undo 操作?

也就是 Force + no steal 就使 log 沒有必要存在了 (no redo & no undo)?

答案是錯的,此法有兩點問題。

  • No steal 不現實: buffer 在未 commit 前都需被 pin 的話並行性會大幅降低
  • Flush 時系統仍有可能出錯,沒寫 log 還是等著抽搐

Force undo-redo recovery

特別注意到這些 log rollback 操作都是 idempotent (每次做都得到一樣的結果) 的。

No-force undo-redo recovery

更進一步的,no-force 版的更激進,在 rollback 時不需要將 dirty pages flush 掉,更不需要寫入 <ROLLBACK> 進 log,因為重啟後重新 rollback 的結果都是 idempotent 的!

這帶來了另一版本的 rollback 演算法:

此時 undo journaling 一定要先於 redo! 不然 undo 的部分可能蓋掉 redo 的。

比較

undo-only 的

  • 優點 <主要反映在非服務時期>
    • recovery 時加速
    • 不需要 redo 的 "log"
  • 缺點 <主要反映在服務時期>
    • commit 和 rollback 時較慢

明顯 undo-redo 版的對服務時期比較友好,更佳適合採用。

Checkpoint

降低每次 rollback undo/redo 的成本,適合在離峰時進行。

Quiescent 版

  1. 停止接受新交易
  2. 等待現有交易結束
  3. Flush 所有 dirty buffer
  4. 新增 (Quiescent) checkpoint 至 log 並將其 flush 回 disk
  5. 恢復接受新交易

但第二步不知道要等多久

Nonquiescent 版

  1. 停止接受新交易
  2. 紀錄正在進行的交易 \(T_1 \sim T_k\)
  3. Flush 所有 dirty buffer
  4. 新增 <NOQCKPT, T1, T2, ..., Tk> 至 log 並將其 flush 回 disk
  5. 恢復接受新交易

但由於 checkpointing 時仍有交易在進行,他們可能也要寫 log,故在上述 3~4 步時需要 latch log file + all blocks (始於 3 前,終於 4 後)。

Select a repo