mysql的update底層一定是先刪除再插入嗎?

目錄:

  1. 場景分析
    1.1 不更新主鍵
  • 1.1.1 就地更新(in-place update)
  • 1.1.2 先刪除掉舊記錄,再插入新記錄
    1.2. 更新主鍵
  1. 附錄
    2.1 delete操作的兩個階段
  • 階段一:delete_mark階段
  • 階段二:purge階段

先說結(jié)論:不一定是。

可以分為三種場景:

  1. 不更新主鍵
    1.1 更新后的列和更新前的列占用存儲空間都一樣大(就地更新);
    1.2 更新后的列和更新前的列占用存儲空間不一樣大(事務(wù)提交后,同步purge刪除后插入);
  2. 更新主鍵(事務(wù)提交前:舊數(shù)據(jù)先delete_mark,事務(wù)提交后:異步purge刪除)

1. 場景分析

1.1 不更新主鍵

1.1.1 就地更新(in-place update)

更新記錄時,對于被更新的每個列來說,如果更新后的列和更新前的列占用的存儲空間都一樣大,那么就可以進行 就地更新 (in-place update),也就是直接在原記錄的基礎(chǔ)上修改對應(yīng)列的值。

1.1.2 先刪除掉舊記錄,再插入新記錄

在不更新主鍵的情況下,如果有任何一個被更新的列更新前和更新后占用的存儲空間大小不一致,那么就需要先把這條舊的記錄從聚簇索引頁面中刪除掉,然后再根據(jù)更新后列的值創(chuàng)建一條新的記錄插入到頁面中。

請注意(事務(wù)提交后):我們這里所說的刪除并不是delete mark操作,而是真正的刪除掉,也就是把這條記錄從 正常記錄鏈表中移除并加入到垃圾鏈表中。加入到垃圾鏈表的過程并不是另外的purge線程處理的,而是由用戶線程同步執(zhí)行真正的刪除(purge)操作。真正刪除后緊接著就要根據(jù)各個列更新后的值創(chuàng)建新紀錄插入。

這里如果新創(chuàng)建的記錄占用的存儲空間大小不超過舊記錄占用的空間,那么可以直接重用被加入到垃圾鏈表中的舊記錄所占用的存儲空間,否則的話需要在頁面中新申請一段空間以供新記錄使用,如果本頁面內(nèi)已經(jīng)沒有可用的空間的話,那就需要進行頁面分裂操作,然后再插入新記錄。

1.2. 更新主鍵

在聚簇索引中,記錄是按照主鍵值的大小連成了一個單向鏈表的,如果我們更新了某條記錄的主鍵值,意味著這條記錄在聚簇索引中的位置將會發(fā)生改變,比如你將記錄的主鍵值從1更新為10000,如果還有非常多的記錄的主鍵值分布在 1 ~ 10000 之間的話,那么這兩條記錄在聚簇索引中就有可能離得非常遠,甚至中間隔了好多個頁面。針對 UPDATE 語句中更新了記錄主鍵值的這種情況, InnoDB 在聚簇索引中分了兩步處理:

  • 將舊記錄進行 delete mark 操作

高能注意:這里是delete mark操作!這里是delete mark操作!這里是delete mark操作!也就是說在 UPDATE語句所在的事務(wù)提交前,對舊記錄只做一個 delete mark 操作,在事務(wù)提交后才由專門的線程做purge操作,把它加入到垃圾鏈表。這里一定要和我們上邊所說的在不更新記錄主鍵值時,先真正刪除舊記錄,再插入新記錄的方式區(qū)分開!

之所以只對舊記錄做delete mark操作,是因為別的事務(wù)同時也可能訪問這條記錄,如果把它真
正的刪除加入到垃圾鏈表后,別的事務(wù)就訪問不到了。這個功能就是所謂的MVCC,

  • 根據(jù)更新后各列的值創(chuàng)建一條新記錄,并將其插入到聚簇索引中(需重新定位插入的位置)。

由于更新后的記錄主鍵值發(fā)生了改變,所以需要重新從聚簇索引中定位這條記錄所在的位置,然后把它插進去。

