InnoDB 事務/鎖/多版本分析?你瞭解多少?

目錄

• InnoDB事務

– 事務結構/功能

– XA事務/Group Commit

– mini-transaction• InnoDB鎖 – 鎖結構/類型/功能

– 鎖等待/死鎖檢測

– 自增序列鎖(autoinc lock)

– 半一致讀(semi-consistent read)

– 隱式鎖(implicit lock)

• InnoDB多版本

– ReadView

– 聚簇索引/二級索引

– 快照讀 – Index Only Scan

– RC vs RR

– Purge

• InnoDB事務/鎖/多版本總結


 InnoDB事務

– 事務結構/功能

– XA事務/Group Commit

– mini-transaction

InnoDB事務-結構

InnoDB事務-結構(cont.)

• trx_sys(全局唯一)

– mutex: critical section,控制事務的分配/提交/回滾

– max_trx_id: 當前系統最大的事務號 分配256次,寫一次文件,持久化

– trx_list: 系統當前所有活躍事務鏈表

– view_list: 系統當前所有ReadView鏈表
• trx_struct(事務對象)

– id/no: 事務號,標識事務起始/提交順序 • id用戶可見,no用戶不可見;共用trx_sys的max_trx_id進行分配 – xid: XA事務標識 – (global)read_view: 事務所屬的ReadView

– trx_locks: 事務持有的所有lock(表鎖/記錄鎖/Autoinc鎖)

– wait_lock: 事務當前正在等待的lock

InnoDB事務-功能

 快照讀

– 創建ReadView,實現RC/RR隔離級別(MVCC時分析)
• 當前讀

– 對錶/記錄加鎖

– 同一事務,所有的鎖,鏈成鏈表
• I/U/D

– 加鎖

– 記錄undo日誌/redo日誌
• 數據持久化

– 事務commit • 需要哪些操作?
• 數據回滾

– 事務rollback • 需要哪些操作?

InnoDB事務-XA事務

Why XA? – 爲了保證InnoDB redo log與MySQL binlog的一致性 – backup

XA Commit流程

– InnoDBprepare -> Binlog commit -> InnoDB commit

– Binlog作爲事務協調器 – Transaction Coordinator

– 參數 – MySQL:sync_binlog – InnoDB:innodb_flush_log_at_trx_commit

Group Commit – MariaDB/Percona 5.5.19-rel24/MySQL 5.6

InnoDB事務-mini-transaction

mini-transaction(微事務) – 定義 • mini-transaction不屬於事務;InnoDB內部使用
• 對於InnoDB內所有page的訪問(I/U/D/S),都需要mini-transaction支持
– 功能 • 訪問page,對page加latch ( 只讀訪問: S latch ;寫訪問: X latch)
• 修改page,寫redo日誌 (mtr 本地緩存 )
• page操作結束,提交mini-transaction ( 非事務提交 ) – 將redo日誌寫入log buffer – 將髒頁加入Flush List鏈表 – 釋放頁面上的 S/X latch
– 總結 • mini-transaction,保證單page操作的原子性(讀/寫單一page) • mini-transaction,保證多pages操作的原子性(索引SMO/記錄鏈出,多pages訪問的原子性)

 InnoDB鎖

– 鎖結構/類型/功能

– 鎖等待/死鎖檢測

– 自增序列鎖(autoinc lock)

– 半一致讀(semi-consistent read)

– 隱式鎖(implicit lock)

InnoDB鎖-定義

 數據庫中常用鎖類型(Lock/Latch/Mutex)

– 相同 • 都是用來鎖住一個資源
– 不同 • Lock (事務鎖)

– 實現複雜;功能多樣;可大量/長時間持有

– 支持死鎖檢測 – 用途:鎖用戶記錄/表
• Latch (頁面鎖)

– 實現相對簡單;功能相對簡單;少量/短時間持有

– 不支持死鎖檢測

– 用途:Latch page,防止訪問時頁面被修改/替換(pin)
• Mutex (臨界區鎖)

– 最爲簡單(CAS, TAS); 功能簡單;極短持有時間

– 無死鎖檢測 – 用途:保護critical section(臨界區)

