1. 程式人生 > 其它 >10--innodb儲存引擎表空間詳解

10--innodb儲存引擎表空間詳解

目錄

一 innodb儲存引擎表空間詳解

1 .段、區、頁、行

之前我們提過msyql中的庫、表、記錄行與我們自己操作的資料夾、檔案、檔案行的對應關係

庫 -------------> 資料夾
表 -------------> 檔案
row(記錄行) ---> 檔案中的一行內容

當時我們為了方便理解,對於資料庫中的一張表

# 庫:db1
use db1;

# 表:t1
create table t1(id int,name varchar(16),age int);

# 記錄行
insert t1 values
(1,"egon",18),
(2,"tom",19),
(3,"jack",20);


mysql> select * from t1;
+------+------+------+
| id   | name | age  |
+------+------+------+
|    1 | egon |   18 |
|    2 | tom  |   19 |
|    3 | jack |   20 |
+------+------+------+
3 rows in set (0.00 sec)

我們可以理解成,在db1資料夾下有一個文字檔案,檔案中有三行內容

事實上,mysql的儲存引擎中關於表中資料的儲存結構要複雜的多

InnoDB儲存引擎的邏輯儲存結構和 Oracle大致相同 ,所有資料都被邏輯地存放在一個空間中 ,我們稱之為表空間 ( tablespace ) ,表空間又由:段 ( segment ) 、區 ( extent ) 、頁 ( page ) 組成 。頁在一些文件中有時也稱為塊(block)或磁碟塊,一次io操作的是一個磁碟的資料,即一頁資料。

InnoDB儲存引擎的邏輯儲存結構大致如下圖所示

詳解如下

  • Row行

    一個Row存放的是一行內容,有trx id,回滾指標,該行包含的n列內容
    InnoDB儲存引擎是面向行的(row-oriented),也就是說資料的存放是按行進行存放的。
    這裡提到面向行(row-oriented)的資料庫,那麼也就是說,還存在有面向列(column-orientied)的資料庫。MySQL infobright儲存引擎就是按列來存放資料的,這對於資料倉庫下的分析類SQL語句的執行以及資料壓縮很有好處。類似的資料庫還有Sybase IQ、Google Big Table。面向列的資料庫是當前資料庫發展的一個方向。
    
  • Page頁:最多包含7992行記錄

    多個Row組織到一個Page頁中,一個Page頁即一個磁碟塊大小,是io操作的最小物理儲存單元,也就是我們讀取一頁內的資料時候,實際上才發生了一次IO,這個理論對於索引的資料結構設計非常有幫助。
    每個頁存放的行記錄也是有硬性定義的,最多允許存放16KB/2~200行的記錄,即7992行記錄。
    InnoDB儲存引擎page頁的大小為16KB,且不可以更改(也許通過更改原始碼可以)。
    
  • Extent區:由64個連續的頁組成的

    區是由64個連續的頁組成的,每個頁大小為16KB,即每個區的大小為1MB。
    
  • Segment 段 :最多由4個區組成

    對於大的資料段,InnoDB儲存引擎最多每次可以申請4個區,以此來保證資料的順序效能。
    
  • Tablespace 表空間

    表空間由三種段構成
    1、葉子節點資料段:即資料段
    2、非葉子節點資料段:即索引段
    3、回滾段
    

總結:

7992行--->一頁(16kB)

64個頁--->一個區(1MB)

4個區---> 一個數據段(4M)

表空間由三種段構成

1.葉子節點資料段 : 即資料段
2.非葉子節點資料段 : 即索引段
3.回滾段

2. 表空間tablespace

表空間可以看做是InnoDB儲存引擎邏輯結構的最高層,所有的資料都存放在表空間中。表空間的管理模式的出現是為了資料庫的儲存更容易擴充套件,關於表空間我們還需要詳細說一下

  • mysql 5.5版本以後出現共享表空間概念

  • mysql5.6版本中預設的是獨立表空間

  • mysql5.7版本新特性共享臨時表空間

