MySQL技術內幕讀書筆記(四)——表

​ 表就是關於特定實體的數據集合,是關係型數據庫模型的核心。mysql

索引組織表

​ 在INNODB存儲引擎中,表都是根據主鍵順序組織存放的。這種存儲方式的表稱爲索引組織表。在INNODB存儲引擎表中,每張表都有個主鍵,若是在建立表時沒有顯式地定義主鍵,則INNODB存儲引擎會按以下方式選擇或建立主鍵。算法

  • 首先判斷表中是否有非空的惟一索引,若是有,則該列爲主鍵。sql

    ​ 表中有多個非空惟一索引時候,引擎選擇建表時第一個定義的非空惟一索引做爲主鍵。數據庫

​ 這個的主鍵是d安全

​ 對於主鍵只有一個的表,可使用_rowid查詢主鍵值架構

SELECT a,b,c,d,_rowid FROM z;​
  • 若是不符合上述條件,INNODB存儲引擎自動建立一個6字節大小的指針。

InnoDB邏輯存儲結構

​ 全部數據都放在表空間中。表空間又由段、區、頁組成。less

表空間

​ 默認狀況下,INNODB存儲引擎有一個共享表空間ibdata1`,即全部數據存放在這個默認表空間。函數

​ 經過參數innodb_file_per_table能夠將每張表內的數據能夠單獨放到一個表空間內。可是單獨表空間內存放的只是數據、索引和插入緩衝Bitmap頁,其餘類的數據,如回滾信息,插入緩衝索引頁,系統事務信息,二次寫緩衝等仍是放在原來的默認空間。性能

​ 常見的段有:數據段、索引段、回滾段等。

​ 數據段是B+樹的葉子節點,索引段是B+樹的非葉子節點。

​ 區是由連續頁組成的空間,在任何狀況下的大小都爲1MB。可是頁的大小可能不一樣。

​ 可是有個問題???新建立的表默認大小是96K,區中是64個連續的頁,建立表至少是1MB啊。其實由於在每一個段開始時,先用32個頁大小的碎片也來存放數據,在使用完這些頁以後纔是64個連續頁的申請。目的是,對於一些小表或者undo這類的段,能夠在開始時申請較少的空間,節省磁盤容量的開銷。

​ 是INNODB磁盤管理的最小單位。能夠經過innodb_page_size將頁的大小設置爲4K、8K、16K。若設置完成,則全部表中頁的大小都爲innodb_page_size,不能夠對其再次進行修改。除非經過mysqldump導入和導出操做來產生新的庫。

​ 常見頁的類型:

  • 數據頁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行記錄格式

​ 記錄數據的行格式有兩種:

  • Compact:新版本
  • Redundant:舊版本
# 經過這個,能夠查看錶使用的行格式,row_format表示行格式。
SHOW TABLE STATUS LIKE 'table_name';

Compact行記錄格式

​ 首部,是一個非NULL變長字段長度列表,而且其是按照列的順序逆序放置的,其長度爲:

  • 列的長度小於255字節,用1字節表示

  • 大於255個字節,用2字節表示

    不可超過2字節,由於MYSQL數據庫中VARCHAR類型的最大長度爲65535.

​ NULL標誌位「1」表示該行數據中有NULL值。

​ 記錄頭信息,固定佔用5字節(40位)

​ 最後的部分是實際存儲每一個列的數據,NULL不佔該部分的任何空間。還有兩個隱藏列,事務ID列和回滾指針列。若沒有主鍵,還會增長一個6字節的rowid列。

​ 使用命令能夠查看頁內容

hexdump -C -v mytest.ibd > mytest.txt



Redundant行記錄格式

​ 首部是一個字段長度偏移列表,按照逆序排放。

​ 記錄頭信息

​ 最後就是列數據了。

行溢出數據

INNODB存儲引擎能夠將一條記錄中的某些數據存儲在真正的數據頁面以外。

​ 研究下VARCHAR數據類型的行溢出行爲~

​ 首先VARCHAR的長度官方說明爲65535,但實際上建立表會報錯,經測試,最大長度爲65532。可是實際上數據庫已經將VARCHAR轉換爲了TEST

​ 可是注意使用的字符類型是latin1,若是改成GBK或者utf-8呢?

​ 也會報錯,說明VARCHAR(N)中的N是指字符的長度。且是整個表全部的列的超度綜合不能超過N。

​ 可是即便可以存放65532個字節,可是一個頁只有16KB(16384字節),所以,在通常狀況下,INNDB存儲引擎的數據都是存放在頁類型爲B-tree node中。可是發生行溢出時,數據存放在頁類型爲Uncompress BLOB頁中。對於行移除數據採用存放方式如圖:

​ INNODB存儲引擎表是索引組織的,即B+Tree機制,因此每一個頁至少要有兩條行記錄(不然回退成列表),這個閾值通過測試後是8098。

​ 類比TEXT或者BLOB的數據類型,跟VARCHAR一致,8098長度以後的才存放在Uncompressed BLOB Page中,不然仍是存放在數據頁中。

CompressedDynamic行記錄格式

​ 原有的CompactRedundant統稱爲Antelope文件格式

​ 新的文件格式CompressedDynamic統稱爲Barracuda

​ 新的兩種記錄格式對於存放子BLOB中的數據採用了徹底的行溢出的方式

​ 而Compressed的另外一個功能就是,存儲在其中的行數據會以zlib的算法進行壓縮。

CHAR的行結構存儲

​ 對於多機子字符編碼的CHAR數據類型的存儲,INNODB存儲引擎在內部視爲VARCHAR實現。

INNODB數據頁結構

​ 由如下七個部分組成:

  • FIle Header文件頭
  • Page Header頁頭
  • InfimunSupremum Records
  • User Records用戶記錄,即行記錄
  • Free Space空閒空間
  • Page Directory頁目錄
  • File Tralier文件結尾信息

約束

數據完整性

​ 關係數據庫自己能保證存儲數據的完整性。使用約束機制來保障完整性。

​ INNODB保障實體完整性:

  • 定義一個Primary KeyUnique Key約束來保障實體的完整性。
  • 還能經過編寫一個觸發器來保障數據完整性

​ 域完整性保證數據每列的值知足特定的條件。經過一下途徑來保障:

  • 選擇合適的數據類型確保一個數據值知足特定條件
  • 外鍵約束
  • 編寫觸發器
  • 使用DEFAULT約束做爲強制域完整性的一個方面。

​ 參照完整性保證兩張表之間的關係。經過外鍵來保障。也能夠經過觸發器來強制執行。

​ 對於INNODB存儲引擎自己而言,提供瞭如下幾種約束:

  • Primary Key
  • Unique Key
  • Foreign Key
  • Default
  • NOT NULL

約束的建立與查找

​ 建立方式:

  • 建立表時定義
  • ALTER TABLE命令來建立約束。

​ 建立主鍵和惟一索引,主鍵約束名爲PRIMARY,惟一索引約束名爲列名。

CREATE TABLE u (
    id int,
    name varchar(20),
    id_card char(18),
    PRIMIARY KEY(id),
    UNIQUE KEY (name)
);

# 查詢約束
select constarint_name, constraint_type
from
information_schema.TABLE_CONSTRAINTS
where table_schema='mytest' AND table_name='u'\G;

​ 可是使用ALTER TABLE的話能夠修改惟一索引的約束名。

# uk_id_card爲約束名  id_card爲列名
ALTER TABLE u
ADD UNIQUE KEY uk_id_card (id_card);

​ 建立Foreign Key約束

CREATE TABLE p(
    id int,
    u_id int,
    primary key (id),
    foreign key (u_id) references p (id)
);

​ 還能夠經過查看錶REFERENTIAL_CONSTRAINTS,而且能夠詳細地瞭解外鍵的屬性。

SELECT * FROM
information_schema.REFERNTIAL_CONSTRAINTS
WHRER constraint_schema='mytest'\G;

對錯誤數據的約束

​ 在某些默認設置下,MYSQL數據庫容許非法的或者不正確的數據的插入或更新,又或者能夠在數據庫內部轉換爲一個合法的值,例如像對NOT NULL的字段插入一個NULL值,MYSQL數據庫會將其改成0再進行插入,所以數據庫自己沒有對數據的正確性進行約束。

​ 可是上述狀況MYSQL會發出警告,可是想設置爲報錯,必須設置參數sql_mode來嚴格審覈輸入的參數。

SET sql_mode = 'STRICT_TRANS_TABLES';

ENUM和SET約束

​ MYSQL數據庫不支持傳統的CHECK約束,可是經過ENUM和SET類型能夠解決部分這樣的約束需求

​ 依舊能夠設置參數sql_mode來嚴格要求

觸發器與約束

​ 建立觸發器的語法

CREATE
[DEFINER = {user | CURRENT_USER}]
TRIGGER trigger_name BEFORE|AFTER   INSERT|UPDATE|DELETE
ON tb1_name FOR EACH ROW 
trigger_stmt

​ 一個表最多建立6個觸發器,也僅支持按每行記錄進行觸發。

​ 能夠經過建立觸發器實現約束的一種手段和方法。

外鍵約束

CREATE TABLE parent(
    id INT NOT NULL
    PRIMARY KEY(id)
)ENGINE = INNODB;

CREATE TABLE child(
    id INT,
    parent_id INT,
    FOREIGN KEY(parent_id) REFERENCES parent(id) ON DELETE RESTRICT
)ENGINE = INNODB

​ 被引用的表爲父表,引用的表稱爲子表。外鍵定義時的ON DELETE 和 ON UPDATE表示在對父表進行DELETE 和 UPDATE操做時,對子表作的操做,能夠定義的子表操做有:

  • CASCADE:父表進行UPDATE或者DELETE操做時,子表也進行這種操做
  • SET NULL:父表發生UPDATE或者DELETE操做時,子表對應的數據設置爲NULL
  • NO ACTION:父表發生DELETE或者UPDATE操做時,拋出錯誤,不容許這類操做發生。
  • RESTRICT:父表發生DELETE或者UPDATE操做時,拋出錯誤,不容許這類操做發生。

視圖

​ 在MYSQL中,視圖是一個命名的虛表,它由一個SQL查詢定義,能夠當作表使用。與持久表不一樣的是,視圖中的數據沒有實際的物理存儲。

視圖的做用

​ 語法:

CREATE VIEW v_t
AS
SELECT * FROM t WHERE id < 0;

​ 被用做一個抽象裝置,也能夠起到一個安全層的做用。

分區表

概述

​ 分區的過程是將一個表或索引分解爲多個更小、更可管理的部分。就訪問數據庫的應用而言,從邏輯上只有一個表或者索引,可是在物理上這個表或者索引可能由數十個物理分區組成。沒個分區都是獨立的對象,能夠獨自處理,也能夠做爲一個更大對象的一部分進行處理。

​ MYSQL數據庫支持的分區類型爲水平分區,即將同一張表中不一樣行的記錄分配到不一樣的物理文件中。MYSQL的分區都是局部分區索引,一個分區中既存放了數據又存放了索引。而全局分區是指,數據存放在各個分區中,可是全部數據的索引放在一個對象中。

​ 查看數據庫是否啓動了分區功能

SHOW VARIABLES LIKE '%partition%'\G;
SHOW PLUGINS\G;

​ 使用分區,並不必定會是使得數據運行的更快。分區可能會給某些SQL語句性能提升,可是主要用於數據庫高可用性的管理。

​ MYSQL支持的分區

  • RANGE分區:行數據基於屬於一個給定連續區間的列值被放入分區。

  • LIST分區:LIST分區面向的是離散的值

  • HASH分區:根據用戶自定義表達式返回值來進行分區

  • KEY分區:根據MYSQL提供的哈希函數來進行分區。

    不論使用何種分區,表中存在在主鍵或者惟一索引時,分區列必須是惟一索引的一個組成部分。

分區類型

  1. RANGE分區

    # id小於10 數據插入到p0分區, id大於等於10小於20,輸入插入到p1分區
    CREATE TABLE t (
     id INT
    )ENGINE=INNODB
    PARTITION BY RANGE(id)(
     PARTITION p0 VALUES LESS THAN(10),
     PARTITION p1 VALUES LESS THAN(20)
    );

    ​ 分區以後,表再也不由一個ibd文件組成了,而是由多個分區Ibd文件組成。

    ​ 能夠經過查詢infomation_scheme架構下的PARTITIONS表來查看每一個分區的具體信息

    SELECT * FROM information_scheme.PARTITIONS
    WHERE table_schema=database() AND table_name='t'\G;

    ​ 若是咱們插入id爲30的數值,會拋出異常,不讓添加。因此咱們須要再添加一個MAXVALUE的值的分區。

    ALTER TABLE t
    ADD PARTITION(
     partition p2 values less than maxvalue);
    )

    ​ RANGE主要用於日期列的分區,一個demo

    CREATE TABLE sales(
     money INT UNSIGNED NOT NULL
        date DATETIME
    )ENGINE = INNODB
    PARTITION by RANGE(YEAR(date)) (
     PARTITION p2008 VALUES LESS THAN (2009),
        PARTITION p2009 VALUES LESS THAN (2010),
        PARTITION p2010 VALUES LESS THAN (2011)
    );

    ​ 這樣建立的好處是,管理sales這樣表,若是要刪除2008年的數據,不用執行SQL,只需刪除2008年數據所在的分區便可。查詢2008年的數據也會變快。

    ​ 對於sales這張分區表,設計按照每一年每個月進行分區

    CREATE TABLE sales(
     money INT UNSIGNED NOT NULL,
        date DATETIME
    )ENGINE = INNODB
    PARTITION by RANGE(YEAR(date)*100+MONTH(date)) (
     PARTITION p201001 VALUES LESS THAN (201001),
        PARTITION p201002 VALUES LESS THAN (201002),
        PARTITION p201003 VALUES LESS THAN (201003)
    );

    ​ 可是執行先SQL的時候仍是去查找了3個分區

    EXPLAIN PARTITIONS
    SELECT * FROM sales
    WHERE date>='2010-01-01' AND date<='2010-01-31'\G;

    ​ 這個是因爲對RANGE分區的查詢,優化器只能對YEAR()TO_DAYS()TO_SECONDS()UNIX_TIMESTAMP()這類函數進行優化選擇。所以對於上述需求,應該更改成:

    CREATE TABLE sales(
     money INT UNSIGNED NOT NULL,
        date DATETIME
    )ENGINE = INNODB
    PARTITION by RANGE(TO_DAYS(date)) (
     PARTITION p201001 VALUES LESS THAN (TO_DAYS('2010-02-01')),
        PARTITION p201002 VALUES LESS THAN (TO_DAYS('2010-03-01')),
        PARTITION p201003 VALUES LESS THAN (TO_DAYS('2010-04-01'))
    );
  2. LIST分區

    ​ 與RANGE分區很類似,只是分區列的值是離散的,而非連續的。

    CREATE TABLE t (
     a INT,
        b INT
    )ENGINE = INNODB
    PARTITION BY LIST(b) (
     PARTITION P0 VALUES IN (1,3,5,7,9),
        PARTITION P1 VALUES IN (0,2,4,6,8)
    );
  3. HASH分區

    ​ 目的是將數據均勻地分佈到預先定義的各個分區中,保證各分區的數據數量大體同樣的。

    ​ 用戶不須要指定列值,只要基於將要進行哈希分區的列值指定一個列值或者表達式,以及指定分區的表要被分割成的分區數量。

    CREATE TABLE t_hash(
     a INT,
        b DATETIME
    )ENGING=INNODB
    # expr返回一個整數的表達式。僅僅是字段類型爲MYSQL整形的列名
    PARTITION BY HASH (YEAR(b))
    # 劃分分區的數量
    PARTITIONS 4

    ​ 其實就是使用取餘的方式分配到不一樣的分區中。mod(YEAR(b), 4)

    ​ 還支持一種LINEAR HASH的分區,語法與HASH一致。可是進行分區的判斷算法不一樣

    • 取大於分區數量的下一個2的冪值V,V=POWER(2, CEILING(LOG(2, num)))

    • 所在分區N=YEAR('2010-04-01')&(V-1)

      優點在於增長、刪除、合併和拆分分區變得更加快捷,這有利於處理含有大量數據的表。缺點是,數據分佈可能不是太均衡。

  4. KEY分區

    ​ 和HASH分區類似,不一樣之處HASH使用用戶定義的函數進行分區,KEY分區使用MYSQL數據庫提供的函數進行分區,INNODB使用哈希函數。

    CREATE TABLE t_hash(
     a INT,
        b DATETIME
    )ENGING=INNODB
    PARTITION BY KEY (b)
    PARTITIONS 4
  5. COLUMNS分區

    ​ 能夠對多個列的值進行分區,能夠支持的數據類型爲:

    • 全部整型類型,浮點型不支持

    • 日期類型:DATE和DATETIME

    • 字符串類型:CHAR 、VARCHAR、BINARY、VARBINARY,不支持BLOB和TEXT

      能夠用來替代RANGE 和LIST分區

    CREATE TABLE t_hash(
     a INT,
        b DATETIME
    )ENGING=INNODB
    PARTITION BY RANGE COLUMNS(b)(
     partition p0 values less than ('2009-01-01'),
        partition p1 values less than ('201--01-01'), 
    )
    PARTITIONS 4

子分區

​ 子分區是在分區的基礎上再進行分區,有時也稱這種分區爲複合分區。MYSQL容許數據庫在RANGE和LIST的分區上再進行HASH或KEY的子分區。

CREATE TABLE ts(a INT, b DATE)
PARTITION BY RANGE(YEAR(b))
SUBPARTITION BY HASH(TO_DAYS(b)) (
    PARTITION p0 VALUES LESS THAN (1990) (
        SUBPARTITION S0,
        SUBPARTITION s1
    )
    PARTITION p1 VALUES LESS THAN (2000) (
        SUBPARTITION S2,
        SUBPARTITION s3
    )
    PARTITION p2 VALUES LESS THAN MAXVALUE (
        SUBPARTITION S4,
        SUBPARTITION s5
    )
);

​ 注意點:

  • 每一個子分區的數量必須相同
  • 要在一個分區表的任何分區上使用SUBPARTITION來明肯定義任何子分區,就必須定義全部的子分區。
  • 每一個SUBPARTITION子句必須包括子分區的一個名字。
  • 子分區的名必須是惟一的。

分區中的NULL值

​ MYSQL容許對NULL值作分區,在MYSQL中,NULL值視爲小於任何一個非NULL值。

  • 對於RANGE分區,若是插入了NULL值,會放在最左邊的分區。

  • 對於LIST分區,若是要是用NULL值,必須顯式地指出哪一個分區中放入NULL值,不然會報錯。、

    CREATE TABLE t_list(
      a INT,
        b INT
    )ENGINE=INNODB
    PARTITION BY LIST(b)(
      PARTITION p0 VALUES IN (1,3,5,7,9, NULL),
        PARTITION p1 VALUES IN (0,2,4,6,8)
    )
  • 對於HASH和KEY分區,任何分區函數都會將含有NULL值的記錄返回爲0.

分區和性能

​ 分區不必定會提高查詢速度。

​ 數據庫的應用分爲兩類:

  • 在線事務處理OLTP
  • 在線分析處理OLAP

​ 對於OLAP應用,分區能夠提高查詢性能。由於OLAP大多數查詢須要頻繁掃描一張很大的表。假設有一個一億行的表,其中有時間戳屬性,用戶的查詢須要從這張表中獲取一年的數據。若是按時間戳進行分區。則只須要掃響應分區便可。

​ 對於OLTP應用,一般不會獲取一張大表中10%的數據,大部分是經過索引返回幾條記錄便可。而根據B+樹索引的原理可知,對於一張大表,通常的B+樹須要2~3次的磁盤IO。所以B+樹能夠很好的完成操做,不須要分區的幫助。

在表和分區間交換數據

ALTER TABLE ... EXCHANGE PARTITION可讓分區或子分區中的數據與另一個非分區的表中的數據進行交換。

  • 要交換的表需和分區表有着相同的表結構,可是表不能含有分區
  • 在非分區表中的數據必須和在交換的分區定義內
  • 被交換的表中不能含有外鍵,或者其餘的表含有對該表的外鍵引用。
  • 用戶除了須要ALTER INSERT和CREATE權限外,還須要DROP權限
  • 使用該語句時,不會觸發交換表和被交換表上的觸發器
  • AUTO_INCREMENT列會被重置。
ALTER TABLE e EXCHANGE PARTITION p0 WITH TABLE e2;
相關文章
相關標籤/搜索