共計 4369 個字符,預計需要花費 11 分鐘才能閱讀完成。
丸趣 TV 小編給大家分享一下 MySQL 中大對象多版本并發控制的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
MySQL 8.0:InnoDB 中大對象的 MVCC
在本文中,我將解釋 MySQL InnoDB 存儲引擎中大對象(LOB)設計的多版本并發控制(MVCC)。MySQL 8.0 有一個新功能,允許用戶部分更新大型對象,包括 JSON 文檔。使用此部分更新功能,當 LOB 部分更新時,MVCC 對 LOB 的工作方式已發生變化。對于正常更新(完整更新),MVCC 將像以前的版本一樣工作。讓我們看一下 MVCC 在不涉及部分更新時的工作原理,然后考慮對 LOB 進行部分更新的用例。
MVCC 常規更新
我使用術語常規更新來指代不是部分更新的更新。我將通過一個例子解釋 MVCC 如何用于常規更新大對象。我將為此目的使用以下 mtr(1) 測試用例:
create table t1 ( f1 int primary key , f2 longblob ) engine = innodb ;
insert into t1 values ( 1 , repeat ( a , 65536 ) ) ;
start transaction ;
update t1 set f2 = repeat ( b , 65536 ) where f1 = 1 ;
-- echo # Connection con1:
-- 對于使用 MySQL 客戶端的用戶,可能需要通過另開一個終端窗口建立新鏈接, 下同。 connect ( con1 , localhost , root , , ) ;
-- echo # Must see the old value aaaaaaaaaa
select f1 , right ( f2 , 10 ) from t1 order by f1 ;
-- echo # Connection default:
connection default ;
disconnect con1 ;
commit ;
drop table t1 ;
為了理解下面的解釋,仔細理解上述測試用例非常重要。
測試場景如下:
最初,表 t1 包含單個記錄(R1)。
事務 trx1 將記錄更新為新值。
當 trx1 仍處于活動狀態時,另一個事務 trx2 正在讀取記錄。它將讀取舊值。
表 t1 僅包含一個記錄(R1)。但是 trx1 和 trx2 會看到兩個不同的值。該表實際上只包含最新值(trx1 所見的值),而 trx2 看到的值或記錄是從撤消日志記錄中獲得的。讓我們看下面的圖片來更好地理解它。
初始狀態:更新操作之前
下圖顯示了更新操作之前的情況。撤消日志為空。表的聚簇索引包含一行。表中有一個 LOB。聚簇索引記錄包含對 LOB 的引用。
最終狀態:更新操作后
現在讓我們看一下更新操作后的情況。
以下是一些重要的觀察:
用戶表空間中有兩個 LOB – 舊的 LOB 和新的 LOB。舊的 LOB 只能通過撤消日志訪問。聚集索引記錄指向新 LOB。
更新操作已創建包含更新向量的撤消日志記錄。此撤消日志記錄指向舊 LOB。
聚簇索引記錄通過 DB_ROLL_PTR 系統列指向撤消日志記錄。此滾動指針指向撤消日志記錄,該記錄可用于構建聚簇索引記錄的先前版本。
撤消記錄不包含 LOB 本身。而是它只包含對存儲在用戶表空間中的 LOB 的引用。
存儲在撤消日志記錄中的 LOB 引用與存儲在聚簇索引記錄中的 LOB 引用不同。
事務在連接 1 中采取的步驟如下:
事務查看 R1 并確定尚未提交修改聚簇索引記錄的事務。這意味著它無法讀取該記錄(因為默認隔離級別是 REPEATABLE READ)。
它查看 R1 中的 DB_ROLL_PTR 并找到撤消日志記錄。使用撤消日志記錄構建 R1 的先前版本。
它讀取了這個構建的舊版 R1。請注意,此版本在聚簇索引記錄中不可用。但它使用撤消記錄即時構建。
當 R1 指向新的 LOB 時,這個構造的舊版本的 R1 指向舊的 LOB。所以結果包含舊的 LOB。
這是 LOB 的 MVCC 在不涉及部分更新時的工作方式。
MVCC 部分更新
讓我們看另一個例子,了解 MVCC 在部分更新的情況下是如何工作的。我們需要另一個例子,因為目前僅通過函數 json_set()和 json_replace()支持 JSON 文檔的部分更新。
create table t2 ( f1 int primary key , j json ) engine = InnoDB ;
set @ elem_a = concat ( , repeat ( a , 200 ) , ) ;
set @ elem_a_with_coma = concat ( @ elem_a , , ) ;
set @ json_doc = concat ( [ , repeat ( @ elem_a_with_coma , 300 ) , @ elem_a , ] ) ;
insert into t2 ( f1 , j ) values ( 1 , @ json_doc ) ;
start transaction ;
update t2 set j = json_set ( j , $[200] , repeat ( b , 200 ) ) where f1 = 1 ;
-- echo # Connection con1:
connect ( con1 , localhost , root , , ) ;
-- echo # Must see the old value aaaaaaaaaa...
select json_extract ( j , $[200] ) from t2 ;
-- echo # Connection default:
connection default ;
disconnect con1 ;
commit ;
該場景與前面的示例相同。只是 longblob 字段已更改為 JSON 文檔。加載的數據也略有不同,以符合 JSON 格式。
提示:您可以在上述 mtr 測試用例(兩者中)中添加語句 set debug = + d,innodb_lob_print,以在服務器日志文件中打印 LOB 索引。LOB 索引將在插入后立即打印。LOB 索引將為您提供存儲的 LOB 對象的結構。
在部分更新操作之前
完全或部分更新操作之前的初始條件是相同的,并且已經在上面給出。但是在下圖中,提供了一些附加信息。
讓我們看看圖中顯示的其他信息:
存儲在聚簇索引記錄中的 LOB 引用現在包含 LOB 版本號 v1。在初始插入操作期間,將其設置為 1,并在每次部分更新時遞增。
每個 LOB 數據頁面在 LOB 索引中都有一個條目。每個條目都包含 LOB 版本信息。每當修改一個 LOB 數據頁時,它將被復制到具有新數據的新 LOB 數據頁中,并且將創建具有遞增的 LOB 版本號的新 LOB 索引條目。
附加信息是 LOB 版本號。這在聚集索引記錄中的 LOB 引用中以及 LOB 索引的每個條目中都可用。
部分更新操作后
下圖說明了部分更新操作后的情況。
這里最重要的優化是用戶表空間中仍然只有一個 LOB。僅更新需要修改的那些 LOB 數據頁。部分更新操作后的這個單個 LOB 包含舊版本和新版本的 LOB。圖中 LOB 數據頁面上的 v1 和 v2 標簽說明了這一點。
另一個重要的觀察是撤消日志和聚簇索引記錄中的 LOB 引用指向同一個 LOB。但 LOB 引用包含不同的版本號。撤消日志記錄中的 LOB 引用包含 v1(舊版本號),聚簇索引記錄中的 LOB 引用包含新版本號 v2。
LOB 版本號的目的
如上所示,具有不同版本號的不同 LOB 引用指向相同的 LOB。單個 LOB 包含來自不同版本的部分。LOB 版本號用于獲取各種 LOB 引用指向的正確版本。在本節中,我們將了解如何完成此操作。
LOB 索引包含組成 LOB 的 LOB 頁面列表。它包含 LOB 數據頁的頁碼,每個 LOB 數據頁包含的數據量以及版本號。此列表的每個節點稱為 LOB 索引條目。每個 LOB 索引條目都包含舊版本的列表。讓我們看一個說明上述部分更新測試用例的結構的圖。
最初,在完成部分更新之前,LOB 索引總共包含 4 個條目。四個條目的頁碼是 5,6,7 和 8. 沒有 LOB 索引條目具有舊版本。所有四個條目的版本號均為 1。
部分更新完成后,我們注意到頁碼 9 已替換頁碼 7,頁碼 7 現在被視為頁碼 9 的舊版本。頁碼 9 的版本號為 2,并且頁碼 7 的版本號為 1。
部分更新完成后,當通過版本號為 1 的 LOB 引用訪問 LOB 時,將查看第 5 頁的第一個索引條目。它的版本號為 1. 如果索引條目中的版本號小于或等于 LOB 引用中的版本號,則將讀取該條目。因此,將讀取第 5 頁。然后將查看頁碼為 6 的索引條目。它的版本號為 1,因此將被讀取。然后將查看頁碼為 9 的索引條目。它的版本號為 2. 但是 lob 引用的版本號為 1. 如果索引條目中的版本號大于 LOB 引用中的版本號,則不會讀取該條目。由于頁碼 9 的條目具有版本 2,因此將查看其舊版本。將檢查頁碼為 7 的索引條目。它的版本號為 1,因此將被讀取。在此之后,將檢查頁碼為 8 的索引條目。它的版本號為 1,因此也將被讀取。這是訪問舊版 LOB 的方式。
部分更新完成后,當通過版本號為 2 的 LOB 引用訪問 LOB 時,將查看第 5 頁的第一個索引條目。它的版本號為 1. 如果索引條目中的版本號小于或等于 LOB 引用中的版本號,則將讀取該條目。因此它將按順序讀取頁碼 5,6,9,8。由于版本號始終 = 2,因此無需使用舊版本訪問頁碼 7。
需要記住的一點是 LOB 在 InnoDB 中不是獨立存在的。它被視為聚簇索引記錄的擴展。LOB 對事務是否可見并不由 LOB 模塊處理。LOB 模塊只是處理聚簇索引記錄。如果事務訪問 LOB,則意味著它已經在聚簇索引記錄中的 DB_TRX_ID 的幫助下確定它可以查看 LOB(而不是 LOB 的特定版本)。所以我們不擔心 LOB 模塊中的那個方面。我們只專注于為給定的 LOB 版本號提供正確的內容。
結論
在本文中,我們了解了如何在 InnoDB 中為大對象完成 MVCC。當對 LOB 進行部分更新時,多個 LOB 引用可以指向同一個 LOB。但他們將擁有不同的版本號。使用這些 LOB 版本號,可以訪問正確的 LOB 內容。
希望您發現此信息有用。
謝謝你使用 MySQL!
注釋:
(1) Mtr 即 Mini-transaction 的縮寫,字面意思小事物,相對邏輯事物而言,我們把它稱作物理事物。屬于 Innodb 存儲引擎的底層模塊。主要用于鎖和日志信息。
以上是“MySQL 中大對象多版本并發控制的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注丸趣 TV 行業資訊頻道!