1. MySQL 深入總結

刨根问底_liang發表於2024-11-22
# mysql 叢集架構
1. master-slave 架構模式
高可用: master 掛了,slave 可提升為 master,對位提供服務。
2. 複製模式
非同步複製、半同步複製、全同步複製。
非同步複製:不需要等待 slave 將 binlog 日誌同步到 relay log 中,就提交事務。
半同步複製:需要等待一個 slave 將 binlog 日誌同步到 relay log 中,就提交事務。
全同步複製:需要等待所有 slave 將 binlog 日誌同步到 relay log 中,就提交事務。
3. 複製流程
3.1 從伺服器連線主伺服器:
從伺服器上的 IO 執行緒透過網路連線主動連線到主伺服器。
從伺服器向主伺服器傳送一個複製請求,該請求包含從伺服器所期望的開始位置(binlog 檔案和位置)。
3.2 主伺服器響應連線請求:
主伺服器在接收到從伺服器的複製請求後,會為該從伺服器啟動一個 binlog dump 執行緒。
binlog dump 執行緒從指定的 binlog 檔案和位置開始讀取二進位制日誌。
3.3 主伺服器傳送 binlog 資料:
binlog dump 執行緒將讀取到的二進位制日誌事件傳送給從伺服器。
從伺服器的 IO 執行緒接收這些 binlog 資料,並將其寫入從伺服器的中繼日誌(Relay Log)。
3.4 從伺服器應用 binlog 資料:
從伺服器的 SQL 執行緒從中繼日誌中讀取事件,並逐個將其應用到從伺服器的資料庫中。

總示意圖:
從伺服器:
1. IO 執行緒 --------------> 向主伺服器發起連線請求
(啟動連線)

主伺服器:
2. Binlog Dump 執行緒 -----> 響應連線請求,傳送 binlog 資料
(傳輸二進位制日誌)

從伺服器:
3. IO 執行緒 <-------------- 接收 binlog 資料,寫入中繼日誌
4. SQL 執行緒 -------------> 從中繼日誌中讀取並應用日誌
(應用變化)

總結:
儘管主伺服器的 binlog dump 執行緒中扮演了傳送日誌的角色,但這一過程的啟動和驅動是由從伺服器主動發起的。

# mysql 內部架構
1. mysql server 層 + 儲存引擎層
2. InnoDB 和 MyISAM的區別。
鎖的級別:MyISAM 採用表級鎖定,一次只能有一個寫操作。InnoDB 採用行級鎖定,這在高併發寫操作的情況下表現得較好。
事務:InnoDB 支援事務,確保資料的一致性和完整性。MyISAM 不支援事務。


# 事務
1. 定義 - what
事務(Transaction)是一個資料庫操作序列,其中的操作要麼全部成功,要麼全部失敗,保障了資料的完整性與一致性。
2. 使用場景 - why
保證業務場景的一致性。
3. 事務特性(設計原則):ACID - how
原子性(Atomicity):事務中的所有操作要麼全部完成,要麼全部不完成,即事務是一個不可分割的工作單元。
一致性(Consistency):執行事務前後,資料庫都必須保持一致狀態,不允許出現違反資料庫約束的狀態。比如外來鍵約束。
隔離性(Isolation):一個事務的執行不能被其他事務干擾,各併發事務之間相互獨立。
永續性(Durability):一旦事務提交,其結果必須永久儲存到資料庫中,即使系統發生故障也不能丟失。
4. 事務的隔離級別 - how
讀未提交(READ UNCOMMITTED):最低隔離級別,允許看到未提交的事務,這會導致“髒讀”。
讀已提交(READ COMMITTED):一個事務只能看到已經提交的事務,這會導致“不可重複讀”。
可重複讀(REPEATABLE READ):預設的隔離級別,確保一個事務看到的一致資料快照,避免“不可重複讀”。
序列化(SERIALIZABLE):最高隔離級別,確保事務完全序列化地執行,避免“幻讀”。
5. innodb 儲存引擎 各個隔離級別的實現原理 - how
RC 和 RR 隔離級別,採用的是 MVCC + 一致性讀檢視實現,提高了讀寫併發執行。 詳見:https://www.cnblogs.com/DengGao/p/mysql.html
RC 隔離級別,每次讀的時候,重新生成一份讀檢視,而 RR 隔離級別,複用第一次生成的讀檢視。

# 索引
1. 定義 - what

2. 使用場景 - why
加快查詢。

3. 分類 - how
一級索引、二級索引