針對 UPDATE 語句更新記錄主鍵值的這種情況,在對該記錄進行 delete mark 操作前,會記錄一條類型為TRX_UNDO_DEL_MARK_REC 的 undo日志 ;之后插入新記錄時,會記錄一條類型為TRX_UNDO_INSERT_REC 的 undo日志 ,也就是說每對一條記錄的主鍵值做改動時,會記錄2條 undo日志 。

2. 附錄:

2.1 delete操作的兩個階段

我們知道插入到頁面中的記錄會根據(jù)記錄頭信息中的next_record屬性組成一個單向鏈表,我們把這個鏈表稱之為正常記錄鏈表。
被刪除的記錄其實也會根據(jù)記錄頭信息中的next_record屬性組成一個鏈表,只不過這個鏈表中的記錄占用的存儲空間可以被重新利用,所以也稱這個鏈表為垃圾鏈表。Page Header部分有一個稱之為 PAGE_FREE的屬性,它指向由被刪除記錄組成的垃圾鏈表中的頭節(jié)點。

假設(shè)此刻某個頁面中的記錄分布情況是這樣的(這個不是undo_demo表中的記錄,只是我們隨便舉的一個例子):

image.png

為了突出主題,在這個簡化版的示意圖中,我們只把記錄的 delete_mask 標志位展示了出來。從圖中可以看出,正常記錄鏈表中包含了3條正常記錄, 垃圾鏈表 里包含了2條已刪除記錄,在 垃圾鏈表 中的這些記錄占用的存儲空間可以被重新利用。

頁面的 Page Header 部分的 PAGE_FREE 屬性的值代表指向 垃圾鏈表 頭節(jié)點的指
針。假設(shè)現(xiàn)在我們準備使用 DELETE 語句把 正常記錄鏈表 中的最后一條記錄給刪除掉,其實這個刪除的過程需要經(jīng)歷兩個階段:

階段一:delete_mark階段

  • 階段一:僅僅將記錄的 delete_mask 標識位設(shè)置為 1 ,其他的不做修改(其實會修改記錄的 trx_id 、roll_pointer 這些隱藏列的值)。這個階段稱之為 delete mark 。
delete_mark階段.png

可以看到, 正常記錄鏈表 中的最后一條記錄的 delete_mask 值被設(shè)置為 1 ,但是并沒有被加入到 垃圾鏈表 。此時這條記錄處于一個中間狀態(tài)。在刪除語句所在的事務(wù)提交之前,被刪除的記錄一直都處于這種所謂的 中間狀態(tài) 。

ps:為啥會有這種奇怪的中間狀態(tài)呢?其實主要是為了實現(xiàn)一個稱之為MVCC的功能。

階段二:purge階段

  • 階段二::當(dāng)該刪除語句所在的事務(wù)提交之后,會有專門的線程后來真正的把記錄刪除掉。所謂真正的刪除就是把該記錄從正常記錄鏈表中移除,并且加入到垃圾鏈表中。并且還要調(diào)整一些頁面的其他信息。這個階段稱之為 purge 。
purge階段.png

推薦閱讀

《MySQL 是怎樣運行的:從根兒上理解 MySQL》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 回顧 上節(jié)我們了解Master Thread的主要工作,它包括:刷新redo日志到磁盤中.合并插入緩沖.回收und...
    AbstractCulture閱讀 2,565評論 3 5
  • 事務(wù)回滾的需求 我們說過事務(wù)需要保證原子性,也就是事務(wù)中的操作要么全部完成,要么什么也不做。但是偏偏有時候事務(wù)執(zhí)行...
    tracy_668閱讀 802評論 1 5
  • 具體細節(jié) 請去掘金購買《MySQL 是怎樣運行的:從根兒上理解 MySQL》 事務(wù)回滾的需求 1.這些為了回滾而記...
    簡書徐小耳閱讀 2,835評論 1 1
  • 并發(fā)控制 實現(xiàn)事務(wù)隔離的機制,稱之為并發(fā)控制 所謂并發(fā)控制,就是保證并發(fā)執(zhí)行的事務(wù)在某一隔離級別上的正確執(zhí)行的機制...
    祁小彬閱讀 1,129評論 0 2
  • 在MySQL中InnoDB屬于存儲引擎層,并以插件的形式集成在數(shù)據(jù)庫中。從MySQL5.5.8開始,InnoDB成...
    AI喬治閱讀 2,295評論 0 70

友情鏈接更多精彩內(nèi)容