2.1 共享表空間

1)概念

類似於LVM邏輯卷,是動態擴充套件的
預設只有12M,會根據資料的量慢慢變越來越大

優點:可以將表空間分成多個檔案存放到各個磁碟上(表空間檔案大小不受表大小的限制,如一個表可以分佈在不同的檔案上)。資料和檔案放在一起方便管理。

缺點:所有的資料和索引存放到一個檔案中,雖然可以把一個大檔案分成多個小檔案,但是多個表及索引在表空間中混合儲存,這樣對於一個表做了大量刪除操作後表空間中將會有大量的空隙,特別是對於統計分析,日值系統這類應用最不適合用共享表空間。

2)檢視共享表空間

mysql> show variables like '%path%';
+----------------------------------+------------------------+
| Variable_name                    | Value                  |
+----------------------------------+------------------------+
| innodb_data_file_path            | ibdata1:12M:autoextend |
| sha256_password_private_key_path | private_key.pem        |
| sha256_password_public_key_path  | public_key.pem         |
| ssl_capath                       |                        |
| ssl_crlpath                      |                        |
+----------------------------------+------------------------+
5 rows in set (0.01 sec)

3)修改共享表空間

#1.編輯配置檔案
[root@db01 ~]# vi /etc/my.cnf
# 開啟獨享表空間,並指定ibdata1大小為1G,ibdata2大小200M,自動擴張。
[mysqld]
# innodb_data_home_dir = /var/lib/mysql
innodb_data_file_path = ibdata1:1G;ibdata2:200M:autoextend

#配置一個就夠用了,為什麼要配置兩個呢?
第一個共享表空間資料達到1G以後,他會往第二個表空間裡面寫資料,當第二個共享表空間資料也達到2M以後會動態擴容,這個檔案會越來越大,就像日誌一樣;

這樣就會導致一個問題,當越來越多的資料增加的時候,ibdata也會持續膨脹,有的達到幾十G,上百G

那麼,當前儲存資料的磁碟分割槽滿的時候,要怎麼樣去擴充套件資料空間呢?
	
#2.修改完配置檔案重啟
[root@db03 ~]# systemctl start mysqld # 啟動會報錯或者啟動不了
檢視日誌
[root@db03 ~]# less /usr/local/mysql/data/db03.err
2021-07-21 22:26:00 50917 [ERROR] InnoDB: Data file ./ibdata1 is of a different size 768 pages (rounded down to MB) than specified in t
he .cnf file 3200 pages!

#3.報錯說明共享表空間大小與當前已經存在的表空間不一致,我們要把共享表空間修改為當前共享表空間的大小才行
[root@localhost ~]# ll -h /var/lib/mysql/ibdata1 
-rw-rw---- 1 mysql mysql 76M Jul  8 16:57 /var/lib/mysql/ibdata1
[root@localhost ~]# vim /etc/my.cnf
[mysqld]
innodb_data_file_path=ibdata1:76M;ibdata2:200M:autoextend

2.2 獨立表空間

對於使用者自主建立的表,會採用此種模式,每個表由一個獨立的表空間進行管理

優點:

  • 1.每個表都有自已獨立的表空間,易於區分與管理
  • 2.每個表的資料和索引都會存在自已的表空間中。
  • 3.可以實現單表在不同的資料庫中移動。
  • 4.空間可以回收(除drop table操作外,表空不能自已回收)
    • 4.1 Drop table操作自動回收表空間
    • 4.2 如果對於統計分析或是日值表,刪除大量資料後可以通過:alter table TableName engine=innodb;回縮不用的空間。
    • 4.3 對於使innodb-plugin的Innodb使用turncate table也會使空間收縮。
    • 4.4 對於使用獨立表空間的表,不管怎麼刪除,表空間的碎片不會太嚴重的影響效能,而且還有機會處理。

缺點:

  • 1、單表增加過大,如超過100個G。

