切換語言為:簡體

開發易忽視的問題:InnoDB 行鎖設計與實現

  • 爱糖宝
  • 2024-09-08
  • 2055
  • 0
  • 0

InnoDB 的行鎖是透過其儲存模型和鎖機制來實現的。下面是有關其具體實現和儲存結構的深入分析:

儲存結構

  1. 資料頁

    • InnoDB 將表的資料儲存在資料頁中,每個頁預設大小為 16KB。

    • 資料頁中儲存多個行記錄,行記錄按照主鍵順序存放。

  2. 行格式

    • InnoDB 支援多種行格式,包括 Compact、Redundant、Dynamic 和 Compressed。這些格式影響資料的儲存方式,但對行鎖機制影響不大。

  3. 索引組織表

    • InnoDB 是一種索引組織表(Index-Organized Table),即資料按主鍵聚集儲存,這意味著行資料實際上儲存在 B+ 樹的葉子節點上。

    • 非主鍵索引以主鍵作為葉子節點指向的資料行的指標。

行鎖實現

  1. 基於索引的鎖定

    • 行級鎖是基於索引實現的,具體來說,InnoDB 鎖定的是索引上的記錄,而不是實際的資料行。

    • 當事務要鎖定一行時,它實際上是在鎖定該行所在索引的一個“間隙”或確切的索引鍵。

  2. 鎖的型別

    • Record Lock:鎖定單個索引記錄。

    • Gap Lock:鎖定索引記錄之間的間隙,用於防止幻影讀,通常用於範圍查詢。

    • Next-Key Lock:結合了 Record Lock 和 Gap Lock,在索引記錄及其前面的間隙上加鎖。這樣可以防止其他事務插入新的索引記錄。

  3. 鎖的儲存

    • 鎖資訊並不是直接儲存在行本身,而是儲存在記憶體中的鎖表中。

    • 鎖表包含有關每個鎖的資訊,如鎖的型別、被鎖定的事務等。

  4. 壓縮行鎖

    • 爲了減少鎖的開銷和提高效率,InnoDB 使用了一種稱為鎖壓縮(Lock Compression)的技術,使得對於連續範圍內的鎖,僅需要維護較少數量的鎖物件。

  5. 意向鎖

    • 在表級別實現的一種鎖,它表示某個事務想要在行級別獲得鎖。這種設計使得 InnoDB 能夠快速判斷是否可以安全地對整個表進行鎖定。

實際操作

  • 鎖定機制依賴於索引,因此合理設計索引可以有效地利用行級鎖。

  • 當沒有適當的索引來支援查詢時,InnoDB 可能會退化到鎖定更多的行甚至整個表。

鎖表分析

InnoDB 的鎖表是用來管理和維護所有活動事務的鎖資訊的關鍵元件。鎖表並不是一個物理儲存的表,而是一種記憶體數據結構,其設計與實現旨在高效地處理併發事務,確保資料一致性和完整性。

鎖表的設計和實現

  1. 鎖結構

    • 每個鎖都有一個鎖結構,用於表示特定事務對某個資源(比如行或間隙)的鎖定。

    • 鎖結構包含的資訊包括:被鎖定的資源、鎖型別(如共享鎖、排他鎖)、擁有鎖的事務ID等。

  2. 鎖的儲存

    • 鎖資訊儲存在記憶體中,具體來說,是透過一個全域性的雜湊表來維護所有當前活動的鎖。

    • 這個雜湊表以被鎖定的資源為鍵,以相應的鎖結構連結串列為值。這樣可以快速查詢某個資源上的鎖。

  3. 鎖型別

    • InnoDB 支援多種型別的鎖,如前面提到的 Record Lock、Gap Lock 和 Next-Key Lock。每種鎖型別在鎖表中都有不同的表現形式和處理邏輯。

    • 鎖的型別決定了如何對其他事務進行阻塞或允許併發訪問。

  4. 鎖相容性

    • 鎖表需要處理鎖的相容性問題,即檢測新請求的鎖是否與現有鎖衝突。共享鎖與共享鎖相容,但排他鎖則與其他任何鎖衝突。

    • 透過鎖相容矩陣,InnoDB 可以有效地決定是否授予新的鎖請求。

  5. 死鎖檢測

    • InnoDB 實現了自動死鎖檢測機制,透過分析鎖表中的鎖依賴圖來判斷是否存在死鎖迴圈

    • 一旦發現死鎖,InnoDB 會主動回滾其中一個事務,以解除死鎖狀態。

  6. 鎖等待佇列

    • 對於因鎖衝突而無法立即獲得的鎖請求,InnoDB 將其放入鎖等待佇列中。

    • 當鎖釋放時,InnoDB 會檢查鎖等待佇列,並嘗試授予佇列中合適的鎖請求。

  7. 效能最佳化

    • 爲了減少鎖開銷,InnoDB 使用了一些最佳化技術,比如鎖壓縮。這種技術在可能的情況下將多個相鄰範圍的鎖合併成一個,從而減少鎖物件的數量。

