MySQL調優(六):分區設計,分區優化案例

上節課沒講完的一個案例

優化 limit 分頁

在很多應用場景中我們需要將數據進行分頁,一般會使用limit加上偏移量的方法實現,同時加上合適的orderby 的子句,如果這種方式有索引的幫助,效率通常不錯,否則的化需要進行大量的文件排序操作,還有一種情況,當偏移量非常大的時候,前面的大部分數據都會被拋棄,這樣的代價太高。
要優化這種查詢的話,要麼是在頁面中限制分頁的數量,要麼優化大偏移量的性能

利用子查詢,優化一個 limit 分頁
在這裏插入圖片描述
例如,原語句:

select * from rental limit 1000000,5;

利用子查詢,優化後的語句:(是因爲走了索引)

select * from rental a join(select rental_id from rental limit 1000000,5) b on a.rental_id=b.rental_id;

優化union查詢

mysql總是通過創建並填充臨時表的方式來執行union查詢,因此很多優化策略在union查詢中都沒法很好的使用。經常需要手工的將where、limit、order by等子句下推到各個子查詢中,以便優化器可以充分利用這些條件進行優化
在這裏插入圖片描述

行轉列可以使用unoin快速實現:
在這裏插入圖片描述

推薦使用用戶自定義變量

用戶自定義變量是一個容易被遺忘的mysql特性,但是如果能夠用好,在某些場景下可以寫出非常高效的查詢語句,在查詢中混合使用過程化和關係話邏輯的時候,自定義變量會非常有用。
用戶自定義變量是一個用來存儲內容的臨時容器,在連接mysql的整個過程中都存在。
在這裏插入圖片描述

自定義變量的使用案例

在這裏插入圖片描述

分區表

對於用戶而言,分區表是一個獨立的邏輯表,但是底層是由多個物理子表組成。分區表對於用戶而言是一個完全封裝底層實現的黑盒子,對用戶而言是透明的,從文件系統中可以看到多個使用#分隔命名的表文件。
mysql在創建表時使用partition by子句定義每個分區存放的數據,在執行查詢的時候,優化器會根據分區定義過濾那些沒有我們需要數據的分區,這樣查詢就無須掃描所有分區。
分區的主要目的是將數據安好一個較粗的力度分在不同的表中,這樣可以將相關的數據存放在一起。

分區表的應用場景

在這裏插入圖片描述

分區表的限制

在這裏插入圖片描述

分區表的底層原理

​ 分區表由多個相關的底層表實現,這個底層表也是由句柄對象標識,我們可以直接訪問各個分區。存儲引擎管理分區的各個底層表和管理普通表一樣(所有的底層表都必須使用相同的存儲引擎),分區表的索引知識在各個底層表上各自加上一個完全相同的索引。從存儲引擎的角度來看,底層表和普通表沒有任何不同,存儲引擎也無須知道這是一個普通表還是一個分區表的一部分。

​ 分區表的操作按照以下的操作邏輯進行:

select查詢

​ 當查詢一個分區表的時候,分區層先打開並鎖住所有的底層表,優化器先判斷是否可以過濾部分分區,然後再調用對應的存儲引擎接口訪問各個分區的數據

insert操作

​ 當寫入一條記錄的時候,分區層先打開並鎖住所有的底層表,然後確定哪個分區接受這條記錄,再將記錄寫入對應底層表

delete操作

​ 當刪除一條記錄時,分區層先打開並鎖住所有的底層表,然後確定數據對應的分區,最後對相應底層表進行刪除操作

update操作

​ 當更新一條記錄時,分區層先打開並鎖住所有的底層表,mysql先確定需要更新的記錄再哪個分區,然後取出數據並更新,再判斷更新後的數據應該再哪個分區,最後對底層表進行寫入操作,並對源數據所在的底層表進行刪除操作

有些操作是支持過濾的,例如,當刪除一條記錄時,MySQL需要先找到這條記錄,如果where條件恰好和分區表達式匹配,就可以將所有不包含這條記錄的分區都過濾掉,這對update同樣有效。如果是insert操作,則本身就是隻命中一個分區,其他分區都會被過濾掉。mysql先確定這條記錄屬於哪個分區,再將記錄寫入對應得曾分區表,無須對任何其他分區進行操作

​ 雖然每個操作都會“先打開並鎖住所有的底層表”,但這並不是說分區表在處理過程中是鎖住全表的,如果存儲引擎能夠自己實現行級鎖,例如innodb,則會在分區層釋放對應表鎖

分區表的類型

這個可以直接到MySQL官網上去看,解釋和例子都給的比較清晰。
在這裏插入圖片描述

如何使用分區表