檢視獨立表空間

#物理檢視
[root@db01 ~]# ll /application/mysql/data/world/
-rw-rw---- 1 mysql mysql 688128 Aug 14 16:23 city.ibd

#命令列檢視
mysql> show variables like '%per_table%';
innodb_file_per_table=ON

二 回滾日誌的物理空間

innodb儲存引擎支援事務,一個事務在執行時會產生大量回滾日誌,即undo log
在6.1小節中我們瞭解到,表空間由三種段構成

1、葉子節點資料段:即資料段
2、非葉子節點資料段:即索引段
3、回滾段

也就是說回滾日誌也是要存放於表空間中的,大家都是混在一起的,這會造成什麼問題呢

1 .undo log表空間

1.1 MySQL 5.5時代的undo log(共享的undo表空間)

在MySQL5.5以及之前,大家會發現隨著資料庫上線時間越來越長,ibdata1檔案(即InnoDB的共享表空間,或者系統表空間)會越來越大,這會造成2個比較明顯的問題:
(1)磁碟剩餘空間越來越小,到後期往往要加磁碟;
(2)物理備份時間越來越長,備份檔案也越來越大。

這是怎麼回事呢?
原因除了資料量自然增長之外,在MySQL5.5以及之前,InnoDB的撤銷記錄undo log也是存放在ibdata1裡面的。一旦出現大事務,這個大事務所使用的undo log佔用的空間就會一直在ibdata1裡面存在,即使這個事務已經關閉。

那麼問題來了,有辦法把上面說的空閒的undo log佔用的空間從ibdata1裡面清理掉嗎?答案是沒有直接的辦法,只能全庫匯出sql檔案,然後重新初始化mysql例項,再全庫匯入。

1.2 MySQL 5.6時代的undo log(獨立的undo表空間)

MySQL5.6中開始支援把undo log分離到獨立的表空間,並放到單獨的檔案目錄下;採用獨立undo表空間,再也不用擔心undo會把 ibdata1 檔案搞大;也給我們部署不同IO型別的檔案位置帶來便利,對於併發寫入型負載,我們可以把undo檔案部署到單獨的高速儲存裝置上,那麼如何在獨立出undo log的表空間呢?

MySQL 5.6在資料庫初始化的時候使用如下三個引數就可以把undo log從ibdata1移出來單獨存放

(1) innodb_undo_directory,指定單獨存放undo表空間的目錄,預設為.(即datadir),可以設定相對路徑或者絕對路徑。如果需要將undo log放到更快的裝置上時,可以設定innodb_undo_directory引數,但是一般我們不這麼做,因為現在SSD非常普及。
該引數例項初始化之後雖然不可直接改動,但是可以通過先停庫,修改配置檔案,然後移動undo表空間檔案的方式去修改該引數;

(2) innodb_undo_tablespaces,指定單獨存放的undo表空間個數,例如如果設定為3,即可將undo log設定到單獨的undo表空間中,undo表空間為undo001、undo002、undo003,每個檔案初始大小預設為10M。該引數我們推薦設定為大於等於3,原因下文將解釋。該引數例項初始化之後不可改動;

(3) innodb_undo_logs,指定回滾段的個數(早期版本該引數名字是innodb_rollback_segments),預設128個,使用預設值即可。每個回滾段可同時支援1024個線上事務。這些回滾段會平均分佈到各個undo表空間中。該變數可以動態調整,但是物理上的回滾段不會減少,只是會控制用到的回滾段的個數。

那麼問題又來了,mysql5.6中undo log單獨拆出來後就能縮小了嗎?答案是不能?

mysql5.6中確實可以把undo log回滾日誌分離到一個單獨的表空間裡,這隻解決了不把ibdata1搞大的問題,至於撤銷記錄依然存在,空間是不能被回收(收縮)的。直到MySQL5.7 ,才支援線上收縮。

1.3 MySQL 5.7時代的undo log(共享臨時表空間)