InnoDB鎖-結構

 

 lock_sys(Rec lock hash)

– InnoDB的行鎖,通過hash表管理

– hash值,通過[space, page_no]計算:同一頁面在同一hash bucket中

– 思考:表鎖呢?
• lock_struct – trx_locks: 屬於同一事務的鎖鏈表

– type_mode: 加鎖模式 • 一個鎖對象,一個加鎖模式 – hash: 記錄鎖在hash表中的指針

– index: 鎖對應的索引 – rec_lock/tab_lock: 以上屬於表鎖/行鎖共用結構,此處爲不同結構

InnoDB鎖-結構(cont.)

 lock_rec_struct

– InnoDB行鎖特殊結構
– InnoDB行鎖實例,對應於一個索引頁面中的所有記錄 n_bits = page_dir_get_n_heap(page) + LOCK_PAGE_BITMAP_MARGIN; n_bytes = 1 + n_bits / 8; lock = mem_heap_alloc(trx->lock_heap, sizeof(lock_t) + n_bytes);
– 行鎖實例的最後,是n_bytes的bitmap • bitmap的下標對應於page中的heap_no(記錄唯一) • bitmap=1,heap_no記錄加鎖

InnoDB鎖-行鎖

行鎖 – 行鎖實例 • 對應 Index Page (聚簇 & 非聚簇)
– 行鎖標識 • 記錄在page中的heap_no
– heap_no • 記錄插入page,分配 • 刪除記錄重用,heap_no可重用 • heap_no與slot_no不同 • heap_no不可用來查找記錄
– 行鎖實例(右圖) • 根據查詢條件,行鎖實例bitmap 的1,3,6 bits設置爲1,對應於 heap_no 1,3,6號記錄

InnoDB鎖-行鎖開銷

 鎖開銷接近Oracle? InnoDB 宣稱自己的行鎖代價接近於 Oracle ,一條記錄用 1 bit 即可,實 際情況呢? InnoDB 的行鎖對象,管理一個 Page ,行鎖上的 1 bit ,對應 Page 中的一條記錄。一個 400 條記錄的 Page ,一個行鎖對象大小約爲 102 bytes 。
鎖一行 :
代價 爲 102 bytes/ 行 ;
鎖 400 行 :
代價 爲 102 bytes/400 = 2 bits/ 行。
• 實際情況 – 鎖一行代價巨大(如何優化,後續揭曉) – 鎖一頁代價較小

InnoDB鎖-鎖模式

 數據鎖模式

– 數據鎖:僅僅鎖住數據

LOCK_IS, LOCK_S, LOCK_IX, LOCK_X
– 意向鎖:LOCK_IS, LOCK_IX • 表級鎖;加記錄鎖前必須先加意向鎖; • 功能: 杜絕行鎖與表鎖相互等待

 非數據鎖模式

– 不鎖數據,標識數據前GAP的加鎖情況;非數據鎖與數據鎖之間不衝突
– LOCK_ORDINARY • next key鎖,同時鎖住記錄(數據),並且鎖住記錄前面的Gap
– LOCK_GAP • Gap鎖,不鎖記錄,僅僅記錄前面的Gap
– LOCK_NOT_GAP • 非Gap鎖,鎖數據,不鎖Gap
– LOCK_INSERT_INTENSION • Insert操作,若Insert的位置的下一項上已經有Gap鎖, 則獲取insert_intension鎖,等待Gap鎖釋放
– LOCK_WAIT • 當前鎖不能立即獲取,需要等待

– 非數據鎖兼容模式

InnoDB鎖-實例分析

 

InnoDB鎖-加鎖總結

InnoDB鎖-等待

InnoDB鎖-死鎖檢測

死鎖檢測 – 加鎖需要等待,則觸發死鎖檢測
– 死鎖檢測由用戶線程處理
– 構造Wait-For-Graph (WFG)
– 深度優先遍歷 (遞歸) • 後續改爲非遞歸實現(棧)
– 死鎖檢測過程中,持有kernel_mutex • 後續MySQL版本中,新增lock_sys->mutex
– 根據事務權重,選擇犧牲者 • 事務權重:undo日誌量 + 鎖數量