如果需要從非常大的表中查詢出某一段時間的記錄,而這張表中包含很多年的歷史數據,數據是按照時間排序的,此時應該如何查詢數據呢?
因爲數據量巨大,肯定不能在每次查詢的時候都掃描全表。考慮到索引在空間和維護上的消耗,也不希望使用索引,即使使用索引,會發現會產生大量的碎片,還會產生大量的隨機IO,但是當數據量超大的時候,索引也就無法起作用了,此時可以考慮使用分區來進行解決

全量掃描數據,不要任何索引
使用簡單的分區方式存放表,不要任何索引,根據分區規則大致定位需要的數據爲止,通過使用where條件將需要的數據限制在少數分區中,這種策略適用於以正常的方式訪問大量數據

索引數據,並分離熱點
如果數據有明顯的熱點,而且除了這部分數據,其他數據很少被訪問到,那麼可以將這部分熱點數據單獨放在一個分區中,讓這個分區的數據能夠有機會都緩存在內存中,這樣查詢就可以只訪問一個很小的分區表,能夠使用索引,也能夠有效的使用緩存

在使用分區表的時候需要注意的問題

在這裏插入圖片描述

範圍分區示例

範圍分區表的分區方式是:每個分區都包含行數據且分區的表達式在給定的範圍內,分區的範圍應該是連續的且不能重疊,可以使用values less than運算符來定義。

​ 1、創建普通的表

CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT NOT NULL,
    store_id INT NOT NULL
);

​ 2、創建帶分區的表,下面建表的語句是按照 store_id 來進行分區的,指定了4個分區

CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT NOT NULL,
    store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
    PARTITION p0 VALUES LESS THAN (6),
    PARTITION p1 VALUES LESS THAN (11),
    PARTITION p2 VALUES LESS THAN (16),
    PARTITION p3 VALUES LESS THAN (21)
);
--在當前的建表語句中可以看到,store_id的值在1-5的在p0分區,6-10的在p1分區,11-15的在p3分區,16-20的在p4分區,但是如果插入超過20的值就會報錯,因爲mysql不知道將數據放在哪個分區

3、可以使用less than maxvalue來避免此種情況

CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT NOT NULL,
    store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
    PARTITION p0 VALUES LESS THAN (6),
    PARTITION p1 VALUES LESS THAN (11),
    PARTITION p2 VALUES LESS THAN (16),
    PARTITION p3 VALUES LESS THAN MAXVALUE
);
--maxvalue表示始終大於等於最大可能整數值的整數值

4、可以使用相同的方式根據員工的職務代碼對錶進行分區

CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT NOT NULL,
    store_id INT NOT NULL
)
PARTITION BY RANGE (job_code) (
    PARTITION p0 VALUES LESS THAN (100),
    PARTITION p1 VALUES LESS THAN (1000),
    PARTITION p2 VALUES LESS THAN (10000)
);

5、可以使用date類型進行分區:如虛妄根據每個員工離開公司的年份進行劃分,如year(separated)

CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT,
    store_id INT
)
PARTITION BY RANGE ( YEAR(separated) ) (
    PARTITION p0 VALUES LESS THAN (1991),
    PARTITION p1 VALUES LESS THAN (1996),
    PARTITION p2 VALUES LESS THAN (2001),
    PARTITION p3 VALUES LESS THAN MAXVALUE
);

​ 6、可以使用函數根據range的值來對錶進行分區,如timestampunix_timestamp()