MySQL 5.7引入了新的引數,innodb_undo_log_truncate,開啟後可線上收縮拆分出來的undo表空間。在滿足以下2個條件下,undo表空間檔案可線上收縮:

(1) innodb_undo_tablespaces>=2。因為truncate undo表空間時,該檔案處於inactive狀態,如果只有1個undo表空間,那麼整個系統在此過程中將處於不可用狀態。為了儘可能降低truncate對系統的影響,建議將該引數最少設定為3;

(2) innodb_undo_logs>=35(預設128)。因為在MySQL 5.7中,第一個undo log永遠在系統表空間中,另外32個undo log分配給了臨時表空間,即ibtmp1,至少還有2個undo log才能保證2個undo表空間中每個裡面至少有1個undo log;

滿足以上2個條件後,把 innodb_undo_log_truncate設定為ON即可開啟undo表空間的自動truncate,這還跟如下2個引數有關:

(1) innodb_max_undo_log_size,undo表空間檔案超過此值即標記為可收縮,預設1G,可線上修改;

(2) innodb_purge_rseg_truncate_frequency,指定purge操作被喚起多少次之後才釋放rollback segments。當undo表空間裡面的rollback segments被釋放時,undo表空間才會被truncate。由此可見,該引數越小,undo表空間被嘗試truncate的頻率越高。

2 MySQL5.6表空間管理

2.1 分離undo log表空間

可以把Undo Log從共享表空間裡ibdata1拆分出去

注意,需要在安裝mysql時,在my.cnf裡指定,否則等建立資料庫以後再指定,就會報錯,如下

mysql> show variables like '%undo%';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| innodb_undo_directory   | .     |
| innodb_undo_logs        | 128   |
| innodb_undo_tablespaces | 0     |
+-------------------------+-------+
3 rows in set (0.00 sec)

mysql> set global innodb_undo_tablespaces=3;
ERROR 1238 (HY000): Variable 'innodb_undo_tablespaces' is a read only variable
mysql> 
已安裝資料庫不能修改 innodb_undo_tablespaces 

我們建立新的空資料目錄,重啟mysql,重新初始化庫,就可以把undo log從共享表空間分離出去了

# 1、建立新的資料目錄並設定許可權
mkdir /var/lib/mysql1
chown -R mysql.mysql /var/lib/mysql1

# 2、修改配置檔案,指向新的數目錄/var/lib/mysql1
vim /etc/my.cnf
[mysqld]
datadir=/var/lib/mysql1
socket=/var/lib/mysql1/mysql.sock
# 共享表空間
innodb_data_file_path=ibdata1:76M;ibdata2:12M:autoextend
# 分離
innodb_undo_logs = 128
innodb_undo_tablespaces = 4

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

# 3、重啟mysql
systemctl restart mysql

便可以看到
[root@localhost ~]# ll /var/lib/mysql1/
-rw-rw---- 1 mysql mysql 10485760 Jul  8 18:16 undo001
-rw-rw---- 1 mysql mysql 10485760 Jul  8 18:16 undo002
-rw-rw---- 1 mysql mysql 10485760 Jul  8 18:16 undo003
-rw-rw---- 1 mysql mysql 10485760 Jul  8 18:16 undo004

undo log建立好後,就不能再次修改,或者增加。

2.2 線上不停機把.ibd資料拷貝到另外一臺機器上(線上遷移表)

從mysql5.6版本開始,引入了表空間傳輸的功能。可以把一張表從一個數據庫移動到另一個數據庫中或者另一臺機器上。使用該功能必須滿足如下條件:

  • Mysql版本必須是5.6及以上
  • 使用獨立表空間方式,現在版本預設開啟innodb_file_per_table
  • 源庫和目標庫的page size必須一致,表結構必須一致
  • 如果要做表的匯出操作,該表只能進行只讀操作

注意:只有獨立表空間才支援資料線上遷移!!!共享表空間不支援線上遷移!!!

假設
源主機:主機A
目標主機:主機B

