1. 程式人生 > >MySQL表結構,表空間,段,區,頁,MVCC

MySQL表結構,表空間,段,區,頁,MVCC

索引組織表(IOT表):為什麼引入索引組織表,好處在那裡,組織結構特點是什麼,如何建立,建立IOT的限制LIMIT。

IOT是以索引的方式儲存的表,表的記錄儲存在索引中,索引即是資料,索引的KEY為PRIMARY KEY。資料的查詢可以通過查詢索引的同時查詢到資料,因為索引和資料儲存在一個數據塊中,減少了一次磁碟I/O。資料是按照主鍵順序建立的索引,索引中有對應的資料,這樣依據主鍵做範圍掃描時,減少了讀取的資料塊數量,減少了磁碟I/O。也減少了索引的儲存空間,因為索引和資料存在一起。如果是B樹索引就需要建立對索引的儲存空間。

兩個好處:

一個是減少了範圍掃描的磁碟I/O資料塊數(頁塊中有資料,有索引)
一個是避免了索引自身的空間開銷,因為索引和資料在一起,不需要額外的空間。這些優點都是索引組織表的特點決定。
何時使用IOT:

(1)資料的相關資料片需要儲存在一起。
(2)資料必須按照指定的順序物理儲存。IOT表多用於資訊獲取、空間應用和OLAP應用(OLAP:Online-Analysis Process)(聯機分析處理)。
HOT與IOT

myisam使用的堆組織表(Heap Organize Table, HOT)使用B-tree索引的儲存格式,顯示都是隨機順序。
innodb表是索引組織表(Index Organized Table, IOT),它的索引則是採用 clustered index 方式,因此主鍵會按照順序儲存,每次有記錄有更新時,會重新整理更新其主鍵。因此無論是直接從 myisam 錶轉換過來的,還是後來插入的記錄,顯示時都會按照主鍵的順序。
mysql> select * from duplicate_key;
+----+------+
| id | p_id |
+----+------+
| 2 | 2 |
| 3 | 3 |
| 5 | 5 |
| 4 | 4 |
| 6 | 6 |
| 7 | 7 |
+----+------+
6 rows in set (0.00 sec)
1
2
3
4
5
6
7
8
9
10
11
12
此時的duplicate_key表是myisam引擎的,
update duplicate_key set id=id-1的時候會提示
Duplicate entry ‘4’ for key ‘PRIMARY’錯誤。

update duplicate_key set id=id-1 order by id;
如果這樣做,就不會出錯,原理上面已做出了說明。

如果是innodb引擎就不會出現這樣的情況,因為他的聚集索引儲存方式會按順序來顯示。

在myisam引擎使用的時候如果你delete了其中的幾條資料,這時的表就是一個hole表。
如果你不使用表維護命令進行維護,你新插入的資料就會放到你剛剛刪除的那個位置。

lnnoDB儲存引擎表型別
對比Oracle 支援的各種表型別 ,InnoDB 儲存引擎表更像是 Orale 中的索引組織表 ( index organized table ) 。在InnoDB儲存引擎表中 ,每張表都有個主鍵 ,如果在建立表時沒有顯式地定義主鍵 ( Primary Key ) , 則innoDB儲存引擎會按如下方式選擇或建立主鍵 。

①首先表中是否有非空的唯一索引 ( Unique NOT NULL ),如果有,則該列即為主鍵;
②不符合上述條件,InnoDB儲存引擎自動建立一個 6個位元組大小的指標。
lnnoDB邏輯儲存結構
InnoDB儲存引擎的邏輯儲存結構和 Oracle大致相同 ,所有資料都被邏輯地存放在一個空間中 ,我們稱之為表空間 ( tablespace ) 。表空間又由段 ( segment ) 、區 ( extent ) 、頁 ( page ) 組成 。頁在一些文件中有時也稱為塊(block) , InnoDB儲存引擎的邏輯儲存結構大致如圖4-1所示。

段:也叫表;
區:物理上連續的幾個頁;
頁:16K
表空間
表空間可以看做是InnoDB儲存引擎邏輯結構的最高層 ,所有的資料都是存放在表空間中。已經介紹了預設情況下 InnoDB儲存引擎有一個共享表空間 ibdata1 ,即所有資料都放在這個表空間內 。如果我們啟用了引數innodb_file_per_table ,則每張表內的資料可以單獨放到一個表空間內 。