CREATE TABLE quarterly_report_status (
    report_id INT NOT NULL,
    report_status VARCHAR(20) NOT NULL,
    report_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
PARTITION BY RANGE ( UNIX_TIMESTAMP(report_updated) ) (
    PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-01-01 00:00:00') ),
    PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-04-01 00:00:00') ),
    PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-07-01 00:00:00') ),
    PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-10-01 00:00:00') ),
    PARTITION p4 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-01-01 00:00:00') ),
    PARTITION p5 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-04-01 00:00:00') ),
    PARTITION p6 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-07-01 00:00:00') ),
    PARTITION p7 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-10-01 00:00:00') ),
    PARTITION p8 VALUES LESS THAN ( UNIX_TIMESTAMP('2010-01-01 00:00:00') ),
    PARTITION p9 VALUES LESS THAN (MAXVALUE)
);
--timestamp不允許使用任何其他涉及值的表達式

基於時間間隔的分區方案,在mysql5.7中,可以基於範圍或事件間隔實現分區方案,有兩種選擇

1、基於範圍的分區,對於分區表達式,可以使用操作函數基於date、time、或者datatime列來返回一個整數值

CREATE TABLE members (
    firstname VARCHAR(25) NOT NULL,
    lastname VARCHAR(25) NOT NULL,
    username VARCHAR(16) NOT NULL,
    email VARCHAR(35),
    joined DATE NOT NULL
)
PARTITION BY RANGE( YEAR(joined) ) (
    PARTITION p0 VALUES LESS THAN (1960),
    PARTITION p1 VALUES LESS THAN (1970),
    PARTITION p2 VALUES LESS THAN (1980),
    PARTITION p3 VALUES LESS THAN (1990),
    PARTITION p4 VALUES LESS THAN MAXVALUE
);

CREATE TABLE quarterly_report_status (
    report_id INT NOT NULL,
    report_status VARCHAR(20) NOT NULL,
    report_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
PARTITION BY RANGE ( UNIX_TIMESTAMP(report_updated) ) (
    PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-01-01 00:00:00') ),
    PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-04-01 00:00:00') ),
    PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-07-01 00:00:00') ),
    PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-10-01 00:00:00') ),
    PARTITION p4 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-01-01 00:00:00') ),
    PARTITION p5 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-04-01 00:00:00') ),
    PARTITION p6 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-07-01 00:00:00') ),
    PARTITION p7 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-10-01 00:00:00') ),
    PARTITION p8 VALUES LESS THAN ( UNIX_TIMESTAMP('2010-01-01 00:00:00') ),
    PARTITION p9 VALUES LESS THAN (MAXVALUE)
);

2、基於範圍列的分區,使用date或者datatime列作爲分區列

CREATE TABLE members (
    firstname VARCHAR(25) NOT NULL,
    lastname VARCHAR(25) NOT NULL,
    username VARCHAR(16) NOT NULL,
    email VARCHAR(35),
    joined DATE NOT NULL
)
PARTITION BY RANGE COLUMNS(joined) (
    PARTITION p0 VALUES LESS THAN ('1960-01-01'),
    PARTITION p1 VALUES LESS THAN ('1970-01-01'),
    PARTITION p2 VALUES LESS THAN ('1980-01-01'),
    PARTITION p3 VALUES LESS THAN ('1990-01-01'),
    PARTITION p4 VALUES LESS THAN MAXVALUE
);
真實案例:
#不分區的表
CREATE TABLE no_part_tab
(id INT DEFAULT NULL,
remark VARCHAR(50) DEFAULT NULL,
d_date DATE DEFAULT NULL
)ENGINE=MYISAM;
#分區的表
CREATE TABLE part_tab
(id INT DEFAULT NULL,
remark VARCHAR(50) DEFAULT NULL,
d_date DATE DEFAULT NULL
)ENGINE=MYISAM
PARTITION BY RANGE(YEAR(d_date))(
PARTITION p0 VALUES LESS THAN(1995),
PARTITION p1 VALUES LESS THAN(1996),
PARTITION p2 VALUES LESS THAN(1997),
PARTITION p3 VALUES LESS THAN(1998),
PARTITION p4 VALUES LESS THAN(1999),
PARTITION p5 VALUES LESS THAN(2000),
PARTITION p6 VALUES LESS THAN(2001),
PARTITION p7 VALUES LESS THAN(2002),
PARTITION p8 VALUES LESS THAN(2003),
PARTITION p9 VALUES LESS THAN(2004),
PARTITION p10 VALUES LESS THAN maxvalue);
#插入未分區表記錄
DROP PROCEDURE IF EXISTS no_load_part;
 

DELIMITER//
CREATE PROCEDURE no_load_part()
BEGIN
    DECLARE i INT;
    SET i =1;
    WHILE i<80001
    DO
    INSERT INTO no_part_tab VALUES(i,'no',ADDDATE('1995-01-01',(RAND(i)*36520) MOD 3652));
    SET i=i+1;
    END WHILE;
END//
DELIMITER ;
 
CALL no_load_part;
#插入分區表記錄
DROP PROCEDURE IF EXISTS load_part;
 
DELIMITER&& 
CREATE PROCEDURE load_part()
BEGIN
    DECLARE i INT;
    SET i=1;
    WHILE i<80001
    DO
    INSERT INTO part_tab VALUES(i,'partition',ADDDATE('1995-01-01',(RAND(i)*36520) MOD 3652));
    SET i=i+1;
    END WHILE;
END&&
DELIMITER ;
 
CALL load_part;

其他

有個疑問,文件描述符 fd 的個數受限於這個 open files 嗎?如果是這樣的話,每一個 socket 都要開啓一個 fd 對吧,那在使用網絡 IO 的時候,開啓的 socket 的總個數是不是也要受到這個 open files 的限制?
在這裏插入圖片描述

實戰:查看分區文件的存儲方式

在這裏插入圖片描述
可以看到產生的分區文件:
在這裏插入圖片描述

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