亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.430618.com 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

故事從好多年前說起。

想必大家也聽說過數據庫單表建議最大兩千萬條數據這個說法。如果超過了,性能就會下降得比較厲害。

巧了。我也聽說過。

但我不接受他的建議,硬是單表裝了 1 億條數據。

這時候,我們組里新來的實習生看到了之后,天真無邪地問我:"單表不是建議最大兩千萬嗎?為什么這個表都放了 1 個億還不分庫分表"?

我能說我是因為懶嗎?我當初設計時哪里想到這表竟然能漲這么快……

我不能。

說了等于承認自己是開發組里的毒瘤,雖然我確實是,但我不能承認。

我如坐針氈,如芒刺背,如鯁在喉。

開始了一波騷操作。

"我這么做是有道理的。"

"雖然這個表很大,但你有沒有發現它查詢其實還是很快。"

"這個兩千萬是個建議值,我們要來看下這個兩千萬是怎么來的。"

數據庫單表行數最大多大?

我們先看下單表行數理論最大值是多少。

建表的 SQL 是這么寫的,

CREATE TABLE `user` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵', `name` varchar(100) NOT NULL DEFAULT '' COMMENT '名字', `age` int(11) NOT NULL DEFAULT '0' COMMENT '年齡', PRIMARY KEY (`id`), KEY `idx_age` (`age`) ) ENGINE=InnoDB AUTO_INCREMENT=100037 DEFAULT CHARSET=utf8;

 

其中 id 就是主鍵。主鍵本身唯一,也就是說主鍵的大小可以限制表的上限。

如果主鍵聲明為 int 大小,也就是 32 位。那么能支持 2^32-1,也就是 21 個億左右。

如果是 bigint,那就是 2^64-1。但這個數字太大,一般還沒到這個限制之前,磁盤先受不了。

搞離譜點。

如果我把主鍵聲明為 tinyint 一個字節, 8位。最大 2^8-1,也就是 255。

