第四章 表 (學習筆記)

  1. InnoDB邏輯存儲結構

  表是根據主鍵順序組織存放的,這種存儲方式的表稱爲索引組織表。html

  全部數據被邏輯的存放在一個空間中,稱爲表空間(tablespace),表空間又由段(segment)、區(extent)、頁(page)等組成。linux

  1.1 表空間算法

  默認狀況下,InnoDB存儲引擎有一個共享表空間ibdata1,即全部數據存放在這個表空間內。若是用戶啓用了參數innodb_file_per_table,則每張表內的數據能夠單獨放到一個表空間內。單獨的表空間內存放的是數據、索引和插入緩衝Bitmap頁,其餘的數據,如回滾(undo)信息、插入緩衝索引頁、系統事務信息、二次寫緩衝(double write buffer)等仍是存放在原來的共享表空間內。sql

  1.2 段數據庫

  數據段爲B+樹的葉子節點。索引段爲B+樹的非索引節點。回滾段在第七章涉及。安全

  1.3 區網絡

  區是由連續頁組成的空間,每一個區的大小爲1MB。爲了保證區中頁的連續性,InnoDB存儲引擎一次從磁盤申請4-5個區。默認狀況下,InnoDB存儲引擎頁的大小爲16KB,即一個區一共有64個連續的頁。InnoDB 1.2.x新增了參數innodb_pages_size,經過該參數能夠將頁設置爲4K, 8K。數據結構

  在每一個段開始時,先用32個頁大小的碎片頁來存放數據,在使用完這些頁以後纔是64個連續頁的申請。這是由於,對於一些小表,或者undo這類段,能夠在開始時申請較少的空間,節省磁盤容量的開銷。函數

  1.4 頁性能

  頁是InnoDB磁盤管理的最小單位。InnoDB中常見的頁類型:

  • 數據頁(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)

  1.5 行

  InnoDB存儲引擎中,數據是按行進行存放的。每一個頁最多容許存放16KB / 2 -200 行記錄。

  2. InnoDB行記錄格式

  2.1 Compact和Redundant 行記錄格式 參考博客:https://www.cnblogs.com/wilburxu/p/9435818.html

  2.2 行溢出數據

  MySQL官方手冊中定義的長度65535,單位爲字節,指的是一行中全部VARCHAR列的長度總和,若是超出,則沒法建立。

  有這樣一個問題,InnoDB存儲引擎的頁爲16KB,即16384字節,怎麼存放65532字節呢?

  行數據只保存了前768字節的前綴數據,以後是偏移量,指向行溢出頁,即BLOB page。

   

 

   2.3 Compressed 和 Dynamic 行記錄格式  

  InnoDB 1. 0.x 版本開始引入了新的文件格式(file format,用戶能夠理解爲新的頁格式),之前支持的 Compact 和 Redundant 格式稱爲 Antelope 文件格式,新的文件格式稱爲 Barracuda 文件格式。Barracuda 文件格式下擁有兩種新的行記錄格式:Compressed 和 Dynamic。新的兩種記錄格式對於存放在 BLOB 中的數據採用了徹底的行溢出的方式,如圖所示,在數據頁中只存放 20 個字節的指針,實際的數據都存放在 Off Page 中,而以前的 Compact 和 Redundant 兩種格式會存放 768 個前綴字節。  

   


  Compressed 行記錄格式的另外一個功能就是,存儲在其中的行數據會以 zlib 的算法進行壓縮,所以對於 BLOB、TEXT、VARCHAR 這類大長度類型的數據可以進行很是有效的存儲。

  3. InnoDB數據頁結構

  該部分參考了博客: https://www.huaweicloud.com/articles/81e3e782d140e68cfea255e90be83889.html

  下圖是InnoDB數據頁結構示意圖,以及各部分的簡單說明。

     

 

   每當咱們插入一條記錄,都會從Free Space部分申請一個記錄大小的空間劃分到User Records部分,當Free Space部分的空間所有被User Records部分替代掉以後,也就意味着這個頁使用完了,若是還有新的記錄插入的話,就須要去申請新的頁了,這個過程的圖示以下:

  

  下面兩張圖顯示的是一條記錄被刪除的過程。  

  delete_mask 這個屬性標記着當前記錄是否被刪除。這些被刪除的記錄之因此不當即從磁盤上移除,是由於移除它們以後把其餘的記錄在磁盤上從新排列須要性能消耗,因此只是打一個刪除標記而已。全部被刪除掉的記錄都會組成一個所謂的垃圾鏈表,在這個鏈表中的記錄佔用的空間稱之爲所謂的可重用空間,以後若是有新記錄插入到表中的話,可能把這些被刪除的記錄佔用的存儲空間覆蓋掉。

  min_rec_mask B+樹的每層非葉子節點中的最小記錄都會添加該標記,min_rec_mask值都是0,意味着它們都不是B+樹的非葉子節點中的最小記錄。

  n_owned 在頁目錄分組時使用,每一個組的最後一條記錄(也就是組內最大的那條記錄)的頭信息中的n_owned屬性表示該記錄擁有多少條記錄,也就是該組內共有幾條記錄。

  heap_no 這個屬性表示當前記錄在本頁中的位置,從圖中能夠看出來,咱們插入的4條記錄在本頁中的位置分別是:二、三、四、5。heap_no值爲0和1的記錄,稱爲僞記錄或者虛擬記錄。這兩個僞記錄一個表明最小記錄,一個表明最大記錄。  

  record_type 這個屬性表示當前記錄的類型,一共有4種類型的記錄,0表示普通記錄,1表示B+樹非葉節點記錄,2表示最小記錄,3表示最大記錄。

  next_record 它表示從當前記錄的真實數據到下一條記錄的真實數據的地址偏移量。比方說第一條記錄的next_record值爲32,意味着從第一條記錄的真實數據的地址處向後找32個字節即是下一條記錄的真實數據。下一條記錄指得並非按照咱們插入順序的下一條記錄,而是按照主鍵值由小到大的順序的下一條記錄。並且規定 Infimum記錄(也就是最小記錄) 的下一條記錄就是本頁中主鍵值最小的用戶記錄,而本頁中主鍵值最大的用戶記錄的下一條記錄就是 Supremum記錄(也就是最大記錄)

       

       

  Page Directory(頁目錄)  

  下圖是頁目錄的示意圖。若是根據主鍵值查找頁中某條記錄呢?  

  1. 經過二分法肯定該記錄所在的槽,並找到該槽所在分組中主鍵值最小的那條記錄。

  2. 經過記錄的next_record屬性遍歷該槽所在的組中的各個記錄。

     

        

  4. Named File Formats機制

  InnoDB存儲經過Named File Formats機制解決不一樣版本下頁結構兼容性問題。

            

  5. 約束

  推薦閱讀博客: https://blog.csdn.net/w_linux/article/details/79655073

   5.1 數據完整性

  約束用來保證數據庫中數據的完整性。完整性有如下三種形式:

  • 實體完整性保證表中有一個主鍵   用戶能夠經過定義primary key 或 unique key約束來保證明體的完整性
  • 域完整性保證每列的值知足特定的條件  能夠經過如下途徑保證:

  1) 選擇合適的數據類型確保一個數據值知足特定條件

  2) 外鍵約束

  3) 編寫觸發器

  4) DEFAULT 約束

  • 參照完整性保證兩張表之間的關係  經過定義外鍵或編寫觸發器以強制執行

  InnoDB存儲引擎提供如下幾種約束:

  • primary key
  • unique key
  • foreign key
  • default
  • not null

  5.2 約束的建立和查找

  約束的建立能夠有如下兩種方式:

  • 表創建時就進行約束定義
  • 利用ALTER TABLE命令來進行約束建立

    

  5.3 約束和索引的區別

  的確,當用戶建立了惟一索引就建立了惟一約束。可是,約束是一個邏輯概念,是爲了保證數據完整性,索引是一個數據結構,是爲了提升查詢效率。

  5.4 對錯誤數據的約束(NOT NULL)

  在某些默認設置下,MySQL數據庫容許非法的或不正確的數據的插入或更新,經過設置參數sql_mode = 'STRICT_TRANS_TABLES' 對輸入的值進行約束。

 

 

   

 

  5.5 ENUM 和 SET 約束

  MySQL數據庫不支持傳統的CHECK約束,可是經過ENUM和SET能夠解決部分這樣的約束需求。以下代碼所示:

  

        

 

  可是ENUM只能對離散數值進行約束,對於傳統CHECK約束支持的連續值的範圍約束或更復雜的約束,須要經過觸發器來實現對於值域的約束。

  5.6 觸發器與約束

  觸發器的做用是在執行INSERT、DELETE和UPDATE命令以前或以後自動調用SQL命令或存儲過程。MySQL目前支持FOR EACH ROW的觸發方式。經過觸發器,用戶能夠實現MySQL數據庫自己並不支持的一些特性,如對於傳統CHECK約束的支持、物化視圖、高級複製和審計等特性。下面代碼展現觸發器對約束的支持。

     

       

     

  首先建立了一張usercah_err_log來記錄錯誤數值更新的日誌。而後建立觸發器,判斷新、舊值之間的差值,大於原值的數據會被判斷爲非法的輸入,將cash設置爲原來的值,並將非法數據更新到usercash_err_log。

  5.7 外鍵約束

  MyISAM存儲引擎自己並不支持外鍵,對於外鍵的定義只起到一個註釋的做用。而InnoDB存儲引擎則完整支持外鍵約束。

  通常來講,被引用的表稱爲父表,引用的表稱爲子表。可定義的子表操做有:

  • CASCADE    當父表發生DELETE或UPDATE操做時,對相應的子表數據也進行UPDATE 或DELETE操做
  • SET NULL     當父表發生DELETE或UPDATE操做時,對相應的子表數據更新爲NULL
  • NO ACTION  當父表發生DELETE或UPDATE操做時,拋出錯誤,不容許這類操做發生
  • RESTRICT    當父表發生DELETE或UPDATE操做時,拋出錯誤,不容許這類操做發生

  能夠經過設置 foreign_key_checks = 0 在數據導入過程當中忽視外鍵檢查。

  6. 視圖

  6.1 視圖的做用

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

  視圖的建立