segment:表;
extent:物理上連續的幾個頁;
page(block):16K
(即:將共享表空間獨立出去 innodb_file_per_table 引數)

對於啟用了innodb_file_per_table的引數選項,需要注意的是 ,每張表的表空間記憶體放的只是資料、索引和插入緩衝 ,其他類的資料,如撤銷( Undo) 資訊、系統事務資訊、 二次寫緩衝 (double write buffer ) 等還是存放在原來的共享表空間內。這也就說明了另一個問題:即使在啟用了引數innodb_file_per_table之後,共享表空間還是會不斷地增加其大小。
看看初始共享表空間檔案有多大 :

mysql> show variables like %innodb_file_per_table%';
1


mysql>system ls -lh /var/lib/mysql/ib*
1


mysql
做insert時:要做索引。

IOT(Index Orangized Table,索引組織表)的特點:

1.表按照主鍵排好序;
2.主鍵上有一棵樹;
3.表本身就是索引;
4.葉子節點就是資料節點;
5.IOT對於通過主鍵找表資料的成本最低。
所以,表選擇主鍵的時候:

1.表上有明顯的訪問條件,會員條件、會員id;
2.這個列資料唯一;
假設沒有上面的條件,我們也要選擇一個依次遞增的數字列來作為主鍵列;
假設沒有選擇主鍵,mysql會自動建立一個隱含的主鍵,預設6個位元組。
【非空+唯一==主鍵】
主鍵的選擇最好不要出現過度跳躍的情況。特別是對於insert速度要求很高的系統。對於select要求很高的系統就無所謂了。

IOT表的特點:

①對於insert資源消耗相對大;
②特別是批量insert;
通過索引從表中取20個數據 :
①走索引可能效果很差
②全表掃描效果也不好
③這時,IOT就開始啟命令了。【沒有索引和跳的環節~!】

IOT特別適合的場景:
從表中批量取資料,這個條件必須是主鍵列條件。

如何解決批量insert?
答:讓insert主鍵依次遞增。

表空間:表、索引、insert buffer bitmap【記錄二級索引每一個數據頁的空閒空間剩餘情況】
【知道:計算機裡很多地方用bitmap(點陣圖)來儲存東西。】

共享表空間:

undo
insert buffer
double write(2M)
system transaction table(系統事務表16K)
undo可能會導致ibdata共享表空間變得很大很大。。所以,在安裝好時,先把undo獨立出去。

 

將undo獨立出去:刪除資料庫,重新安裝,重新初始化:
改引數:
directory:指定目錄(絕對路徑)
tablespaces:>0,eg:1,2,3

undo可能會變得很大
1.大事務
一個事務中有大量的dml,產生了大量的undo資料;
2.長事務
事務開始後,長時間不提交;

Compact行記錄格式
Compact行記錄是在MySQL 5 .0時被引入的,其設計目標是能高效存放資料。簡單來說,如果一個頁中存放的行資料越多,其效能就越高 。Compact 行記錄以如下方式進行儲存:

 

從圖4-2可以看到 ,Compact行格式的首部是一個非 NULL變長宇段長度列表 ,而且是按照列的順序逆序放置的 。當列的長度小於 255 位元組 ,用 1位元組表示,若大於255個位元組, 用2個位元組表示,變長宇段的長度最大不可以超過2個宇節( 這也很好地解釋了為什麼 MySQL中varchar 的最大長度為65535 ,因為2個位元組為 16位 ,即2^16=1=65535 ) 。第二個部分是NULL標誌位,該位指示了該行資料中是否有 NULL值 ,用1表示。該部分所佔的位元組應該為 bytes 。接下去的部分是為記錄頭資訊(record header),固定佔用5個位元組(40位),每位的含義見表 4-1。最後的部分就是實際儲存的每個列的資料了 ,需要特別注意的是 , NULL不佔該部分任何資料 ,即NULL 除了佔有NULL標誌位 ,實際儲存不佔有任何空間 。 另外有一點需要注意的是,每行資料除了使用者定義的列外 ,還有兩個隱藏列 ,事務ID列和回滾指標列 ,分別為6個位元組和7個位元組的大小 。若InnoDB 表沒有定義Primary Key ,每行還會增加一個6位元組的RowID列。


(頭資訊共5B,即40位(bit))