4. 實現原理 - how
Innodb 儲存引擎使用的是 B+ 樹。
因為B樹不管葉子節點還是非葉子節點,都會儲存資料,這樣導致在非葉子節點中能儲存的指標數量變少,指標少的情況下要儲存大量資料,只能增加樹的高度,導致IO操作變多,查詢效能變低。


# 鎖
1. 定義 - what

2. 使用場景 - why
使用場景:防止併發,產生髒資料。

3. 分類 - how
根據鎖粒度不同,分為表鎖、行鎖。粒度越小,併發越高。
控制併發程度不同,分為共享鎖、排它鎖。排它鎖和共享鎖、排它鎖和排它鎖不可以併發執行。
行鎖的具體實現:行鎖、間隙鎖、next-key 鎖。

4. 對於 mysql innodb 儲存引擎,鎖的具體使用場景如下 - how
1. 事務裡的【寫】操作(insert、update、delete),四種隔離級別,都會加排他鎖。
2. 事務裡的【當前讀】操作,四種隔離級別,都會加鎖。加鎖如下:
select * from table where ? lock in share mode; 加共享鎖。
select * from table where ? for update; 加排他鎖。
3. 事務裡的【快照讀】操作,前三種隔離級別,不會加鎖,最後一種隔離級別,會加共享鎖。簡單的 select 操作,就是快照讀。select * from table where ?;
4. 事務裡的鎖都是同一時刻釋放的,就是事務提交或者回滾的那一刻。

5. 排它鎖和共享鎖、排它鎖和排它鎖是不可以併發執行的。

6. 在讀未提交、讀已提交和可重複度的隔離級別下,【快照讀】是不加鎖的,因此併發效能會高不少。

7. 在RC、RR隔離級別下,為了防止事務等待,我們近可能做到:
7.1 加鎖時間要儘可能短。因此使用小事務。
7.2 加鎖範圍儘可能小。因此where條件儘可能鎖定少量行。

# 死鎖
1. 死鎖產生的原因
兩個事務迴圈等待對方持有的鎖。比如事務1持有鎖A,等待鎖B;事務2持有鎖B,等待鎖A。
2. 死鎖的解決方案
2.1 預防死鎖
小事務:保持事務儘可能短,即減少事務在同一時間段內鎖定的資源數量,降低死鎖的機率。
按一致的順序訪問資源:確保所有事務按照相同的順序請求鎖定資源,避免迴圈等待。
設定合理的鎖粒度:避免一次性鎖定太多的資源,鎖的粒度要適當,不要過大。
2.2 檢測與處理死鎖
當出現死鎖,回滾其中一個事務。
2.3 等待超時。
加鎖超時後,自動回滾。

# mysql 檔案組成以及IO操作流程
https://www.cnblogs.com/DengGao/p/12734775.html
1. redo log:innodb 儲存引擎獨有。 寫日誌優先。 物理日誌。
1.1 作用:保證資料永續性、原子性、崩潰恢復。
1.2 相較於直接寫資料的方式,存在的意義:
提高效能:寫 redo log 比直接寫資料檔案要快,因為 redo log 採用順序寫入。這樣就能加速事務的提交,提升資料庫的整體效能。
2. undo log:innodb 儲存引擎獨有。undo log 並不儲存當前資料,儲存的是修改前的資料快照。邏輯日誌。
2.1 作用:回滾、用於RC/RR隔離級別的多版本併發控制,進而提高併發性。
3. bin log:mysql server 日誌。 邏輯日誌。
3.1 作用:主從複製。
3.2 兩種模式:
statement格式:SQL語句。
row格式:行內容(記兩條,更新前和更新後)。推薦。
4. innodb 為什麼採取兩階段提交。redo log 的 prepare階段 和 commit 階段。
確保兩個日誌的一致性,從而保證分散式場景下,主從一致性。
如果 redo log 直接提交,那麼事務就不能回滾了。binlog寫入失敗,會導致從庫與主庫的資料不一致。


# 總結
1. 儘可能不使用大事務的原因:
影響響應時間:大事務的執行時間較長,會影響系統的整體響應時間,增加事務等待時間,降低使用者體驗。
鎖定資源時間長:大事務需要長時間持有鎖,這可能導致併發事務等待鎖,從而引發鎖爭用和死鎖問題,影響系統的併發效能和吞吐量。
回滾代價高:如果大事務中途出現錯誤或需要回滾,這將導致大量的回滾操作,產生極大的系統開銷。這不僅影響效能,還可能導致更多的資源爭用問題。
日誌和記憶體開銷大:事務需要記錄日誌以保證資料的一致性和永續性。大事務會佔用更多的日誌空間和記憶體資源,可能導致資料庫的效能下降。