# 步驟1:在主機A操作
首先為t1表加讀鎖(只能讀,不能寫,目的是保證資料一致性)(在凌晨操作--) 
然後把資料從記憶體匯出到磁碟上。
mysql> flush tables t1 for export;

# 步驟2:在主機B操作
到主機B上,建立與原表一樣的表結構
create table t1(欄位1 型別,欄位2 型別,...);


在主機B上關閉t1表的資料空間,刪除.ibd檔案
mysql> alter table t1 discard tablespace;
[root@db02 db01]# ll
total 16
-rw-r----- 1 mysql mysql   65 Jul 13 15:28 db.opt
-rw-r----- 1 mysql mysql 8556 Jul 13 15:29 t1.frm   #此時t1.ibd檔案已刪除

# 步驟3:在主機A上
將主機A上原表的t1.cfg和t1.ibd拷貝到主機B的資料目錄下。
[root@db01 db01]# scp /var/lib/mysql/db01/dep.ibd [email protected]
:/var/lib/mysql/db01/t1.ibd
注意拷貝的ibd檔案的屬主屬組與許可權問題,新表授權。
[root@db02 db01]# chown -R mysql.mysql /var/lib/mysql
拷貝完後主機A執行UNLOCK TABLES;
mysql> unlock tables;

# 步驟4:在主機B上
執行ALTER TABLE  t1 IMPORT TABLESPACE;就會進行恢復操作。
mysql> alter table t1 import tablespace;
Query OK, 0 rows affected, 1 warning (0.02 sec)

然後check table t1;

沒問題的話,select * from t1;你會發現資料恢復了。
mysql> select * from t1;
+------+
| id   |
+------+
|    1 |
|    2 |
|    3 |
+------+
3  rows in set (0.00 sec)

#注意:遷移表時,兩個表的行格式必須是一樣,5.7以後的行模式預設是Dynamic
mysql> show table status like "dep";   #檢視dep表的行模式
設定行模式,如:create table t1(id int)row_format=Compact   #5.6版本預設compact

示例:基於上述原理完成物理備份與恢復

# 1、安裝mysql5.6+版
[root@localhost ~]# cat /etc/yum.repos.d/mysql.repo 
[mysql56-community]
name=MySQL 5.6 Community Server
baseurl=http://repo.mysql.com/yum/mysql-5.6-community/el/7/$basearch/
enabled=1
gpgcheck=0
[root@localhost ~]# yum install mariadb-server* -y
[root@localhost ~]# yum install mariadb-* -y
[root@localhost ~]# systemctl start mysql
[root@localhost ~]# mysql -uroot
mysql> select version();
+-----------+
| version() |
+-----------+
| 5.6.51    |
+-----------+
1 row in set (0.00 sec)
mysql> 

# 2、設定獨立表空間
vim /etc/my.cnf
[mysqld]
default-storage-engine=innodb
innodb_file_per_table=1

# 3、準備測試資料
create database egon;
use egon;
create table t1(id int);
insert t1 values(1),(2),(3);

# 4、將/var/lib/mysql/目錄下的egon庫打包,並刪除目錄egon
cd /var/lib/mysql/
tar czf egon.tar.gz egon/
rm -rf egon/

# 5、本機測試也行,重啟mysql測試,或者把資料拷貝到另外一臺資料庫伺服器進行解壓測試
[root@localhost ~]# systemctl restart mysql
[root@localhost ~]# mysql -uroot -p
mysql> create database db1;
mysql> create table test(id int); -- 表結構與源應一致
Query OK, 0 rows affected (0.01 sec)

mysql> alter table test discard tablespace; -- 刪除test.ibd檔案
Query OK, 0 rows affected (0.00 sec)
mysql> exit
Bye
[root@localhost ~]# tar xvf egon.tar.gz 
egon/
egon/db.opt
egon/t1.frm
egon/t1.ibd
[root@localhost ~]# cp -a egon/t1.ibd /var/lib/mysql/db1/test.ibd
[root@localhost ~]# 

