《MySQL技術內幕二》-InnoDB存儲結構與表分區

《MySQL技術內幕-InnoDB存儲引擎》學習筆記二


2019-06-25 ╮(╯▽╰)╭ 撿起來,繼續學習

第4章 表 的東東

InnoDB存儲結構

這裏有幾個部分組成,層級包含:表空間(tablespace),段(segment),區(extent),頁(page) 。

表空間

InnoDB默認共享表空間爲data 目錄下的ibdata1,在開啓innodb_file_per_table參數之後【每個表有獨立的表空間】,獨立表空間只保存表的數據、索引、插入緩存,而其它的例如:回滾信息(undo),系統事務信息等還在這個共享表空間裏面的。

默認共享表空間,在增大之後,是不會變小的,多出來的空間只會標記可用,但不會收回的。

:分爲:數據段,索引段,回滾段等。這個由系統本身自動管理,管他的了 (ノ`Д)ノ

:由連續的頁組成的空間,默認每個區都是1MB大小,(默認頁爲16KB,所以一個區可以有64頁)

新表初始大小小於1MB(96KB),是由於第一個區裏有32頁大小的碎片頁,使用完了之後纔會一個區一個區的申請空間。

:也被稱爲塊,爲最小的磁盤管理單位,默認16KB(可設置但不可修改)。
有許多的類型,例如:數據頁(B-tree Node),undo頁(undo Log page),事務數據頁,壓縮/未壓縮二進制大對象頁等等。

:InnoDB是面向行的(row-oriented),數據是按行進行存放的。每頁最多允許存放16KB/2-200 = 7992行數據。

Mysql 中的infobright存儲器就屬於column-oriented(面向列)的。還有:Sybase IQ、Google Big Table;

關於每頁最多允許存放16KB/2-200 = 7992行數據說明:

我認爲這個確實是內核定義的,在InnoDB存儲引擎數據頁結構中的page Header 中有參數位:PAGE_N_HEAP,佔2字節,表示堆中的記錄數,其中第15位表示行記錄格式。而-200位系統預留。所以數據爲只有14位,就是16KB/2。

InnoDB 行記錄格式

就是每一行數據的格式吧╮(╯_╰)╭: 分爲兩種:Compact(默認) 和 Redundant 兩種。

show table status like 'table_name' 中的row_format屬性就是這個東東。

Compact模式下null是不佔空間的,只是佔NULL標誌偏移量的大小。(空列數/8 bit)
Redundant 模式下NULL的CHAR是佔最大位的空間的,VACHAR也是不佔空間的。

其它行記錄格式:
Compressed和Dynamic:使用的完全行溢出的方式存儲BLOB數據,只存放20字節的指針。
Compressed:行數據會以zlib算法進行行壓縮,對BLOB、TEXT、VARCHAR 能有效的存儲。

行溢出數據

數據頁默認的大小是16KB(6384字節),而定義的VARCHAR行長度大小65535字節,這裏會存在一個也放不下的情況,於是數據會被放到大對象頁中(Uncompressed BLOB Page),原數據中保留768字節+偏移量;

VARCHAR最大長度問題:
定義,VARCHAR所在行可以存放6384字節,然而實際有行頭數據開銷,最大值爲65532字節
需要注意的是這裏不是單個列的長度。

數據是否溢出使用大對象頁存儲:
由於數據存儲使用的是B+Tree的結構,一個頁中至少要有兩個節點,並且頁大小爲16KB。
所以,這個閾值是8098字節,小於此值當行數據會存儲在本身頁中,大於這個值則會使用BLOB頁進行存儲,(這時原行數據只存儲前768字節數據+BLOB頁的偏移量)

關於CHAR 的行存儲結構

在變長字符集的(例如:UTF8)的情況下,InnoDB對CHAR的存儲也是看做變長字符類型的,與VARCHAR沒有區別的。

InnoDB 數據頁結構

這一部分沒辦法┐(゚~゚)┌整理啊,完全就是解釋如何看懂InnoDB的二進制存儲文件的。根據行頁的格式,來分析數據二進制存儲的格式。(⊙_⊙) 略過略過。。。。

查詢當前文件格式:show variables like 'innodb_file_format%'【按字母表,包含之前版本格式】

約束

MySQL的約束的使用只是用來做數據校驗,並沒有生成索引(這兩個是不一樣的,當然主鍵和唯一鍵本身是包含了兩者)

常用的約束有:主鍵,唯一鍵,外鍵(忽略基本不用),默認值,非空。

校驗的約束:可以使用觸發器來完成對數據的高級自定義的約束。(一些數據的操作日誌可以由觸發器生成)

注意:約束的使用在沒有開啓嚴格審覈的情況下,數據還是會插入,只是會報個警告而已。
(這裏數據會被自動轉成0【非空約束】,0000-00-00【時間格式】等合法的奇怪的值插入)

需要設置:sql_mode = STRICT_TRANS_TABLES (參考官方文檔還要很多可設置的值 )

視圖

普通視圖,作爲虛表的存在提供一個數據處理結果集,僅此而已。

而物化視圖,MySQL不支持,╮(╯_╰)╭ 沒什麼說的了。

這裏的視圖是不存在索引的東東的,所以在大數據量的情況下 查詢性能堪憂。
即使是Oracle支持的物化視圖,也是沒有索引的,而且在做數據主備同步的時候可能存在問題,還有就是本身對聚合函數的限制。

分區表 【重點】

先說結果:分區對查詢性能上的優化非常的有限,並且有很多的限制。
(個人看完認爲需要在非常特殊的情況下使用纔可以:數據量大並且完全依據時間查詢等的情況下)

查看是否可以使用分區:show variables like '%partition%' 或者 show PLUGINS;

一些介紹的東西

MySQL所支持的分區爲水平分區(即同表的不同行記錄分配到不同的物理文件中),而且是局部分區索引(即數據和索引統一存放)。

所謂的分區,就是在原本默認一個表一個數據文件table_name.ibd 的情況下依據條件,拆分存放好幾個數據文件-例如:table_name#p#p0.ibd table_name#p#p1.ibd table_name#p#p2.ibd

MySQL支持的分區類型:

  • RANGE分區:給定區間範圍的分區
  • LIST分區:離散值的分區
  • HASH分區:根據自定義表達式返回值進行分區
  • KEY分區:Mysql數據庫提供哈希函數進行分區
  • COLUMNS分區:爲最新支持的分區,分RANGE COLUMNS 和 LIST COLUMNS 分區,支持所有類型。

**注意:**表中如果有主鍵或者唯一索引時,分區必須是唯一索引的一個組成部分。

各個分區的詳解

這個就直接看創建分區的SQLl吧,沒有多少的東西。

分區查詢效率分析命令:EXPLAIN PARTITION ....SELECT ........

查詢分區信息:(使用 information_schema 架構下的 PARTITIONS表)

select * from information_schema.PARTITIONS 
where table_schema = database() and table_name='table_name';

RANGE分區:

CREATE TABLE sales (
money INT UNSIGEND NOT NULL,
date DATETIME
)ENGINE=INNDB
PARTITION BY RANGE (YEAR(DATE))(   --只支持int
    -- 比如這裏如果使用by range (year(date)*100+month(date)) 是不會根據這個進行選擇分區的
PARTITION P0 VALUES LESS THEN (2009),  -- NULL 值屬於小於0的值
PARTITION P1 VALUES LESS THEN (2010),  -- 表示date爲2010的數據會存在p1分區的文件裏面
PARTITION P2 VALUES LESS THEN maxvalue,
)
-- 創建表sales ,以時間進行分區。
-- 這裏RANGE分區,只支持year(),to_days(),to_seconds(),unix_timestamp() 方法。除了這些,使用其他的方法,結果查詢的時候並不會根據分區進行選擇。
-- 單獨添加分區 像↓↓↓↓這樣↓↓↓↓↓↓
ALTER TABLE sales ADD PARTITION (PARTITION p3 values less then (2011); 

LIST分區:

CREATE TABLE sales (
money INT UNSIGEND NOT NULL,
date DATETIME
)ENGINE=INNDB
PARTITION BY LIST (money)( 	--只支持int
PARTITION P0 VALUES VALUES IN (1,3,5,7,9),       --表示money 的值在這個列表裏面則數據在p0分區文件裏面
PARTITION P1 VALUES VALUES IN (0,2,4,6,8,null)   --這裏NULL是單獨的值
)

HASH分區:

CREATE TABLE sales (
money INT UNSIGEND NOT NULL,
date DATETIME
)ENGINE=INNDB
PARTITION BY HASH (YEAR(date)) 	--只支持int  (還有一個 LINEAR HASH)  對值的哈希值來分區
PARTITIONS 4 ;   --設置4個分區

KEY分區:

.......--與HASH類似 只是使用內部的哈希函數了
PARTITION BY KEY (date) 	
PARTITIONS 4 ;   --設置4個分區

到這裏的分區的條件都必須是整形的(不是的要用函數處理成整形的);

新增分區類型:COLUMNS

MySQL5.5開始的,支持所有的整形類型,日期類型(DATE,DATETIME),字符串類型(BLOB,TEXT不支持)

.........  --RANGE COLUMNS 
PARTITION BY RANGE COLUMNS (DATE)(   --支持各種類型了 無所謂 就這一點變化
PARTITION P0 VALUES LESS THEN ('2019-01-01')
)
.........  --LIST COLUMNS 
PARTITION BY LIST COLUMNS (city)(   --city VARCHAR(25)
PARTITION P0 VALUES IN ('fuzhou','xiamen')
)
子分區

只是在原有分區上再進行分區,並且雖然有指定數據和索引分開的地址 ,但是是無效的 ╮(╯_╰)╭

--允許在RANGE和LIST 分區基礎上再進行 HASH或KEY分區
CREATE TABLE sales (
money INT UNSIGEND NOT NULL,
date DATETIME
)ENGINE=INNDB
PARTITION BY RANGE (YEAR(date))(   
SUBPARTITION BY HASH(TO_DAY(date))
SUBPARTITIONS  2 (
    PARTITION P0 VALUES LESS THEN (2009),  
    PARTITION P1 VALUES LESS THEN (2010),
    PARTITION P2 VALUES LESS THEN maxvalue,
); -- 這樣就有 3*2 個分區了
最終關心的性能

在及其嚴格的查詢限制的情況下,可以很好的提升查詢性能。

比如1000W數據量的表,本身使用的查詢列已經有索引了,依據B+樹索引的情況,只要IO搜索2次即可(1000W最多3次)【因爲B+樹的高度也就這麼高】,如果這個時候對這個表做了10個分區,然後查詢的條件不在分區條件中,辣麼這裏就要做全表10*2 至少20次IO才能查出數據。

是不是不好理解,直接看個例子,以上面子分區的表作爲例子

-- 條件設置數據量 1000W+  每一年的數據 100W 並且分配了2009-2019 共 (11*2)22個分區
栗子1:
查詢:select * from sales where year(date) = 2010 and  money = 10000; 
結果: 查詢分區 p0 中的 兩個子分區; 
栗子2select * from sales where money = 10000; 
結果: 查詢全部 22個分區;
-- 這是不考慮索引的,如果 money 有建立索引,也是一樣的,數據庫索引和數據 也是一起分區了的 ╮(╯_╰)╭
-- B+樹索引查詢IO次數:栗子1:2*2 4次; 栗子2:22*2 44次;【非常的明顯啊】

這裏的條件限制大概是這個樣子:

需要分區條件與查詢條件一致,並且大量的數據可以根據此條件 至 查詢數量有很大的減少。


好了,看到這裏發現完全沒法提高性能,連原先以爲的分區也有很大的限制 ε=(´ο`*)))唉

2019-07-05 小杭

希望之後的索引算法的章節可以提高性能吧。。。。。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章