1. 程式人生 > >【MySQL技術內幕】17-InnoDB邏輯儲存結構

【MySQL技術內幕】17-InnoDB邏輯儲存結構

從 InnoDB儲存引擎的邏輯儲存結構看,所有資料都被邏輯地存放在一個空間中,稱之為表空間( tablespace)。表空間又由段(segment)、區( extent)、頁(page)組成。頁在一些文件中有時也稱為塊( block), InnoDB儲存引擎的邏輯儲存結構大致如圖所示。

1、表空間

表空間可以看做是InnoDB儲存引擎邏輯結構的最高層,所有的資料都存放在表空間中。在預設情況下 InnoDB儲存引擎有一個共享表空間 ibdata1,即所有資料都存放在這個表空間內。如果使用者啟用了引數 innodb_file_per_table,則每張表內的資料可以單獨放到一個表空間內。 如果啟用了 innodb_file_per_table的引數,需要注意的是每張表的表空間記憶體放的只是資料、索引和插入緩衝 Bitmap頁,其他類的資料,如回滾(undo)資訊,插入緩衝索引頁、系統事務資訊,二次寫緩衝( Double write buffer)等還是存放在原來的共享表空間內。這同時也說明了另一個問題:即使在啟用了引數 innodb_file_per_table之後,共享表空間還是會不斷地增加其大小。可以來做一個實驗,在實驗之前已經將 innodb_file_per_table設為ON了。現在看看初始共享表空間檔案的大小:

可以看到,共享表空間 ibdata1的大小為58MB,接著模擬產生undo的操作,利用生成的表 mytest,並把其儲存引擎更改為 InnoDB,執行如下操作:

這裡首先將自動提交設為0,即使用者需要顯式提交事務(注意,在上面操作結束時,並沒有對該事務執行 commit或 rollback)。接著執行會產生大量undo操作的語句 update mytest set salary=0,完成後再觀察共享表空間,會發現 ibdata1已經增長到了114MB。這個例子雖然簡單,但是足以說明共享表空間中還包含有undo資訊有使用者會問,如果對k這個事務執行 rollback, ibdata1這個表空間會不會縮減至原來的大小(58MB)?這可以通過繼續執行下面的語句得到驗證:

很“可惜”,共享表空間的大小還是114MB,即 InnoDB儲存引擎不會在執行 rollback時去收縮這個表空間。雖然 InnoDB不會回收這些空間,但是會自動判斷這些undo資訊是否還需要,如果不需要,則會將這些空間標記為可用空間,供下次undo使用。 回想一下,在master thread每10秒會執行一次的 full purge操作,很有可能的一種情況是:使用者再次執行上述的 UPDATE語句後,會發現 ibdata1不會再增大了,那就是這個原因了。 py_innodb_page_info小工具(https://github.com/jameslcj/david-mysql-tools/tree/master/py_innodb_page_type

),用來查看錶空間中各頁的型別和資訊。使用方法如下:

可以看到共有83584個頁,其中插人緩衝的空閒列表有204個頁、5467個可用頁、38675個undo頁、39233個數據頁等。使用者可以通過新增v引數來檢視更詳細的內容。由於該工具還在開發之中,因此並不保證在本書出版時此工具最終顯示結果的變化。

2、段

表空間是由各個段組成的,常見的段有資料段、索引段、回滾段等。 因為前面已經介紹過了 InnoDB儲存引擎表是索引組織的( index organized),因此資料即索引,索引即資料。那麼資料段即為B+樹的葉子節點(的 Leaf node segment),索引段即為B+樹的非索引節點(圖的 Non-leaf node segment)。回滾段較為特殊,將會在後面的章節進行單獨的介紹。 在 InnodB儲存引擎中,對段的管理都是由引擎自身所完成,DBA不能也沒有必要對其進行控制。這和 Oracle資料庫中的自動段空間管理(ASSM)類似,從一定程度上簡化了DBA對於段的管理。

3、區

區是由連續頁組成的空間,在任何情況下每個區的大小都為1MB。為了保證區中頁的連續性, InnoDB儲存引擎一次從磁碟申請4~5個區。在預設情況下, InnoDB儲存引擎頁的大小為16KB,即一個區中一共有64個連續的頁。

