MySQL 中刪除的數(shù)據(jù)都去哪兒了?
本文轉(zhuǎn)載自微信公眾號(hào)「SH的全棧筆記」,作者SH的全棧筆記 。轉(zhuǎn)載本文請(qǐng)聯(lián)系SH的全棧筆記公眾號(hào)。
不知道大家有沒有想過下面這件事?
我們平時(shí)調(diào)用 DELETE 在 MySQL 中刪除的數(shù)據(jù)都去哪兒了?
這還用問嗎?當(dāng)然是被刪除了啊
那么這里又有個(gè)新的問題了,如果在 InnoDB 下,多事務(wù)并發(fā)的情況下,如果事務(wù)A刪除了 id=1 的數(shù)據(jù),同時(shí)事務(wù)B又去讀取 id=1 的數(shù)據(jù),如果這條數(shù)據(jù)真的被刪除了,那 MVCC 拿啥數(shù)據(jù)返回給用戶呢?
沒錯(cuò),這就需要了解一下 MySQL 的多版本并發(fā)的原理相關(guān)的東西,感興趣的可以去看我之前寫的這篇文章。
所以,實(shí)際情況中,調(diào)用了 DELETE 語句刪除的數(shù)據(jù)并不會(huì)真正的被物理刪除,這條數(shù)據(jù)其實(shí)還在那,只不過被打上了一個(gè)標(biāo)記,標(biāo)記已刪除。
這其實(shí)跟我們?nèi)粘5牟僮?mdash;—軟刪除,差不多是一個(gè)意思
在 MySQL 中, UPDATE 和 DELETE 操作本質(zhì)上是一樣的, 都屬于更新操作,刪除操作只不過是把某行數(shù)據(jù)中的一個(gè)特定的比特位標(biāo)記為已刪除,僅此而已。
那么問題又來了,那這些刪除的數(shù)據(jù)如果一直這么堆下去,那不早晚把硬盤撐爆?
如果都玩兒成這樣了,那 MySQL 還能像現(xiàn)在這樣被大規(guī)模的用于生產(chǎn)環(huán)境中嗎?那 MySQL 到底是怎么玩的?
這就需要提到 Purge 操作了。
Purge操作是啥?
Purge 操作才是真正將數(shù)據(jù)(已被標(biāo)記為已刪除)物理刪除的操作。
Purge 操作針對(duì)的數(shù)據(jù)對(duì)象,不僅僅是某一行,還有其對(duì)應(yīng)的索引數(shù)據(jù)和 Undo Log。
好的那么問題又來了。
問題是,Purge 操作什么時(shí)候會(huì)執(zhí)行呢?實(shí)際上,你可以將執(zhí)行 Purge 操作的線程(簡(jiǎn)稱 Purge 線程)理解成一個(gè)后臺(tái)周期性執(zhí)行的線程。
Purge 線程可以有一個(gè),也可以有多個(gè),具體的線程數(shù)量可以由 MySQL 的配置項(xiàng) innodb_purge_threads 來進(jìn)行配置。當(dāng)然,我相信你肯定不記得在使用 MySQL 的時(shí)候配置過這個(gè),因?yàn)?innodb_purge_threads 有個(gè)默認(rèn)值,值為 4。
InnoDB 會(huì)根據(jù) MySQL 中表的數(shù)量和 Purge 線程的數(shù)量進(jìn)行分配。
但正是因?yàn)橛羞@種特性,Purge 線程的數(shù)量才需要根據(jù)業(yè)務(wù)的實(shí)際情況來做調(diào)整。舉個(gè)例子,假設(shè) DML 操作都集中在某張表,比如表1上...
你先等等,我打斷一下......
什么叫 DML 操作?總喜歡搞些復(fù)雜的名詞...DML(Data Manipulation Language)數(shù)據(jù)操作語句,實(shí)際上就是CRUD增刪改查...
與之類似的概念還有DDL(Data Definition Language)數(shù)據(jù)定義語句,也就是CREATE、DROP和ALTER等等.
以及DCL(Data Control Language)數(shù)據(jù)控制語句,也就是GRANT、REVOKE等等...
繼續(xù)說回來,雖然 Purge 線程的數(shù)量是可配置的,但是也不是你想配多少就配多少的。不然你給它干個(gè) 10000 個(gè)線程,那不就直接原地 OOM 了嗎?
innodb_purge_threads 的最大值為 32,而且并不是我們配了 32 InnoDB 就真的會(huì)啟動(dòng) 32 個(gè) Purge 線程,為啥呢?舉個(gè)很簡(jiǎn)單的例子,假設(shè)此時(shí)只有一張表,然后我們配置了 32 個(gè) Purge 線程。
你看著上面這個(gè)圖問問自己,這「河里」嗎?這樣不僅浪費(fèi)了系統(tǒng)的資源,同時(shí)還使得不同的 Purge 線程之間發(fā)生了數(shù)據(jù)競(jìng)爭(zhēng)。不僅如此,Purge 線程還可能跟用戶線程產(chǎn)生競(jìng)爭(zhēng)。
但是當(dāng)系統(tǒng)中真的有 32 張表的時(shí)候,情況又不一樣了,一個(gè) Purge 線程對(duì)應(yīng)一張表,線程與線程之間就不會(huì)存在數(shù)據(jù)競(jìng)爭(zhēng),并且沒有浪費(fèi)系統(tǒng)資源,還能夠提升執(zhí)行 Purge 操作的性能。
這就是為啥 InnoDB 會(huì)根據(jù)實(shí)際情況來調(diào)整 MySQL 中 Purge 線程的數(shù)量,所以我們?cè)谂渲玫臅r(shí)候也要按照實(shí)際情況來設(shè)置。
舉個(gè)例子,如果你的數(shù)據(jù)庫中,增刪改 的操作只集中在某幾張表上,則可以考慮將 innodb_purge_threads 設(shè)置的稍微低一點(diǎn)。相反,如果 增刪改 的操作幾乎每張表都有,那么 innodb_purge_threads 就可以設(shè)置的大一些。
了解完 Purge 線程本身之后,我們就可以來了解 Purge 線程所針對(duì)的對(duì)象了。Purge 線程主要清理的對(duì)象是 Undo Logs,其次是行記錄。
因?yàn)?Undo Log 可以分為:
- Insert Undo Log
- Update Undo Log
所以更準(zhǔn)確的說法是,Purge 線程清理的對(duì)象是 Update Undo Log 和 行記錄,因?yàn)?Insert Undo Log 會(huì)在事務(wù)提交之后就會(huì)被刪除。
我們都知道 InnoDB 的 MVCC 的數(shù)據(jù)來源是一個(gè)一個(gè) Undo Log 形成的單鏈表,而 Purge 線程就是用于定期清理 Undo Log 的,并且在清理完 刪除數(shù)據(jù)所生成的 Undo Log 的時(shí)候,就會(huì)把對(duì)應(yīng)的行記錄給移除了。
那么問題又來了,Purge 線程每次會(huì)讀取多少條件 Undo Log 記錄呢?
很明顯,它不是看當(dāng)時(shí)的心情來決定取多少條的。它是通過配置項(xiàng) innodb_purge_batch_size 來控制的,默認(rèn)是 300。然后InnoDB會(huì)將這300條 Undo Log 分給innodb_purge_threads個(gè) Purge 線程。在清理的過程中,Purge 線程還會(huì)釋放 Undo Log 表空間內(nèi)的文件。