2. 一條 sql 是如何執行的。(結合 mysql 機構、索引、鎖、事務等資訊綜合闡述)
分析器(詞法分析、語法分析) -> 最佳化器(尋找適合的索引) -> 執行器(呼叫儲存引擎的介面) -> 寫undolog(在修改資料之前,記錄 undo log 以用於事務回滾。)-> 變更 buffer pool 中的資料 -> 更新 redo log (prepare 階段) -> 寫入 binlog -> 寫入 redo log (commit 階段)

3. innodb 儲存引擎的 buffer pool 是幹啥的
讀取資料時主要依賴於緩衝池(Buffer Pool),以提高讀取效能。下面詳細介紹這一過程:
3.1 檢查 Buffer Pool。
當執行一個讀取操作時,InnoDB 儲存引擎首先會檢查緩衝池(Buffer Pool)中是否已經有需要的資料頁。緩衝池是一個記憶體區域,用於快取經常使用的資料頁和索引頁。
命中緩衝池: 如果資料頁已經存在於緩衝池中,即發生緩衝池命中(Buffer Pool Hit),則直接從緩衝池中讀取該資料。這種方式最快,因為資料已經在記憶體中,避免了磁碟 I/O 操作。
未命中緩衝池: 如果資料頁不在緩衝池中,即未命中緩衝池(Buffer Pool Miss),則需要進行以下步驟:
3.2 從磁碟讀取資料頁
載入資料頁: InnoDB 將從磁碟上的資料檔案中載入所需的資料頁到緩衝池中。這涉及一次磁碟 I/O 操作,通常會比記憶體訪問慢得多。
快取到 Buffer Pool: 一旦資料頁載入進緩衝池,InnoDB 會將其快取下來,以便後續的讀取操作可以直接從緩衝池中獲取資料,減少磁碟 I/O 操作。這一機制提升了系統的整體效能。
讀取髒頁: 在一些情況下,資料頁可能在磁碟上與緩衝池中的版本不一致。特別是在存在未重新整理的髒頁(Dirty Pages)時,讀取操作總是讀取快取池中的最新版本資料,即使這些資料還沒有被寫回磁碟。
3.3 LRU 連結串列管理
緩衝池中使用類似 LRU(Least Recently Used,最近最少使用)演算法的連結串列來管理資料頁的快取策略。最近使用的資料頁會被儲存在連結串列的前端,而較少使用的頁會被移到後端。當緩衝池滿時,較少使用的資料頁將被移出,騰出空間給新載入的資料頁。
透過上面的步驟和機制,InnoDB 大幅度提高了資料讀取的效能和效率。緩衝池在這個過程中起到了關鍵作用,它不僅減少了磁碟 I/O 操作的頻率,還保證了讀取操作能夠迅速獲得所需的資料。


4. 如果查詢條件獲取的資料量過多,對資料庫效能造成影響,甚至 java 程序 oom。怎麼處理?
根據查詢條件,分場景處理。
1. 如果是 in 查詢,比如:select * from 分班記錄 where userId in ()。 那麼我們可以分批次查詢,降低每批次 in 條件裡面的元素數量。
2. 如果是 = 查詢,比如:select * from 分班記錄 where teacherId = xxx。處理方式如下:
2.1 前端展示場景,可以分頁。存在深度分頁的問題,如何解決? 解決不了,只能從業務上面解決。

深度分頁,可能是個偽需求。超過N頁,直接拒絕請求。
            業務場景,篩選條件做限制。也就是說,where 條件再增加一個欄位,建立聯合索引,刷選出足夠少的資料量。
        2.2 一個清洗資料的臨時 job。分批次按照id進行遊標查詢,滿足條件的記錄,批次更新。
2.3 查詢 pipe 庫,隔離對核心業務的影響。
2.4 mysql 分庫。
此分庫方式,資料是隨機儲存到資料庫裡面,並沒有分庫的key路由。查詢的時候,從多個資料庫聚合資料。類似分散式資料庫。
2.5 使用分散式資料庫。

5. 如果併發過多,對資料庫效能造成影響。怎麼處理?
1. mysql 分庫。
1.1 此分庫方式,資料是按照key路由到資料庫。查詢的時候,也是按照路由的key找到資料庫查詢。
1.2 限制併發。比如限流。
2. 多個 slave,讀寫分離。

6. mysql 錶行數多(比如1億),有效能問題嗎?如果有,如何解決呢
1. 存在效能問題。表是樹狀結構,數量越大,層級越多。
2. 可以採用分表的方式。

7. 有個疑問,對於二級索引,如果葉子節點一個鍵值對應多個主鍵,這多個主鍵是採用什麼資料結構儲存的呢?

相關文章