CREATE [OR REPLACE]   
  [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]  
  [DEFINER = { user | CURRENT_USER }]  
  [SQL SECURITY { DEFINER | INVOKER }]
VIEW view_name [(column_list)]  
AS select_statement  
  [WITH [CASCADED | LOCAL] CHECK OPTION]

  視圖的優勢:

  • 簡單:對於一些應用程序,程序自己不須要關心基表的結構,只須要按照視圖定義來取數據或更新數據
  • 安全:使用視圖的用戶只能訪問他們被容許查詢的結果集,對錶的權限管理並不能限制到某個行某個列,可是經過視圖就能夠簡單的實現
  • 數據獨立:一旦視圖的結構肯定了,能夠屏蔽表結構變化對用戶的影響,源表增長列對視圖沒有影響;源表修改列名,則能夠經過修改視圖來解決,不會形成對訪問者的影響

  對視圖進行DML操做更新視圖時,更新的是其背後的基表,可是包含下列內容之一,視圖不能進行DML操做:

  • select子句中包含distinct
  • select子句中包含組函數
  • select語句中包含group by子句
  • select語句中包含order by子句
  • select語句中包含union 、union all等集合運算符
  • where子句中包含相關子查詢
  • from子句中包含多個表
  • 若是視圖中有計算列,則不能更新
  • 若是基表中有某個具備非空約束的列未出如今視圖定義中,則不能作insert操做

  6.2 物化視圖

  Oracle數據庫支持物化視圖——根據基表實際存在的實表,即物化視圖的數據存儲在非易失的存儲設備上。物化視圖能夠用於預先計算保存多表的連接(JOIN)或彙集(GROUP BY)等耗時較多的SQL操做結果。這樣,在執行復雜查詢時,就能夠避免進行這些耗時的操做,從而快速獲得結果。物化視圖有兩種刷新方式:

  • ON DEMAND  在用戶須要時進行刷新
  • ON COMMIT 物化視圖在對基表的DML操做提交的同時進行刷新

  MySQL自己並不支持物化視圖,可是能夠經過一些方法來部分實現物化視圖的功能。

  實現 ON DEMAND 功能:

  有以下訂單表,記錄了用戶採購電腦設備的信息  