案例分析

假設我們有一個名為 employees 的表,其中包含如下欄位:id(主鍵),namesalary。該表的資料如下:

id name salary
1 Alice 5000
2 Bob 6000
3 Carol 7000

現在我們有兩個事務對這個表進行操作:

  • 事務A:將 Bob 的薪水增加 1000。

  • 事務B:嘗試讀取 Bob 和 Carol 的資訊。

操作步驟及鎖機制

  1. 事務A開始

    • 執行 START TRANSACTION;

    • 執行 UPDATE employees SET salary = salary + 1000 WHERE name = 'Bob';

    • 假如name欄位沒有新增索引,InnoDB 使用主鍵索引進行全表掃描,來定位 Bob 的行並獲取行級鎖(Record Lock)在 id=2 上,因為這是精確的行更新。

  2. 事務B開始

    • 執行 START TRANSACTION;

    • 執行 SELECT * FROM employees WHERE name IN ('Bob', 'Carol') FOR UPDATE;

    • 因為事務A已經持有了 id=2 的排他鎖,事務B將在此處被阻塞,等待事務A釋放對 Bob 的鎖。

  3. 鎖表儲存與管理

    • 當事務A執行更新時,InnoDB 會在記憶體中的鎖表中建立一個記錄鎖條目,標記該行(id=2)已被鎖定,並且事務A擁有一個排他鎖。

    • 鎖表的雜湊結構會以 id=2 為鍵,指向一個連結串列,該連結串列包含事務A的鎖資訊。

    • 當事務B嘗試獲取鎖時,會檢查鎖表,發現衝突,因此會將其請求放入等待佇列中,關聯到相同的雜湊鍵(即 id=2)。

  4. 事務A提交或回滾

    • 事務A完成後,執行 COMMIT; 或 ROLLBACK;

    • InnoDB 釋放 id=2 的排他鎖,從鎖表中移除相應的鎖條目。

    • 此時,事務B會從等待佇列中喚醒,重新嘗試獲取鎖,現在它能夠獲取到 id=2 的共享鎖或排他鎖(視具體操作而定)。

  5. 事務B繼續

    • 事務B成功獲得鎖後,可以讀取 Bob 和 Carol 的資訊。

    • 完成後執行 COMMIT; 釋放所有鎖。

結論

透過這個案例,我們可以看到 InnoDB 如何使用行級鎖和鎖表來管理併發訪問。鎖表是一箇中間儲存,用於跟蹤當前所有活動事務的鎖狀態

思考題:查詢條件沒有索引,一定會進行表鎖嘛

如果在執行 SELECT * FROM employees WHERE name = 'Alice' FOR UPDATE; 時,name 欄位上沒有索引,那麼 InnoDB 會執行全表掃描來查詢滿足條件的記錄。這時,InnoDB 的鎖機制會有所不同:

  1. 全表掃描:由於缺少索引,InnoDB 必須掃描整張表來找到所有符合條件的行。

  2. 行鎖應用:即使是在全表掃描的情況下,InnoDB 仍會嘗試對每一行進行行級鎖(Record Lock)。不過,由於需要檢查每一行,效率會降低。

  3. 鎖定效能影響:在沒有索引的情況下,鎖定的範圍可能會更廣泛,因為必須訪問每個資料頁和其中的每一行來判斷是否符合條件。這可能導致更多的鎖競爭和降低併發效能。

  4. 最佳化建議

    • 為避免這種低效的操作,建議為常用的查詢條件列新增適當的索引。

    • 新增索引不僅可以提高查詢的效能,還能使鎖更精確,從而提高事務的併發處理能力。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.