MySQL MEMORY引擎及性能比對
同事咨詢MySQL MEMORY引擎的細節(jié),能否滿足需求。沒有太多了解,這里做個系統(tǒng)總結(jié)。
MEMORY存儲引擎創(chuàng)建的表數(shù)據(jù)只能保存在內(nèi)存。
MySQL宕機、硬件故障或者意外掉電,都會造成MEMORY引擎表丟失數(shù)據(jù)。所以,MEMORY表中的數(shù)據(jù)來源于其他表(可落盤永久保存)用于只讀適用,或者用于臨時工作起到數(shù)據(jù)周轉(zhuǎn)。
MEMORY 存儲引擎特性
[a] Implemented in the server (via encryption functions). Data-at-rest tablespace encryption is available in MySQL 5.7 and later.
[b] Implemented in the server, rather than in the storage engine.
[c] Implemented in the server, rather than in the storage engine.
來源: https://dev.mysql.com/doc/refman/5.6/en/memory-storage-engine.html
何時使用MEMORY
- 臨時使用、不重要的數(shù)據(jù),例如網(wǎng)站的會話管理和緩存??山邮軘?shù)據(jù)丟失。
- 發(fā)揮其訪問快、低延遲特性
- 只讀或者大部分是讀操作,不適合大量寫操作。
性能特性
內(nèi)存表受限于單線程執(zhí)行和表級鎖引起的爭用。在負載增加時限制了可擴展性,特別是寫操作。
盡管內(nèi)存表在內(nèi)存中處理,在繁忙的服務網(wǎng)上,并不一定比InnoDB表更快,如一般查詢或者讀寫場景。特別是,多個會話執(zhí)行update操作會造成性能低下。
根據(jù)不同的SQL查詢,需要創(chuàng)建內(nèi)存表的默認hash索引(基于唯一鍵的單個值查詢),或者B-tree索引(等值查詢、不等值查詢或者范圍查詢)。
內(nèi)存表的物理特性
每張內(nèi)存表會在硬盤上創(chuàng)建一個文件,用于保存表結(jié)構(gòu)(沒有數(shù)據(jù))。文件名為以內(nèi)存表名開始,.frm結(jié)尾。
特性
- 內(nèi)存表的空間使用小塊(small block)。表使用100%動態(tài)hash插入。已刪除的行放到空閑列表中,下次插入新數(shù)據(jù)會被使用。
- 使用定長的行存儲格式。變長類型存入內(nèi)存表需轉(zhuǎn)換為定長。
- 不支持BLOB、TEXT類型的列。
- 內(nèi)存表支持自增列(AUTO_INCREMENT)
- 不能在多個會話上共享臨時表。
內(nèi)存表的DDL操作
創(chuàng)建臨時表
- CREATE TABLE t (i INT) ENGINE = MEMORY;
基于非內(nèi)存表創(chuàng)建內(nèi)存表,并將數(shù)據(jù)拉入到內(nèi)存表中
- mysql> CREATE TABLE test ENGINE=MEMORY
- -> SELECT ip,SUM(downloads) AS down
- -> FROM log_table GROUP BY ip;
- mysql> SELECT COUNT(ip),AVG(down) FROM test;
- mysql> DROP TABLE test;
內(nèi)存表的最大受限于 max_heap_table_size 參數(shù),默認為16MB。根據(jù)場景需自己調(diào)整該參數(shù)。
索引
MEMORY存儲引擎支持HASH和BTREE索引。
- CREATE TABLE lookup
- (id INT, INDEX USING HASH (id))
- ENGINE = MEMORY;
- CREATE TABLE lookup
- (id INT, INDEX USING BTREE (id))
- ENGINE = MEMORY;
每張內(nèi)存表可創(chuàng)建64個索引,每個索引最大支持16個列,一個key的長度最大值為3072bytes。
如果一個內(nèi)存表hash索引的鍵值有很高的重復度,更新鍵值、刪除操作速度都會顯著降低。這種速度下降的程度與鍵值重復度成正比。您可以使用BTREE索引來規(guī)避這個問題。
內(nèi)存表可以有非唯一鍵(這是hash索引不常用的功能)。
索引列中可包含NULL值。
加載數(shù)據(jù)
MySQL啟動時,加入--init-file選項,將下列命令加入到這個文件中,保證啟動后內(nèi)存表中有數(shù)據(jù)。
- INSERT INTO ... SELECT
- LOAD DATA INFILE
內(nèi)存表和復制(Replication)
服務器重啟會導致內(nèi)存表數(shù)據(jù)丟失。如果是主庫,從庫沒有意識到主庫表中數(shù)據(jù)已被情況,所以在從庫你看到的是過期數(shù)據(jù)。
重啟后,主從庫如何同步內(nèi)存表數(shù)據(jù)?
當主庫使用內(nèi)存表,主庫啟動后,一條DELETE語句會寫入到主庫的binary log中,從庫接到命令后清空內(nèi)存表。
主庫重啟期間,從庫還是有讀取到過期數(shù)據(jù)的情況。為了避免這種情況,主庫啟動時加入--init-file參數(shù),這樣主庫啟動自動將數(shù)據(jù)加載到內(nèi)存表中。(官方文檔寫的不嚴謹,我認為僅限于內(nèi)存表只讀場景。如果不是只讀,即使加入--init-file參數(shù)也無法保證主庫內(nèi)存表數(shù)據(jù)一致)。
管理內(nèi)存使用情況
服務器必須有足夠內(nèi)存,來滿足多張內(nèi)存表的使用。
如果從內(nèi)存表中刪除單獨的行,并不會回收內(nèi)存。當整個內(nèi)存表刪除時,才回收內(nèi)存。同一張內(nèi)存表,之前刪除行占用的空間,會被新的行復用。執(zhí)行DELETE、TRUNCATE TABLE來釋放內(nèi)存表占用空間,如果表不在使用可使用DROP TABLE命令。釋放正在使用的內(nèi)存表占用的內(nèi)存,可以使用 ALTER TABLE XX ENGINE=MEMORY 強制重建表。
內(nèi)存表1行記錄占用內(nèi)存計算公式
SUM_OVER_ALL_BTREE_KEYS(max_length_of_key + sizeof(char*) * 4)
+ SUM_OVER_ALL_HASH_KEYS(sizeof(char*) * 2)
+ ALIGN(length_of_row+1, sizeof(char*))
ALIGN()表示一個round-up因子,其會導致行長度等于char指針大小的精確倍數(shù)。sizeof(char*)在32位機器上是4,64位則為8。
前面提到, max_heap_table_size 系統(tǒng)變量決定了內(nèi)存表的最大尺寸。在創(chuàng)建內(nèi)存之前,可設(shè)置該變量控制每個內(nèi)存表的最大尺寸。(不建議修改全局 max_heap_table_size 的大小,否則所有會話的內(nèi)存表最大尺寸都為該值 )。
下面的例子創(chuàng)建了2個內(nèi)存表,最大尺寸為1M和2M
- mysql> SET max_heap_table_size = 1024*1024;
- Query OK, 0 rows affected (0.00 sec)
- mysql> CREATE TABLE t1 (id INT, UNIQUE(id)) ENGINE = MEMORY;
- Query OK, 0 rows affected (0.01 sec)
- mysql> SET max_heap_table_size = 1024*1024*2;
- Query OK, 0 rows affected (0.00 sec)
- mysql> CREATE TABLE t2 (id INT, UNIQUE(id)) ENGINE = MEMORY;
- Query OK, 0 rows affected (0.00 sec)
服務器重啟后,每個表的尺寸都變成了全局 max_heap_table_size 變量的值大小。
壓測結(jié)果
通過sysbench壓測,InnoDB 寫性能完爆MEMORY引擎,MEMORY的讀性能略高于InnoDB。InnoDB的綜合性能更好,足夠滿足日常使用??紤]到MEMORY引擎的雷點太多,建議不使用MEMORY引擎。