如何評估ddl語句對錶的操作風險
eg:
select t-bir from t where id=100;
需要讀兩個列:t_bir,id
描述:假設這個表有4個列,id(9B),varchar(20B),日期(8B),varchar(20B)
在第一行,變長欄位長度列表記錄了varchar的大小,NULL標誌位記錄這行是否有空值,記錄頭資訊,列資料1,列資料2……

DDL操作對錶的風險分析:
修改表的結構(增加列):

①alter table t1 add column (desc varchar(30));
鎖住整個表,假設有1000萬行。
②對列重新命名 alter table t1 rename column t_bir t_birthday;
ddl操作會鎖住表!
要注意,是否意味著對所有資料行進行處理?
是的話,就不要做DDL;
不是的話,就可以。
【一般,修改列的長度、重新命名、增加列啥的,時間都很長,讀寫很大,且會鎖住表。然後生產環境就不能用了。。。】
MySQL如何減少delete操作對undo空間的佔用。
對oracle來說,delete操作會記錄在undo中。而對於MySQL來說,5位元組的記錄頭資訊裡,deleted_flag(1位)記錄的就是該行是否已被刪除,從而減少大量的undo使用;next_record(16位)記錄的是頁中下一條記錄的相對位置,便於從IOT表裡面一行行的掃描。

【上邊提到的兩個隱藏列:事務ID列(6B)和回滾指標列(7B)】

詳細描述一下rollback的過程:系統事務表、回滾段、回滾段頭、事務槽、事務資料塊連結串列。

我們開啟一個事務(start transaction),這個事務會被分配一個事務id:

show engine innodb status \G
1

(事務號是1716486,已經活躍20s了)

ibdata裡面的 系統事務表:

回滾表空間
回滾段
事務
一個事務是怎樣開始的:

1.生成一個事務id;
2.讀取系統事務表,找到一個回滾段(回滾段相對空閒),讀取段頭塊,段頭裡面找到空閒的一行,把事務ID寫進去,一個事務就這樣開始了。


解析:
事務開始時,生成一個事務ID,讀取系統事務表,找到一個空閒的undo段,讀取段頭塊,段頭裡面找到空閒的一行,把事務ID寫進去,一個事務就這樣開始了。
當修改資料行時,①事務ID會寫到修改的資料行裡②資料行的修改前的資料會儲存到undo段的資料頁③修改的資料行裡面的回滾指標同時會指向②所對應的undo頁。
這個事務沒有提交,還沒結束。此時去修改別的資料行,它們也會有自己對應的undo頁,這些同一個事務的undo頁會一個個的連起來(事務資料塊連結串列)。而段頭的第一個事務槽會指向最後一個undo頁(事務資料塊連結串列的末尾),而undo頁依次向前指。因為這樣rollback的時候就會逆著回滾(修改時是順序,回滾當然是逆序了…)
詳細描述MySQL如何實現讀已提交資料的過程:活動事務、roll pointer、事務ID。

 

讀這行資料的時候,會先找事務ID,看事務有沒有提交,讀事務槽就可以。(因為當前系統未提交的資料、事務的狀態等都在事務槽存著呢)。
再通過回滾指標roll pointer 找修改前的資料。


常見的頁型別有 :

資料頁 ( 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 )
InnoDB資料頁結構
我們已經知道頁是 InnoDB儲存引擎管理資料庫的最小磁碟單位。頁型別為B-tree node 的頁,存放的即是表中行的實際資料了。我們將從底層具體地介紹InnoDB資料頁的內部儲存結構 。

InnoDB資料頁由以下七個部分組成:

File Header (檔案頭)
Page Header ( 頁頭)
Infimun + Supremum Records
User Records (使用者記錄 ,即行記錄)
Free Space (空閒空間)
Page Directory (頁目錄)
File Trailer (檔案結尾資訊)

File Header 、Page Header 、File Trailer的大小是固定的,用來標示該頁的一些資訊,如Checksum 、資料所在索引層等 。其餘部分為實際的行記錄儲存空間,因此大小是動態的。
深刻理解varcahr資料型別,特別是最大長度、M的含義。
【PS:utf8:一個字元對應2-3個位元組;gbk一個字元對應2個位元組。】

如果建立varchar長度為65535的表,會報下面的錯誤:

這是因為還有別的開銷,因此實際能存放的長度為65532.

注意!65532指的是所有的varchar列加起來也不能>=65532!!!