InnoDB鎖-分裂/合併/遷移

 鎖分裂 – 索引頁面分裂 -> 鎖分裂
• 鎖合併 – 索引頁面合併 -> 鎖合併
• 鎖遷移 – 插入記錄 • Gap鎖從插入後項遷移到新插入項
– 刪除記錄 • Gap鎖從刪除項遷移到刪除後項

InnoDB鎖-Autoinc鎖

自增序列鎖(Autoinc Lock) – 功能 • 複雜insert語句+statement binlog下,保證master-slave一致性
– 自增序列併發控制 • mutex: 簡單Insert/replace語句 • Autoinc_lock: insert into select * from 語句
– 參數設置 • innodb_autoinc_lock_mode – 0,1,2

InnoDB鎖-半一致讀

Semi-Consistent Read(半一致讀) – 目標 • 提高系統併發性能,減少鎖等待
– 方案 • 當前讀,可讀取記錄歷史版本 • 不滿足查詢條件的記錄,可提前放鎖
– 前提 • Read Committed隔離級別 • innodb_locks_unsafe_for_binlog

InnoDB鎖-隱式鎖

Implict Lock(隱式鎖)

– 目標 • 減少Insert時加鎖開銷,減少鎖內存消耗 • 降低鎖一行記錄的情況( 鎖一行代價巨大 )
– 方案 • Insert時,不加鎖(Implict lock) • 後續scan(當前讀),如果碰到Implicit lock,則轉換爲Explicit lock • 延遲加鎖
– Implicit Lock判斷 • 聚簇索引 – 根據記錄上的trx_id判斷(trx_id 是否爲活躍事務? ) • 二級索引 – 根據索引頁面上的max_trx_id + 回聚簇索引判斷 (max_trx_id 是否小於最小活躍事務? )

– 存在bug

InnoDB多版本

– ReadView

– 聚簇索引/二級索引

– 快照讀

– Index Only Scan

– RC vs RR

– Purge

InnoDB多版本定義

一 條語句,能夠 看到 ( 快照讀 ) 本 語句開始時 (RC)/ 本 事務開始時 (RR) 已 經提交的 其他事務所做的修改
– 快照讀 • 讀記錄歷史版本,而非當前更新未提交版本 • 無需加鎖,lock free • 語句級(RC):語句開始時的快照 – 語句級ReadView • 事務級(RR):事務開始時的快照 – 事務級ReadView
– 看到? • 已提交的Insert/Update後項,可見並返回 • 已提交的Delete/Update前項,可見並略過

• ReadView
所謂 ReadView ,是一個事務的集合,這些事務在 ReadView 創建時是 活躍的 ( 未提交 / 回滾 )

 read_view_struct – low_limit_no • 提交時間早於此值(trx->no < low_limit_no)的事務,可以被purge線程回收 • low_limit_no= trx_sys->max_trx_id
– low_limit_id • >= 此值(trx->id >= low_limit_id)的事務,當前ReadView均不可見 • low_limit_id= trx_sys->max_trx_id
– up_limit_id • < 此值(trx->id < up_limit_id)的事務,當前ReadView一定可見 • up_limit_id= ReadView創建時系統中最小活躍事務ID
– trx_ids[] • 系統中所有活躍事務id組成的數組
• 創建ReadView

– 獲取kernel_mutex • 遍歷trx_sys的trx_list鏈表,獲取所有活躍事務,創建ReadView

– Read Committed • 語句開始,創建ReadView

– Repeatable Read • 事務開始,創建ReadView

 ReadView創建

RC VS RR

InnoDB多版本-記錄組織

聚簇索引記錄 – DB_TRX_ID • 生成此記錄的事務ID

– DB_ROLL_PTR • 此記錄歷史版本的指針

– Delete_Bit(未給出)
• 二級索引記錄

– Delete_Bit – 索引頁面,有DB_MAX_ID • 標識修改索引頁面的最大事務ID

InnoDB多版本-更新

 目的

– 測試各種更新場景下,聚簇索引記錄/二級索引記錄的變化
• 準備 create table test (id int primary key, comment char(50)) engine=innodb; create index test_idx on test(comment);
insert into test values (1, ‘aaa’); insert into test values (2, ‘bbb’);

