Learning Objectives
- 說明什麼是儲存引擎,用 SHOW ENGINES 與 information_schema.TABLES 查詢可用的引擎與每張表格的引擎
- 比較 InnoDB 與 MyISAM:交易、外鍵、鎖定範圍、資料排列方式
- 說出 MEMORY、CSV、ARCHIVE、BLACKHOLE、MERGE 各自的用途與限制,並用 ALTER TABLE … ENGINE= 轉換引擎
- 建立 FULLTEXT 索引,分辨全文檢索的自然語言模式與布林模式
- 知道 MySQL 8.0 已移除的舊語法與舊引擎:TYPE=、BDB、ISAM,以及 HEAP 改名為 MEMORY
什麼是儲存引擎
MySQL 可以分成兩層來看。上層負責解析 SQL、檢查權限、決定查詢怎麼執行; 下層的儲存引擎(storage engine)負責把資料真正寫進硬碟(或記憶體)、建立索引、處理鎖定。 同一個 SQL 敘述交給不同的儲存引擎執行,資料存放的方式、能不能復原、多人同時寫入時的表現都不一樣。
MySQL 的特色是每張表格可以各自選擇儲存引擎,同一個資料庫裡可以同時有 InnoDB 和 MyISAM 的表格。建立表格時用 ENGINE= 指定:
CREATE TABLE asset (
assetID INT NOT NULL,
description VARCHAR(255)
) ENGINE=MyISAM;
沒有寫 ENGINE= 時,使用系統變數 default_storage_engine 的值。MySQL 5.5 起預設引擎是 InnoDB,MySQL 8.0 也是。
舊教材的寫法 TYPE=MyISAM 在 MySQL 5.5 已經移除,8.0 執行會出現語法錯誤(ERROR 1064),一律改寫成 ENGINE=MyISAM。
查詢可用的引擎與表格的引擎
範例在 shop 資料庫裡建立兩張表格:一張指定 MyISAM,一張沒有指定。接著從 information_schema.TABLES 查詢每張表格的引擎,再用 ALTER TABLE 把 MyISAM 的表格轉成 InnoDB。
SHOW ENGINES 的欄位與 ALTER TABLE … ENGINE=
| 欄位 | 意思 |
|---|---|
Engine |
引擎名稱 |
Support |
DEFAULT(預設引擎)、YES(可以使用)、NO(這個版本沒有提供) |
Transactions |
是否支援交易(下一課的主題) |
XA |
是否支援分散式交易 |
Savepoints |
是否支援交易中的存檔點 |
列出的引擎依安裝的版本與套件而略有不同;在 MySQL 8.0 只有 InnoDB 的 Transactions 是 YES。
查詢表格的引擎有兩種方式:
SHOW TABLE STATUS LIKE 'asset'; -- Engine 欄位
SELECT TABLE_NAME, ENGINE
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'shop'; -- 一次列出整個資料庫
information_schema 是 MySQL 內建、唯讀的資料庫,裡面的表格描述了「伺服器上有哪些資料庫、表格、欄位」。
本課的直譯器只模擬了它常用的幾張表格;SHOW TABLE STATUS 裡的列數、檔案大小等數字也是簡化的示意值。
ALTER TABLE … ENGINE= 會重建整張表格:MySQL 建立一張新引擎的空表格,把資料逐列複製過去,再取代原本的表格。
表格很大時要花很久,而且新引擎不支援的定義(例如 MEMORY 不能有 TEXT 欄位)會讓轉換失敗。
InnoDB 與 MyISAM
課程原本以 MyISAM 為主(它在 MySQL 5.5 以前是預設引擎),現在幾乎所有新表格都使用 InnoDB:
| InnoDB | MyISAM | |
|---|---|---|
| 交易(COMMIT/ROLLBACK) | 支援 | 不支援,每個敘述執行完就寫入 |
| 外鍵 | 支援,會檢查參照完整性 | 語法可以寫,但會被忽略,不做任何檢查 |
| 鎖定範圍 | 資料列層級:多人修改不同的列可以同時進行 | 表格層級:有人寫入時,整張表格的其他寫入都要等 |
| 當機後復原 | 依 redo log 自動復原到最後一次提交 | 資料可能損毀,需要 REPAIR TABLE 修復 |
| 資料排列 | 依主鍵排列(叢集索引) | 依寫入的先後存放 |
| 檔案 | 每張表格一個 .ibd 檔 |
.MYD(資料)、.MYI(索引)、.sdi(表格定義) |
依主鍵排列的意思是:InnoDB 的資料本身就存放在主鍵索引裡,不管寫入的順序如何,資料列都依主鍵由小到大排好。
沒有定義主鍵時,InnoDB 會用第一個 UNIQUE NOT NULL 的索引,再沒有就自己產生一個隱藏的列編號。
舊投影片提到 MyISAM 的 .frm 檔:MySQL 8.0 把表格定義改存在 InnoDB 裡的資料字典,.frm 檔已經不再使用,MyISAM 改用 .sdi 檔保存表格定義。
mysql 系統資料庫的表格在 8.0 也大多改成了 InnoDB(記錄查詢日誌的 general_log、slow_log 是 CSV 表格)。
MyISAM 目前的用途很少:舊系統留下的表格、只讀不寫的資料。需要交易、外鍵或多人同時寫入時一律使用 InnoDB。
同樣的資料,兩種引擎的差別
兩張部門表格的結構相同,只有引擎不同。以 30、10、20 的順序寫入同樣的三筆資料,再用沒有 ORDER BY 的 SELECT 讀出來。最後在 MyISAM 的 staff_myisam 寫入一筆部門編號 99 的員工:這張表格建立時寫了外鍵,但 MyISAM 不會檢查。
其他儲存引擎
| 引擎 | 資料存在哪裡 | 特點與限制 | 適合的用途 |
|---|---|---|---|
MEMORY |
記憶體 | 速度快;伺服器重新啟動後資料全部消失(表格定義還在);不能有 TEXT、BLOB 欄位;表格層級鎖定;大小受 max_heap_table_size 限制 |
暫存、可以重新產生的查詢結果 |
CSV |
逗號分隔的文字檔(.CSV),可以直接用試算表開啟 |
所有欄位都必須是 NOT NULL;不能建立索引(所以也不能有主鍵與 AUTO_INCREMENT) |
與其他程式交換資料 |
ARCHIVE |
壓縮後的檔案 | 只能新增(INSERT)與查詢(SELECT),不能 UPDATE、DELETE;只有 AUTO_INCREMENT 欄位可以有索引 | 很少讀取、只增不改的紀錄,例如稽核日誌 |
BLACKHOLE |
不存 | 寫入的資料全部丟掉,SELECT 永遠是空的 | 測試 SQL 語法;複寫(replication)架構中只轉送、不保存資料的中繼伺服器 |
MERGE(MRG_MyISAM) |
由多張結構相同的 MyISAM 表格組成 | 本身不存資料,查詢時把各表格的資料合在一起 | 依月份分開存放的日誌(下一段) |
已經不存在的引擎:原課程提到的幾個名稱在 MySQL 8.0 都不能用了。
ISAM:MyISAM 的前身,MySQL 5.0 移除BDB(Berkeley DB):早期支援交易的引擎,MySQL 5.1 移除,交易功能由 InnoDB 取代HEAP:就是MEMORY的舊名稱。8.0 仍接受ENGINE=HEAP這個寫法,但建立出來的表格顯示為MEMORY,新程式請直接寫MEMORY
指定不存在的引擎(例如 ENGINE=BDB)會出現 ERROR 1286 (42000): Unknown storage engine 'BDB'。
MEMORY、CSV、ARCHIVE 與 BLACKHOLE 的限制
每種引擎先示範一次它不接受的寫法,再示範正確的寫法。紅色的步驟是錯誤訊息:注意每個錯誤代碼對應的是哪一種限制。
MERGE 表格:把每個月的日誌合起來查詢
課程的情境:系統每個月建立一張日誌表格(log2003Jan、log2003Feb、log2003Mar),
單一表格不會太大,舊月份也容易備份或刪除;但查詢「這一季全部的日誌」時,希望像查一張表格一樣方便。MERGE 表格就是做這件事:
CREATE TABLE logs (
logid INT AUTO_INCREMENT PRIMARY KEY,
logts DATETIME,
entry CHAR(255)
) ENGINE=MERGE UNION=(log2003Jan, log2003Feb, log2003Mar) INSERT_METHOD=LAST;
UNION=(…)列出組成的表格。這些表格必須都是 MyISAM,而且欄位與索引的定義完全相同,否則查詢時會出現ERROR 1168INSERT_METHOD決定寫入 MERGE 表格的資料放到哪裡:FIRST(第一張)、LAST(最後一張)、NO(不能寫入,這是預設值)- MERGE 表格的主鍵不會跨表格檢查:三張表格各自的
logid都從 1 開始,合在一起就有三個 1 - 引擎名稱寫
MERGE或MRG_MyISAM都可以,查詢時顯示為MRG_MyISAM
表格名稱的大小寫:MySQL 把每張表格存成檔案,在 Linux(lower_case_table_names=0,Ubuntu 的預設)上,
log2003Jan 和 log2003jan 是兩個不同的檔案名稱,寫錯大小寫就會找不到表格;Windows 預設不分大小寫(值為 1)。
表格名稱一律照建立時的寫法,不要依賴不分大小寫。
MERGE 只能搭配 MyISAM。現在同樣的需求多半改用 InnoDB 的分割表格(PARTITION BY RANGE),一張表格依日期分成多個分割區,同時保有交易與資料列鎖定。
MERGE 表格的查詢與寫入
執行前,logs 資料庫裡已經有三張月份表格,各有一筆資料。範例建立 MERGE 表格 logs,透過它查詢與寫入,再故意把表格名稱的大小寫寫錯。
想一想:各種引擎的限制
在 MySQL 8.0 執行下面的敘述,結果是什麼?
CREATE TABLE export_csv (id INT, name VARCHAR(20)) ENGINE=CSV;
三張月份表格各有 1 筆資料。建立 MERGE 表格時寫的是 INSERT_METHOD=FIRST,再寫入一筆:
CREATE TABLE logs ( … ) ENGINE=MERGE
UNION=(log2003Jan, log2003Feb, log2003Mar) INSERT_METHOD=FIRST;
INSERT INTO logs (logts, entry) VALUES ('2003-03-15', 'x');
SELECT COUNT(*) FROM log2003Jan;
最後一行的結果是什麼?
一張 MEMORY 表格裡有 100 筆資料。MySQL 伺服器重新啟動之後,這張表格會怎樣?
全文檢索:在文章裡找詞
要找內文提到 merger 的文章,第一個想到的寫法是 WHERE body LIKE '%merger%'。
這個寫法可以用,但有兩個問題:開頭是 % 時用不到索引,每次都要讀完整張表格;而且只能回答「有沒有」,不能依相關程度排序。
全文檢索(full-text search)先把文字切成一個一個的詞,建立「哪個詞出現在哪些資料列」的索引,查詢時再算出每一列的相關度。
課程的 article 表格在 title 與 body 兩個欄位上建立一個 FULLTEXT 索引:
CREATE TABLE article (
articleID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
body TEXT,
FULLTEXT (title, body)
) ENGINE=MyISAM;
SELECT title
FROM article
WHERE MATCH (title, body) AGAINST ('merger');
MATCH (…)裡的欄位必須和某一個 FULLTEXT 索引的欄位完全相同。只寫MATCH (title)會出現ERROR 1191 (HY000): Can't find FULLTEXT index matching the column list- 舊投影片說「只有 MyISAM 支援全文檢索」:這是 MySQL 5.5 以前的情況,InnoDB 從 MySQL 5.6 起也支援 FULLTEXT 索引
- 中文沒有空白分隔詞,預設的切詞方式不適用。中文內容要在建立索引時加上
WITH PARSER ngram,依固定長度(預設 2 個字)切詞
自然語言模式(natural language mode,預設的模式)的規則,以 MyISAM 為準:
| 規則 | 說明 |
|---|---|
| 至少符合一個詞 | AGAINST ('merge acquisition takeover') 找出含有其中任何一個詞的列 |
| 依相關度排序 | MATCH 寫在 WHERE 裡、又沒有 ORDER BY 時,結果依相關度由高到低排列 |
| 50% 門檻 | 出現在一半(含)以上資料列的詞被當成太常見的詞,不計分,所以查不到任何一列 |
| 最短詞長 | 少於 4 個字元的詞不會被索引(ft_min_word_len,MyISAM 預設 4) |
| 停用詞 | the、and、about 這類常見的詞不會被索引 |
50% 門檻常讓初學者困惑:在只有兩三筆資料的測試表格裡,幾乎每個詞都超過一半,什麼都查不到。
自然語言模式的全文檢索
article 表格有 6 篇文章(MyISAM)。第二個查詢把 MATCH 放在 SELECT 清單,可以看到每一列的相關度分數:沒有出現 merger 的列分數是 0。最後兩個查詢都查不到資料,但原因不同。
布林模式與 InnoDB 的全文檢索
在 AGAINST 的字串後面加上 IN BOOLEAN MODE,就可以用運算子控制條件。布林模式沒有 50% 門檻:
| 寫法 | 意思 |
|---|---|
linux java |
沒有運算子:含有任一個詞即可 |
+linux |
一定要有 linux |
-desktop |
不能有 desktop |
~Oracle |
含有 Oracle 的列相關度降低,但不排除 |
"Open Source" |
片語:兩個詞要依序相連出現 |
data* |
以 data 開頭的詞 |
課程的範例:
SELECT title FROM article
WHERE MATCH (title, body)
AGAINST ('+linux +"Open Source" -desktop Java ~Oracle' IN BOOLEAN MODE);
意思是:一定要有 linux、一定要有片語 Open Source、不能有 desktop;有 Java 的相關度提高,有 Oracle 的相關度降低。 布林模式不會自動依相關度排序,需要固定順序時自己加 ORDER BY。本課的直譯器在布林模式下的分數是簡化的計算,題目只看查到哪些列。
InnoDB 的全文檢索規則有幾點不同:
| MyISAM | InnoDB | |
|---|---|---|
| 50% 門檻 | 有 | 沒有 |
| 最短詞長 | 4(ft_min_word_len) |
3(innodb_ft_min_token_size) |
| 內建停用詞 | 數百個英文常用詞 | 36 個 |
| 相關度公式 | MyISAM 自己的公式 | 不同的公式,同樣的查詢分數不同 |
最短詞長是伺服器啟動時讀取的設定,修改後要重新啟動 MySQL,並重建 FULLTEXT 索引才會生效。
布林模式,以及轉成 InnoDB 之後
同一張 article 表格。前三個查詢使用布林模式;接著把表格轉成 InnoDB,再查一次上一個範例查不到的 SQL。
想一想:全文檢索
article(MyISAM,6 列)裡,software 出現在其中 3 列。下面的查詢結果是什麼?
SELECT COUNT(*) FROM article
WHERE MATCH (title, body) AGAINST ('software');
merger 出現在第 1 篇與第 5 篇;第 5 篇的內文還提到 telecom。下面的查詢傳回哪一篇的 articleID?
SELECT articleID FROM article
WHERE MATCH (title, body) AGAINST ('+merger -telecom' IN BOOLEAN MODE);
把 article 轉成 InnoDB 之後,同樣用自然語言模式查 software:
ALTER TABLE article ENGINE=InnoDB;
SELECT COUNT(*) FROM article
WHERE MATCH (title, body) AGAINST ('software');
結果是什麼?
動手寫:布林模式的全文檢索
news 資料庫的 article 表格(MyISAM,FULLTEXT 索引在 title, body 上)已經準備好了。請列出提到 java 或 linux、但沒有提到 desktop 的文章的 articleID 與 title,依 articleID 由小到大排序。
重點整理
| 引擎 | 重點 |
|---|---|
| InnoDB | 預設引擎;交易、外鍵、資料列鎖定、當機自動復原,資料依主鍵排列 |
| MyISAM | 不支援交易與外鍵,表格層級鎖定;舊系統才會用到 |
| MEMORY | 存在記憶體,重新啟動後資料消失;不能有 TEXT、BLOB |
| CSV | 文字檔,欄位必須 NOT NULL,不能有索引 |
| ARCHIVE | 壓縮存放,只能 INSERT 與 SELECT |
| BLACKHOLE | 寫入的資料直接丟掉 |
| MERGE(MRG_MyISAM) | 把結構相同的 MyISAM 表格合起來查詢;INSERT_METHOD 決定寫到哪一張 |
- 指定引擎用
ENGINE=(TYPE=已移除);轉換用ALTER TABLE 表格 ENGINE=引擎,會重建整張表格 - 查詢引擎:
SHOW ENGINES、information_schema.TABLES的ENGINE欄位 - ISAM、BDB 已移除;HEAP 是 MEMORY 的舊名稱
- 全文檢索:
FULLTEXT (欄位)建立索引,MATCH (欄位) AGAINST ('詞')查詢,MATCH 的欄位要與索引完全相同 - 自然語言模式(MyISAM)有 50% 門檻、最短詞長 4;布林模式用
+、-、~、"片語"控制條件;InnoDB 沒有 50% 門檻、最短詞長 3
下一課介紹 InnoDB 最重要的功能:交易。