varchar(M)
M:最大字元長度。列儲存的字元數不能超過M。
例如 varchar(20) –》這個列最長可以儲存20個字元
如果用的是gbk字符集:則是40個位元組
如果用的是utf8字符集:則是40-60個位元組
如果用的是ascii字符集:則是20個位元組
====M的範圍
注意:M最大不能超過65532!!65532的含義是位元組的含義。
即,如果我們使用gbk字符集,則M不能超過65532/2;
===怎麼測試:
①create table t1(name varchar(65529),name1 varchar(2)) CHARACTER SET ascii;
如果現實ok,則行。否則會報錯說超出了。
②create table t2(name varchar(65532),name1 blob(65535)) CHARACTER SET ascii;
最大行長度的定義,包括和不包括blob的含義。
定義行列時要注意:
整體行的最長長度要小於65535,每個列還要小於65532位元組!

blob(Binary Large Object,二進位制大物件)
BLOB型別的欄位用於儲存二進位制資料 ;
MySQL中,BLOB是個型別系列,包括:TinyBlob、Blob、MediumBlob、LongBlob,這幾個型別之間的唯一區別是在儲存檔案的最大大小上不同。

MySQL的四種BLOB型別 :

型別 大小(單位:位元組)
TinyBlob 最大 255
Blob 最大 65K
MediumBlob 最大 16M
LongBlob 最大 4G
blob不佔整體行長度!實際blob佔用768位元組。即行的實際長度=65535-768;
blob可以存字元、圖片、檔案。

表設計的時候,最基本的原則。簡述垂直拆分,做一個垂直拆分的例子,並對垂直拆分的表進行訪問。
答:
(1)除了blob以外,行長度不要超出16K。而且要遠遠的小於16K,甚至於只有幾百位元組。
如果一個行的長度確實很長(但肯定不會>16K),我們會對這個表進行垂直拆分。
確保一個數據頁中能夠儲存足夠多的資料行。
(2)垂直拆分的例子:
create table t1(id int primary key,name varchar(20),name1 varchar(100));
拆分為:
create table t1(id int primary key,name varchar(20));
create table t1_name1(id int primary key,name1 varchar(100));

訪問的時候,用上圖中的where t1.id=t1_name.id 即可。
行溢位的情況及應對措施:
①行太長了,列又多,就會出現多個列溢位;(設計問題,建議重做表,再做垂直拆分)
②行中有一列是varchar,假設是放描述產品資訊的,可能varchar列就長了。(不是設計問題,建議做垂直拆分,把這個列單獨拿出去做個新表)
③行中有blob列(說明存的資料比較大)。一般建議做垂直拆分,把blob列單獨拆分出去做個新表。
但是!如果每次都要訪問上面的varchar列或者blob列,就不用管了。
checksum技術實現以及意義:
Checksum:【電腦】總和檢驗碼,校驗總和。在資料處理和資料通訊領域中,用於校驗目的的一組資料項的和。這些資料項可以是數字或在計算檢驗總和過程中看作數字的其它字串。
①在Linux中,cksum+具體軟體包名,就能得到一串數。如果得到的數與官網的數是一樣的,就可以明白是安全的、完整的。
②在MySQL中,有個引數 innodb_checksum,該引數為ON時,資料從磁碟到記憶體時,會做checksum,得出一個值,所得值如果跟資料頁的頭部一致,則資料頁沒有損壞。

MySQL五種約束的風險評估:
資料完整性:

實體完整性;
域完整性;
參照完整性;
五種完整性約束:

primary key;主鍵約束
unique key;唯一約束
foreign key;外來鍵約束
default;預設值約束
not null 非空約束
MVCC特性解讀

InnoDB引擎的特點:

1.支援行鎖(各幹各的活,互補影響)、併發效能好;
2.支援MVCC(多版本併發控制Multi-Version Concurrency Control)(避免使用鎖);
3.支援外來鍵;
4.提供一致性非鎖定讀,併發效能更強;
5.能夠使用大記憶體和充分利用cpu資源。
事務開始時,系統事務表以輪詢的方式,找事務表空間裡面的空閒undo段(一共128個),把事務寫到undo段頭的事務槽(共1024個)裡,每個段頭可以寫512個事務,所以,併發時,能寫512*128個事務。
即:系統事務表(輪詢的方式)–回滾段–段頭塊(1024事務槽、512事務)–事務資訊寫入事務槽–一個事務開始了。