CREATE TABLE `user` ( `id` tinyint(2) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵', `name` varchar(100) NOT NULL DEFAULT '' COMMENT '名字', `age` int(11) NOT NULL DEFAULT '0' COMMENT '年齡', PRIMARY KEY (`id`), KEY `idx_age` (`age`) ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

 

如果我想插入一個 id=256 的數據,那就會報錯。

 

MySQL> INSERT INTO `tmp` (`id`, `name`, `age`) VALUES (256, '', 60); ERROR 1264 (22003): Out of range value for column 'id' at row 1

 

也就是說,tinyint 主鍵限制表內最多 255 條數據。

除了主鍵,還有哪些因素會影響行數?

索引的結構

索引內部是用的 B+ 樹,這個也是八股文老股了,大家估計也背得很熟了。

為了不讓大家有過于強烈的審丑疲勞,今天我嘗試從另外一個角度給大家講講這玩意。

頁的結構

假設我們有這么一張 user 數據表。


user 表

其中 id 是唯一主鍵。

這看起來的一行行數據,為了方便,我們后面就叫它們 record 吧。

這張表看起來就跟個 Excel 表格一樣。Excel 的數據在硬盤上是一個 xx.xlsx 文件。

而上面 user 表數據,在硬盤上其實也是類似,放在了 user.ibd 文件下。含義是 user 表的 innodb data 文件,專業說法又叫表空間。

雖然在數據表里,它們看起來是挨在一起的。但實際上在 user.ibd 里他們被分成很多小數據頁,每份大小16K。

類似于下面這樣:


ibd 文件內部有大量的頁

我們把視角聚焦到頁上面。

整個頁大小為 16K,不大。但 record 這么多,一頁肯定放不下,所以會分開放到很多頁里。并且這 16K 也不可能全用來放 record,對吧。

因為,這些 record 被分成好多份,放到各個頁里了。為了唯一標識具體是哪一頁,那就需要引入頁號(其實是一個表空間的地址偏移量)。同時為了把這些數據頁給關聯起來,于是引入了前后指針,用于指向前后的頁。這些都被加到了頁頭里。

頁需要支持讀寫,16K 說小也不小,寫一半電源線被拔了也是有可能發生的。所以,為了保證數據頁的正確性,還引入了校驗碼。這個被加到了頁尾。

那剩下的空間才被用來放 record。如果 record 行數特別多,進入到頁內時會挨個遍歷效率也不太行。所以,為這些數據生成了一個頁目錄。具體實現細節不重要,只需要知道,它可以通過二分查找的方式將查找效率從 O(n) 變成 O(logn)。


頁結構

從頁到索引

如果想查一條 record,可以把表空間里每一頁查出來,再把里面的 record 挨個判斷是不是我們要找的。

行數小的時候,這么操作也沒啥問題。行數多了,性能就慢了。

于是為了加快搜索,可以在每個數據頁里選出主鍵 id 最小的 record,而且只需要它們的主鍵 id 和所在頁的頁號。將它們組成新的 record,放入到一個新生成的一個數據頁中。這個新數據頁跟之前的頁結構沒啥區別,大小還是 16K。

但為了跟之前的數據頁進行區分,數據頁里加入了頁層級(Page Level)信息,從 0 開始往上算。于是頁與頁之間就有了上下層級的概念,就像下面這樣。


兩層 B+ 樹結構

頁跟頁之間看起來就像是一棵倒過來的樹,也就是我們常說的 B+ 樹索引。

最下面一層 Page Level 為 0,也就是所謂的葉子結點。其余都叫非葉子結點。

上面展示的是兩層的樹。如果數據變多了,還可以再通過類似的方法,往上構建一層,成了三層樹。

三層 B+ 樹結構

現在,可以通過這樣一棵 B+ 樹加速查詢。

舉個例子,比方說我們想要查找數據行 5。

先從頂層頁的 record 入手。record 里包含了主鍵 id 和頁號(頁地址)。

下圖中黃色的箭頭:向左最小 id 是 1,向右最小 id 是 7。

 

  1. 如果 id=5 的數據存在,那必定在左邊箭頭;

     

  2. 于是順著的 record 的頁地址就到了 6 號數據頁里;

     

  3. 再判斷 id=5>4,所以肯定在右邊的數據頁里;

     

  4. 于是加載 105 號數據頁;

     

  5. 在數據頁里找到 id=5 的數據行,完成查詢。

     

 


B+ 樹的查詢過程

另外需要注意,上面的頁的頁號并不是連續的,它們在磁盤里也不一定是挨在一起的。

這個過程中查詢了三個頁,如果這三個頁都在磁盤中(沒有被提前加載到內存中),那么最多需要經歷三次磁盤 IO 查詢,它們才能被加載到內存中。

B+ 樹承載的記錄數量

從上面的結構里可以看出,B+ 樹的最末級葉子結點里放了 record 數據。而非葉子結點里則放了用來加速查詢的索引數據。

也就是說,同樣一個 16K 的頁,非葉子節點里每一條數據都指向一個新的頁。而新的也有兩種可能。

 

  • 如果是末級葉子節點的話,那么里面放的就是 record 數據;

     

  • 如果是非葉子節點,那么就會循環繼續指向新的數據頁。

     

 

假設:

 

  • 非葉子結點內指向其他內存頁的指針數量為 X;

     

  • 葉子節點內能容納的 record 數量為 Y;

     

  • B+ 樹的層數為 Z。

     

 


總行數的計算方法

那這棵 B+ 樹放的行數據總量等于 (X ^ (Z-1)) * Y。

怎么計算 X

我們回去看數據頁的結構。


頁結構

非葉子節點里主要放索引查詢相關的數據,放的是主鍵和指向頁號。

主鍵假設是 bigint(8Byte),而頁號在源碼里叫 FIL_PAGE_OFFSET(4 Byte),那么非葉子節點里的一條數據是 12 Byte 左右。

整個數據頁 16K, 頁頭頁尾那部分數據全加起來大概 128 Byte,加上頁目錄毛估占 1K 吧。那剩下的 15K 除以 12 Byte 等于 1280,也就是可以指向 X=1280 頁。

我們常說的二叉樹指的是一個結點可以發散出兩個新的結點。m 叉樹一個節點能指向 m 個新的節點。這個指向新節點的操作就叫扇出(Fanout)。

而上面的 B+ 樹能指向 1280 個新的節點。恐怖如斯,可以說扇出非常高了。

如何計算 Y

葉子節點和非葉子節點的數據結構是一樣的,所以也假設剩下 15KB 可以利用。

葉子節點里放的是真正的行數據。假設一條行數據 1KB,所以一頁里能放 Y=15 行。

行總數計算

回到 (X ^ (Z-1)) * Y 這個公式,已知 X=1280,Y=15。

 

  • 假設 B+ 樹是兩層,那 Z=2。總行數 (1280 ^ (2-1)) * 15 ≈ 2萬

     

  • 假設 B+ 樹是三層,那 Z=3。總行數 (1280 ^ (3-1)) * 15 ≈ 2.5千萬

     

 

這個 2.5千萬,就是我們常說的單表建議最大行數兩千萬的由來。畢竟再加一層,數據就大得有點離譜了。三層數據頁對應最多三次磁盤 IO,也比較合理。

行數超 1 億就慢了嗎?

上面假設單行數據用了 1KB,所以一個數據頁能放個 15 行數據。

如果我單行數據用不了這么多,比如只用了 250 Byte。那么單個數據頁能放 60 行數據。

那同樣是三層 B+ 樹,單表支持的行數就是 (1280 ^ (3-1)) * 60 ≈ 1億。

你看我 1 億數據,其實也就三層 B+ 樹。在這個 B+ 樹里要查到某行數據,最多也是三次磁盤 IO,所以并不慢。

這就很好的解釋了文章開頭,為什么我單表 1 億條數據,但查詢性能沒啥大毛病。

B 樹承載的記錄數量

既然都聊到這里了,我們就順著這個話題多聊一些吧。

我們都知道,現在 MySQL 的索引都是 B+ 樹。而有一種樹,跟 B+ 樹很像叫 B 樹,也叫 B- 樹。它跟 B+ 樹最大的區別在于,B+ 樹只在末級葉子結點處放數據表行數據,而 B 樹則會在葉子和非葉子結點上都放。

B 樹的結構類似這樣:


B 樹結構

B 樹將行數據都存在非葉子節點上。假設每個數據頁還是 16KB,掐頭去尾每頁剩15KB,并且一條數據表行數據還是占 1KB。就算不考慮各種頁指針的情況下,也只能放個 15 條數據,數據頁的扇出明顯變小了。

計算可承載的總行數的公式也變成了一個等比數列。

 

  • 15 + 15^2 +15^3 + ... + 15^Z

 

其中 Z 還是層數的意思。

為了能放兩千萬左右的數據需要 Z>=6,也就是樹需要有 6 層。查一次要訪問 6 個頁。假設這 6 個頁并不連續,為了查詢其中一條數據,最壞情況需要進行 6 次磁盤 IO。

而 B+ 樹同樣情況下放兩千萬數據左右,查一次最多是 3 次磁盤 IO。

磁盤 IO 越多則越慢,這兩者在性能上差距略大。因此,B+ 樹比 B 樹更適合稱為 MySQL 索引。

總結

 

  • B+ 樹葉子和非葉子結點的數據頁都是 16KB,并且數據結構一致。區別在于葉子節點放的是真實的行數據,而非葉子節點放的是主鍵和下一個頁的地址;

     

  • B+ 樹一般有兩到三層。由于其高扇出,三層就能支持兩千萬以上的數據。并且一次查詢最多 1~3 次磁盤 IO,性能也還行;

     

  • 存儲同樣量級的數據,B 樹比 B+ 樹層級更高,因此磁盤 IO 也更多。所以,B+ 樹更適合稱為 MySQL 索引。

     

  • 索引結構不會影響單表最大行數,兩千萬也只是推薦值。超過了這個值可能會導致 B+ 樹層級更高,影響查詢性能;

     

  • 單表最大值還受主鍵大小和磁盤大小限制。

     

 

參考資料

《MYSQL內核:INNODB存儲引擎 卷1》

- EOF -

分享到:
標簽:MySQL
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定