CREATE TABLE Orders
(
order_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
product_name  VARCHAR(30) NOT NULL,
price DECIMAL(8, 2) NOT NULL,
amount  SMALLINT   NOT NULL,
PRIMARY KEY  (order_id)
)ENGINE = InnoDB


INSERT INTO Orders VALUES
(NULL, 'CPU', 135.5, 1),
(NULL, 'Memory', 48.2, 3),
(NULL, 'CPU', 125.6, 3),
(NULL, 'CPU', 105.3, 4);

  接着建立一張物化視圖的基表,用來統計每件物品的信息

CREATE TABLE Orders_MV(
product_name   VARCHAR(30)       NOT NULL,
price_sum      DECIMAL(8,2)      NOT NULL,
amount_sum     INT               NOT NULL,
price_avg      FLOAT             NOT NULL,
orders_cnt     INT               NOT NULL,
UNIQUE   INDEX  (product_name);


INSERT  INTO Orders_MV
SELECT  product_name, SUM(price), SUM(amount), AVG(price), 
             COUNT(*) 
            FROM Orders
            GROUP BY product_name;

  經過以上方式,用戶就擁有了一個統計信息的物化視圖。若是要實現ON DEMAND功能,只須要把表清空,從新導入數據便可。

  若是要實現ON COMMIT 的物化視圖,須要藉助觸發器來達到目的。須要對錶Orders創建一個觸發器: 

DELIMITER  $$


CREATE  TRIGGER  tgr_Orders_insert
AFTER  INSERT ON Orders
FOR EACH ROW
BEGIN
    SET @old_price_sum = 0;
    SET @old_amount_sum = 0;
    SET @old_price_avg = 0;
    SET @old_orders_cnt = 0;

    SELECT IFNULL(price_num, 0), IFNULL(amount_sum, 0), IFNULL(price_avg, 0), IFNULL(order_cnt, 0)
    FROM Orders_MV
    WHERE product_name = NEW.product_name
    INTO @old_price_sum, @old_amount_sum, @old_price_avg, @old_orders_cnt;
    
    SET @new_price_sum = @old_price_sum + NEW.price;
    SET @new_amount_sum = @old_amount_sum + NEW.amount;
    SET @new_orders_cnt = @old_orders_cnt +1;
    SET @new_price_avg = @new_price_sum / @new_orders_cnt;

    REPLACE INTO Orders_MV
    VALUES(NEW.product_name, @new_price_sum, @new_amount_sum, @new_price_avg, @new_orders_cnt);    
    END
    $$

    DELIMITER;

  因爲MySQL數據庫自己並不支持物化視圖,所以對於物化視圖支持的查詢重寫功能就顯得無能爲力了,用戶只能在應用程序端作一些控制  

  7. 分區表

   7.1 分區概述

  MyISAM, InnoaDB, NDB等存儲引擎都支持分區功能。分區的過程是將一個表或索引分解爲多個更小,更可管理的部分。就訪問數據庫的應用而言,邏輯上講,只有一個表或一個索引,可是物理上這個表或索引可能由數十個物理分區組成。每一個分區都是獨立的對象,能夠獨自處理,也能夠做爲一個更大對象的一部分進行處理。MySQL數據庫支持如下幾種類型的分區:

  • RANGE分區:行數據基於一個給定連續區間的列值被放入分區
  • LIST分區:和RANGE分區相似,只是LIST分區面向的是離散的值
  • HASH分區:根據用戶自定義的表達式的返回值來進行分區
  • KEY分區:根據MySQL數據庫提供的哈希函數來進行分區

  不論建立何種類型的分區,若是表中存在主鍵或惟一索引時,分區列必須是惟一索引的一個組成部分,下面建立分區的SQL語句會產生錯誤。

  

                  

 

 

 

 

        

 

  7.2.1 RANGE 分區

  下面的SQL語句根據id列來建立分區表,id小於10時,數據插入P0,大於等於10,小於20時,插入P1分區。

  

 

  查看錶在磁盤上的物理文件,啓用分區以後,表再也不由一個ibd文件組成了,而是由各個分區ibd文件組成,以下面的p0.ibd, p1.ibd.

  

  RANGE分區主要用於日期列的分區,例如對於銷售類的表,能夠根據年份來分區存放銷售記錄,這種建立方式便於對sales表的管理:

 

  • 能夠經過alter table sales drop partition p2008 直接將2008年數據所在分區刪除
  • 加快某些查詢操做
EXPLAIN PARTITIONS
SELECT * FROM sales
WHERE date>='2008-01-01' AND date<='2008-12-31'

  SQL優化器只會去搜索P2008這個分區,而不是搜索全部的分區——稱爲Partition Pruning(分區修剪),查詢速度會大幅提高。

  

  優化器只能對YEAR(), TO_DAYS(), TO_SECONDS(), UNIX_TIMESTAMP()這類函數進行優化選擇。  

  7.2.2 LIST分區

  list分區中的值是離散的,示例代碼以下:

      

  用INSERT插入多行數據的過程當中遇到分區未定義的值時,MyISAM會將以前知足要求的行數據都插入,不知足要求的行數據即以後的不會插入,InnoDB將其視爲一個事務,所以沒有任何數據會被插入。

  7.2.3 COLUMNS分區

  前面介紹的RANGE, LIST, HASH和KEY分區中,數據必須是整型,若是不是整型,須要經過YEAR(), TO_DAYS(), MONTH()等函數化爲整型。COLUMNS分區能夠直接使用非整型的數據進行分區,分區數據類型直接比較而得。示例代碼以下:

       

 

  7.2.4 子分區

  子分區是在分區的基礎上在進行分區。MySQL數據庫容許在RANGE和LIST的分區上再進行HASH 或 KEY的子分區。

      

  7.2.5 分區中的NULL 值

  對於RANGE分區,若是向分區列插入了NULL值,則MySQL數據庫會將該值放入最左邊的分區。

  對於LIST分區,須要顯式地指出那個分區中放入NULL值。

  HASH 和 KEY 分區中,任何分區函數都會將含有NULL值地記錄返回爲0.

  7.3 分區和性能

  數據庫的應用分爲兩類:一類是OLTP(在線事務處理),如blog,電子商務,網絡遊戲等;另外一類是OLAP(在線分析處理),如數據倉庫,數據集市。在一個實際的應用環境中,可能既有OLTP應用,也有OLAP應用。如網絡遊戲中,玩家操做的遊戲數據庫應用就是OLTP的,可是遊戲廠商須要對遊戲產生的日誌進行分析,經過分析獲得的結果來更好地服務於遊戲,預測玩家的行爲等,而這是OLAP的應用。

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

  對於OLTP應用,一般不可能會獲取一張大表中10%的數據,大部分都是經過索引返回幾條記錄。對於一張大表,通常的B+樹須要2~3次的磁盤IO。

       

  若是對主鍵進行查詢分區是有意義的。

  

  若是用其餘索引進行查詢,查詢開銷則大的多,對於KEY的查詢須要掃描全部的10個分區,每一個分區的查詢開銷爲2次IO,一共須要20次IO。

  

相關文章
相關標籤/搜索