更新主鍵

update test set id = 9 where id = 1;

– 舊記錄標識爲刪除

– 插入一條新紀錄

– 新舊記錄前項均進入回滾段

更新非主鍵

update test set comment = ‘ccc’ where id = 9;

 InnoDB更新總結
– 更新主鍵,聚簇索引/二級索引均無法進行in place update,均會產生 兩個版本
– 更新非主鍵,聚簇索引可以in place update;二級索引產生兩個版本
– 聚簇索引記錄undo,二級索引不記錄undo
– 更新聚簇索引,記錄舊版本會進入Rollback Segment Undo Page
– 更新二級索引,同時需要判斷是否修改索引頁面的MAX_TRX_ID
– 屬於同一事務的undo記錄,在undo page中保存逆向指針

InnoDB多版本-可見性

InnoDB多版本-Cluster Index Scan

聚簇索引掃描

– 當前讀 • 加鎖;讀取記錄最新版本 • 通過記錄的DB_TRX_ID判斷 是否存在Implicit lock
– 快照讀 • 不加鎖; • 根據ReadView讀取可見版本
– Index Only Scan • 一定爲Index Only Scan

 二級索引掃描

– 當前讀 • 加鎖(二級索引/聚簇索引) • 讀取記錄最新版本 • 通過page上的MAX_TRX_ID判斷 是否可能存在Implicit lock
– 快照讀 • 不加鎖 • 讀取記錄唯一可見版本 – 如何過濾同一記錄的不同版本?
– Index Only Scan • cont.

 Index Only Scan

– 當前讀 • 不能進行Index Only Scan – 當前讀需要對聚簇索引記錄加鎖 – 當前讀需要訪問聚簇索引,讀取記錄所有列
– 快照讀 • 訪問索引不存在的列

– 不能進Index Only Scan
• 僅僅訪問索引列

– 二級索引page的MAX_TRX_ID不可見-> 不能進行Index Only Scan » 此概率較小

– MAX_TRX_ID可見 -> 可進行Index Only Scan » 此概率極大

InnoDB多版本-實例講解

MySQL Bugs 65745 UPDATE on InnoDB table enters recursion, eats all disk space

原因分析

– 更新主鍵字段,二級索引同樣會產生Halloween問題

 Purge

– 功能 • 回收索引頁面中被刪除 且不會被其他事務看到的項
– 實現流程 • 拷貝trx_sys ReadView鏈表中最老 的read_view,作爲purge_read_view
• 遍歷InnoDB系統中所有的Rollback Segment, 取出最老的提交事務
• 若purge_read_view.low_limit_no > old_trx.no; 說明對應的事務可以被purge
• 反向遍歷事務的undo日誌, 構造索引記錄,查詢並刪除
– 參數/優化 • innodb_max_purge_lag() • innodb_purge_threads (since MySQL 5.6.2)

• InnoDB事務/鎖/多版本總結

 RR vs RC

– Read Committed • 優勢

– 高併發,低鎖開銷:semi-consistent read – no gap lock;early unlock • 劣勢

– 不支持statement binlog – 語句級快照讀:每條語句,新建ReadView
– Repeatable Read • 優勢 – 支持gap lock;statement binlog – 事務級快照讀:一個事務,對應一個ReadView • 劣勢 – 併發衝突高,加鎖衝突更爲劇烈 – 不支持semi-consistent read;不支持early unlock

事務Commit流程 – prepare • 將redo日誌從log buffer寫入log file,並flush – innodb_flush_log_at_trx_commit
– commit • 處理事務產生的undo pages – insert undo pages直接回收 – 獲取事務的trx->no (標識提交順序) – update undo pages鏈入history list,等待purge
• 釋放事務持有的鎖 – 喚醒必要的等待者
• 關閉事務的read_view
• 將redo日誌寫出,並flush – innodb_flush_log_at_trx_commit

事務Rollback流程 – 反向遍歷undo日誌並應用 • undo操作需要記錄redo (undo的補償日誌)
– 以下流程,與commit一致 • 處理事務產生的undo pages
• 釋放事務鎖
• 關閉read_view
• 將redo日誌寫出,並flush

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章