索引組織表(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)
此時的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%';
mysql>system ls -lh /var/lib/mysql/ib*
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
(事務號是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