InnoDB 1.0.x版本開始引人壓縮頁,即每個頁的大小可以通過引數 KEY_BLOCK_SZE設定為2K、4K、8K,因此每個區對應頁的數量就應該為512、256、128。 InnodB 1.2.x版本新增了引數 innodb_page_size,通過該引數可以將預設頁的大小設定為4K、8K,但是頁中的資料不是壓縮。這時區中頁的數量同樣也為256、128。總之,不論頁的大小怎麼變化,區的大小總是為1M。 但是,這裡還有這樣一個問題:在使用者啟用了引數 innodb_file_per_talbe後,建立的表預設大小是96KB。區中是64個連續的頁,建立的表的大小至少是1MB才對啊?其實這是因為在每個段開始時,先用32個頁大小的碎片頁( fragment page)來存放資料,在使用完這些頁之後才是64個連續頁的申請。這樣做的目的是,對於一些小表,或者是undo這類的段,可以在開始時申請較少的空間,節省磁碟容量的開銷。這裡可以通過一個很小的示例來顯示 InnoDB儲存引擎對於區的申請方式:

mysql> create table t1(
    -> col1 int not null auto_increment,
    -> col2 varchar(7000),
    -> primary key(col1)) engine=InnoDB;
Query OK, 0 rows affected (0.03 sec)

mysql> system sudo ls -lh /usr/local/mysql/data/test_mybatis/t1.ibd
-rw-r-----  1 _mysql  _mysql    96K 10 11 18:04 /usr/local/mysql/data/test_mybatis/t1.ibd

上述的SQL語句建立了t表,將col2欄位設為 VARCHAR(7000),這樣能保證個頁最多可以存放2條記錄。通過ls命令可以發現,初始化並建立tl表後,表空間預設大小為96KB,接著執行如下SQL語句:

mysql> insert into t1 select null,repeat('a',7000);
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> insert into t1 select null,repeat('a',7000);
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> system sudo ls -lh /usr/local/mysql/data/test_mybatis/t1.ibd
-rw-r-----  1 _mysql  _mysql    96K 10 11 18:18 /usr/local/mysql/data/test_mybatis/t1.ibd

插入兩條記錄,根據之前對錶的定義,這兩條記錄應該位於同一個頁中。如果這時通過 py_innodb_page_info工具來查看錶空間,可以看到:

 ./py_innodb_page_info.py -v /usr/local/mysql/data/test_mybatis/t1.ibd

這次用-v詳細模式來看錶空間的內容,注意到了 page offset為3的頁,這個就是資料頁。 page leve表示所在索引層,0表示葉子節點。因為當前所有記錄都在一個頁中,因此沒有非葉節點。但是如果這時使用者再插入一條記錄,就會產生一個非葉節點。

現在可以看到 page offset為3的頁的 page level由之前的0變為了1,這時雖然新插入的記錄導致了B+樹的分裂操作,但這個頁的型別還是B- tree Node接著繼續上述同樣的操作,再插入60條記錄,也就是說當前表t中共有63條記錄,32個頁。

可以看到,在匯入了63條資料後,表空間的大小還是小於1MB,即表示資料空間的申請還是通過碎片頁,而不是通過64個連續頁的區。 可以觀察到B- ree Node頁一共有33個,除去一個 page level為1的非葉節點頁,共有32個 page level為0的頁,也就是說,對於資料段,已經有32個碎片頁了。之後使用者再申請空間,則表空間按連續64個頁的大小開始增長了。好了,接著就這樣來操作,插入一條資料,看之後表空間的大小:

因為已經用完了32個碎片頁,新的頁會採用區的方式進行空間的申請,如果此時使用者再通過 py_ innodb page info工具來看錶空間檔案tbd,應該可以看到很多型別為Freshly Allocated Page的頁。

4、頁

同大多數資料庫一樣, InnoDB有頁(Page)的概念(也可以稱為塊),頁是 InnoDB磁碟管理的最小單位。在 InnoDB儲存引擎中,預設每個頁的大小為16KB。而從InnoDB1.2.x版本開始,可以通過引數 innodb_page_size將頁的大小設定為4K、8K、16K。若設定完成,則所有表中頁的大小都為 innodb_page_size,不可以對其再次進行修改。除非通過 mysqldump導人和匯出操作來產生新的庫。 在 InnodB儲存引擎中,常見的頁型別有

  • 資料頁(B- tree Node)
  • undo頁( undo Log Page)
  • 系統頁( System Page)
  • 事務資料頁( Transaction system Page)
  • 插入緩衝點陣圖頁( Insert Buffer Bitmap)
  • 插入緩衝空閒列表頁( Insert Buffer Free List
  • 未壓縮的二進位制大物件頁( Uncompressed BLOB Page)
  • 壓縮的二進位制大物件頁( compressed BLOB Page)  

5、行

InnoDB儲存引擎是面向列的(row- oriented),也就說資料是按行進行存放的。每個頁存放的行記錄也是有硬性定義的,最多允許存放16KB/2-200行的記錄,即7992行記錄。這裡提到了 row-oriented的資料庫,也就是說,存在有 column- oriented的資料庫。 MySQL infobright儲存引擎就是按列來存放資料的,這對於資料倉庫下的分析類SQL語句的執行及資料壓縮非常有幫助。類