事務開始後,假設要修改資料塊,系統會把修改前的資料放到undo塊中,roll pointer回滾指標指向的是對應的undo塊,所有的undo塊會連起來,而事務槽指向的是最後一個undo塊。
【注意!未提交的事務–》在undo塊的事務槽存著!】
commit提交了,做的事:
①在事務槽裡面,把對應的事務標記為事務已提交。

undo的作用:

①rollback
②寫不阻塞讀(防止未提交資料)
③崩潰恢復(redo前滾,undo回滾)
在併發讀寫資料庫時,讀操作可能會不一致的資料(髒讀)。為了避免這種情況,需要實現資料庫的併發訪問控制,最簡單的方式就是加鎖訪問。由於,加鎖會將讀寫操作序列化,所以不會出現不一致的狀態。但是,讀操作會被寫操作阻塞,大幅降低讀效能。
在MVCC協議下,每個讀操作會看到一個一致性的snapshot,並且可以實現非阻塞的讀。MVCC允許資料具有多個版本,這個版本可以是時間戳或者是全域性遞增的事務ID,在同一個時間點,不同的事務看到的資料是不同的。
說白了,其實MVCC多版本併發控制,就是在undo裡面,只要undo空間足夠,就可以儲存資料行的不同時刻的修改情況。所謂的版本,可以理解為不同時間的修改。
即:一直往前找undo。

由於在update操作提交之前,不能影響已有資料的一致性,所以不會改變舊的資料,update操作會被拆分成insert + delete。需要標記刪除舊的資料,insert新的資料。只有update提交之後,才會影響後續的讀操作。而對於讀操作而且,只能讀到在其之前的所有的寫操作,正在執行中的寫操作對其是不可見的。
上面說了一堆的虛的理論,下面來點乾貨,看一下mysql的innodb引擎是如何實現MVCC的。innodb會為每一行新增兩個欄位,分別表示該行建立的版本和刪除的版本,填入的是事務的版本號,這個版本號隨著事務的建立不斷遞增。在repeated read的隔離級別下,具體各種資料庫操作的實現:
select:滿足以下兩個條件innodb會返回該行資料:(1)該行的建立版本號小於等於當前版本號,用於保證在select操作之前所有的操作已經執行落地。(2)該行的刪除版本號大於當前版本或者為空。刪除版本號大於當前版本意味著有一個併發事務將該行刪除了。
insert:將新插入的行的建立版本號設定為當前系統的版本號。
delete:將要刪除的行的刪除版本號設定為當前系統的版本號。
update:不執行原地update,而是轉換成insert + delete。將舊行的刪除版本號設定為當前版本號,並將新行insert同時設定建立版本號為當前版本號。
其中,寫操作(insert、delete和update)執行時,需要將系統版本號遞增。
由於舊資料並不真正的刪除,所以必須對這些資料進行清理,innodb會開啟一個後臺執行緒執行清理工作,具體的規則是將刪除版本號小於當前系統版本的行刪除,這個過程叫做purge。
通過MVCC很好的實現了事務的隔離性,可以達到repeated read級別,要實現serializable還必須加鎖。
長事務和大事務的危害及處理方式
大事務(一次修改大量行。生產中很少)的危害:
會佔用過多的undo頁,

長事務:開始一個事務,一直不提交。
長事務的危害:
假設昨天九點的時候開始了一個事務,但沒有提交。由於MVCC機制,一直到今天的九點之前,24小時內所做的所有的DML操作,都不會被看到(原來是1萬行,現在看,還是1萬行)。而且會產生過多的undo資料(幾十G)且不能被清空(purge)。
怎麼看當前系統的長事務和大事務?

information_schema #存放的是資料字典
INNODB_TRX #存放的是當前活動的事務
select * from INNODB_TRX \G #查詢當前有哪些活動的事務


(modified:1000 #這個事務修改了1000行)
(trx_rows_locked:1022 #鎖了1022行)

如何處理?

大事務的處理:已經影響生產了,因為回滾太慢,所以

①ps -ef|grep mysql
②kill -9 程序號(不建議)
長事務的處理:

①select * from INNODB_TRX \G #檢視事務執行緒的id;
②kill 執行緒id
---------------------
作者:請記得我的好
來源:CSDN
原文:https://blog.csdn.net/qq_18312025/article/details/78658171
版權宣告:本文為博主原創文章,轉載請附上博文連結!