[root@localhost ~]# mysql -uroot -p
mysql> use db1;
mysql> alter table test import tablespace;
Query OK, 0 rows affected, 1 warning (0.01 sec)

mysql> select * from test;
+------+
| id   |
+------+
|    1 |
|    2 |
|    3 |
+------+
3 rows in set (0.00 sec)

2.3 把ibd檔案建立到其他資料夾下

在之前的版本,採用獨立表空間(.ibd)存放資料時,是不能更改路徑的,比如磁碟滿了,恰巧沒做LVM卷組,那麼通過下述指令

CREATE TABLE t1(id int primary key)engine=innodb DATA DIRECTORY="/egon_data/",

就把建立t1表的.ibd放到了/data2/目錄下。

3 MySQL5.7表空間管理

3.1 MySQL 5.7的undo表空間的truncate示例

(1)安裝mysql5.7

# 1、安裝mysql5.7版
[root@localhost ~]# cat /etc/yum.repos.d/mysql.repo 
[mysql56-community]
name=MySQL 5.7 Community Server
baseurl=http://repo.mysql.com/yum/mysql-5.7-community/el/7/$basearch/
enabled=1
gpgcheck=0
[root@localhost ~]# yum install mysql-* mysql-server* -y

(2) 首先確保如下引數被正確設定:

# 為了實驗方便,我們減小該值 
innodb_max_undo_log_size = 11M 
innodb_undo_log_truncate = ON 
innodb_undo_logs = 128 
innodb_undo_tablespaces = 3 
# 為了實驗方便,我們增大該值 
innodb_purge_rseg_truncate_frequency = 1000

(3)啟動mysqld,並設定密碼

[root@egon ~]# systemctl start mysqld
[root@egon ~]# grep "temporary password" /var/log/mysqld.log # 過濾出隨機密碼
[root@egon ~]# mysql -uroot -p'隨機密碼'
mysql> set password=password("Egon@123"); -- 弱密碼會報錯

(4) 建立表:

[root@egon ~]# mysql -uroot -p'Egon@123'
mysql> create database db1;
mysql> use db1;
mysql> create table t1(id int primary key auto_increment, name varchar(200)); 
Query OK, 0 rows affected (0.13 sec)

(4)插入測試資料

mysql> insert into t1(name) values(repeat('e',200)); 
mysql> insert into t1(name) select name from t1; 
mysql> insert into t1(name) select name from t1; 
mysql> insert into t1(name) select name from t1; 
mysql> insert into t1(name) select name from t1; 


-- 執行n次insert into t1(name) select name from t1; ,直到undo日誌增大超過11M

這時undo表空間檔案大小如下,可以看到有一個undo檔案已經超過了11M:

-rw-r----- 1 mysql mysql  36M Jul  8 20:11 undo001
-rw-r----- 1 mysql mysql  10M Jul  8 20:11 undo002
-rw-r----- 1 mysql mysql  11M Jul  8 20:11 undo003

此時,為了,讓purge執行緒執行,可以執行幾個delete語句:

mysql> delete from t1 limit 1; 
mysql> delete from t1 limit 1; 
mysql> delete from t1 limit 1; 
mysql> delete from t1 limit 1;

過一會之後,再檢視undo檔案大小,可以看到,超過101M的undo檔案已經收縮到10M了。

-rw-r----- 1 mysql mysql  10M Jul  8 20:12 undo001
-rw-r----- 1 mysql mysql  10M Jul  8 20:12 undo002
-rw-r----- 1 mysql mysql  11M Jul  8 20:12 undo003

小練習:

對上述表進行一次全表更新,期間觀察undo表空間一度增長到800多M,更新結束後,表看空間壓縮到10M

mysql> update t1 set name="egon";
Query OK, 4194299 rows affected (2 min 51.00 sec)
Rows matched: 4194299  Changed: 4194299  Warnings: 0