最近工作的時候突然想到一個關(guān)于大對象存儲的問題。先看看這個問題的背景。
背景1:在mysql數(shù)據(jù)庫(innodb引擎)中,我們有這樣的業(yè)務場景,在一個表(下面我們稱為tmp表)中有一個字段(我們稱為describe字段)可能存儲的內(nèi)容會特別的大,假設describe字段的業(yè)務大小上限為64KB,使用TEXT類型來存儲(文章最下方有字段參考)。
背景2:我們都知道innodb把所有的字段都存儲在聚簇索引的葉子節(jié)點上,而在數(shù)據(jù)庫的存儲設計中,innodb的一個page(默認為16K)是希望能存儲多個葉子節(jié)點,這樣在讀一個page頁的時候能讀到更多的葉子節(jié)點,減少查詢的磁盤io。
那么問題來了:
那么tmp表中的describe字段大小都一靜超過了16k了,一個page頁已經(jīng)存儲不下了,innodb是怎么解決這個問題的呢?**
innodb是不是這么干的呢?
猜測1:是不是會擴展一個page的大小或者說把兩個page頁當做一個page頁。
猜測2:會不會大字段就不放在葉子節(jié)點所在的頁了,而是單獨存儲了。
好吧,下面就是通過查找資料來驗證自己的猜測了。
問題探索
我們先來看一個概念。
數(shù)據(jù)行溢出: Innodb會將一條記錄中的某些數(shù)據(jù)存儲在真正的數(shù)據(jù)page之外
它的物理存儲結(jié)構(gòu)其實是這樣的,在page頁面中只會保留describe字段的前768個字節(jié)的前綴數(shù)據(jù),之后是偏移量,偏移量指向存儲完整數(shù)據(jù)的行溢出頁。

到這里,其實對文章開頭提出來的問題是有一個解答了。但是我們繼續(xù)深入探索一下,什么樣的數(shù)據(jù)會出現(xiàn)行溢出的這種現(xiàn)象呢?
行溢出的條件
有一些人認為像BLOB、LONGBLOB這類的大對象數(shù)據(jù)類型會把數(shù)據(jù)存放在數(shù)據(jù)頁面之外。但是如果BLOB只存放了10個字段的數(shù)據(jù),這時候還會把數(shù)據(jù)丟到其他頁面嗎?想來不會這么蠢,看資料別人的實驗也證明了這一點。然后像varchar這樣的類型,如果它存儲了16K的內(nèi)容,這些內(nèi)容會存儲在葉子節(jié)點內(nèi)嗎?事實證明也不合理。innodb是b+樹的存儲結(jié)構(gòu),在一個page中不希望只存儲1條記錄,是否發(fā)生行溢出這與page的大小有關(guān)系,一切都是為了保證一個page中存儲多條數(shù)據(jù),如果因為一條記錄的超大導致了這個page存儲不下其他的記錄,顯然在搜索效率上是無法容忍的。至于具體到怎樣的數(shù)據(jù)級別,是超過9000個字節(jié)會存儲或者其他,我個人認為這個和innodb 定義的page有關(guān)系。(個人理解,僅供參考,歡迎指正、探討與交流)

字段類型大小表
| 字段類型 | 存儲大小 | 解釋 |
|---|---|---|
| CHAR | 0-255字節(jié) | 定長字符串 |
| VARCHAR | 0-65535 字節(jié)(64KB) | 變長字符串 |
| TINYBLOB | 0-255字節(jié) | 不超過 255個字符的二進制字符串 |
| TINYTEXT | 0-255字節(jié) | 短文本字符串 |
| BLOB | 0-65 535字節(jié)(64KB) | 二進制形式的長文本數(shù)據(jù) |
| TEXT | 0-65 535字節(jié)(64KB) | 長文本數(shù)據(jù) |
| MEDIUMBL | OB 0-16 777 215字節(jié)(16M) | 二進制形式的中等長度文本數(shù)據(jù) |
| MEDIUMTE | XT 0-16 777 215字節(jié)(16M) | 中等長度文本數(shù)據(jù) |
| LONGBLOB | 0-4 294 967 295字節(jié)(4G) | 二進制形式的極大文本數(shù)據(jù) |
| LONGTEXT | 0-4 294 967 295字節(jié)(4G) | 極大文本數(shù)據(jù) |