# MySQL: INDEX & Partition ###### tags: `RD1` :::spoiler 目錄 [TOC] ::: ## INDEX ### 索引類型 * **主索引鍵 ( primary key )** 一個表中通常會有一個,而且只能有一個。 設定為主索引鍵的欄位值**不可重複**,而且**不可儲存NULL值**。 * **唯一索引 ( unique index )** 也稱為「不可重複索引」,在一個表中,設定為唯一索引的欄位值不可以重複, 但是**可以儲存NULL值**。 * **非唯一索引 ( non-unique index )** 用來增加查詢與維護資料效率的索引。 設定為非唯一索引的**欄位值可以重**複,也**可以儲存NULL值**。 * **全文檢索 ( full-text search)** 只能用在「 CHAR 」、「 VARCHAR 」與「 TEXT 」型態的欄位字元型態 且**允許NULL**的欄位所組成。 ### 操作方法 ```sql 一次建立一個 CREATE INDEX email_index ON table_name(email); 一次可建立多個 ALTER TABLE table_name ADD INDEX(email); ``` ```sql 刪除索引 DROP INDEX email ON table_name; ``` ```sql 查看索引 SHOW INDEXES FROM table_name IN database_name;; ``` ### 複合索引 搭配好幾個欄位做搜尋排序時,使用複合索引的效率比單一索引效率要高。 ```sql ALTER TABLE member ADD INDEX name_of_index (name, age, sex); ``` 1. 在建立複合索引時要考慮索引的先後順序,愈有識別性的欄位應該放在前面 ex. `index_1(name, age, sex)` 比 `index_2(sex, name, age)` 要有效率 2. 最左前綴 ( Leftmost Prefixing ) 概念 ``` name, age, sex name, age name ``` 創建複合索引 (A,B,C) 相當於創建一個單一索引 A 和兩個複合索引 A+B 與 A+B+C ### 建立索引之原則 最常見在「WHERE」子句中判斷條件的欄位,還有「 ORDER BY 」與「 GROUP BY 」中指定的欄位,也都可以使用建立索引來增加效率。 不過建立這樣的索引的前提,表格所儲存的資料量要夠大,如果量不大的話,建立索引反而會浪費儲存的空間,效率也增加不多,而且還會讓執行新增或修改時的效率變差。 1. **選擇唯一性索引:** 唯一性索引的值是唯一的,可以更快速的透過該索引來找尋資料。 2. **經常需要排序、分組和操作的欄位建立索引:** 排序會浪費很多時間。 如果建立索引,可以有效地避免排序操作。 3. **索引不要建立過多:** 每個索引都需要佔用空間,索引越多,需要的空間就越大。 4. 索引的欄位儘量不要有「NULL」值 5. **最左前綴匹配原則:** 最具識別性的欄位放在鍵的左邊 6. 雖然某個欄位很常使用在 WHERE、 ORDER BY 或 GROUP BY 子句中,也不一定要建立索引。 例如性別欄位的值只有兩種(F , M),建立索引所增加的效率也不多。 7. 如果有 or,where 後面就必須全部條件有 index,否則 index 將不會起作用,所以要使用 or 的就要為相對應的欄位加上 index [參考資料](https://medium.com/@michael80402/mysql索引-e002f707a5f4) ## Partition (分區) 在建立資料表同時建立分區,新增資料時按照分區條件劃入不同空間中。 是一種水平分區的方式,將同一個資料表的資料分成不同的檔案儲存,甚至可以放在不同磁碟, 查詢時針對特定分區執行,縮小範圍進而增加存取速度 (分區後所有的資料還是在同一張表) ### 四種方式 (range、list、hash、key) 採用哪種分區方式要取決於需求以及資料本身特性,如果選到不適合的分區方式,效能甚至會不進反退。 不同的分區方式,也有不同的維護方法,在決定方式前一定都要考量到。 * Range 連續值橫向分區: 資料以id依照範圍設定分為三個區 ```sql PARTITION BY RANGE (id) ( PARTITION p0 VALUES LESS THAN (11), PARTITION p1 VALUES LESS THAN (21), PARTITION p2 VALUES LESS THAN MAXVALUE ); ``` > 註:若用來分區的欄位不是主鍵,會出現錯誤訊息 * List 列舉值橫向分區: 指定集合內的元素(int)分別屬於哪個分區。 ```sql PARTITION BY LIST (id) ( PARTITION pA VALUES IN (1,3,5), PARTITION pB VALUES IN (12,15) ); ``` * Hash 讓資料在設定好的分區均勻分布: 指定分區數量 N,以單一 int 欄位或以回傳 int 的表達式結果,決定資料屬於哪個分區。 ```sql CREATE TABLE aa ( id INT NOT NULL, mydate DATE NOT NULL DEFAULT '1970-01-01' ) PARTITION BY HASH(id) /or/ PARTITION BY HASH(YEAR(mydate)) PARTITIONS 3; ``` * Key(與hash分區類似): 指定分區數量 N,支援多個欄位為 partition key,用內建 hash function 決定資料屬於哪個分區。 [MySQL 資料表分區(partition)](https://xyz.cinc.biz/2015/07/mysql-partition.html) ## 實作紀錄 專案:https://github.com/esther-lin069/go-mysql-api ![](https://i.imgur.com/9cPH1wz.png) ```sql room_id BETWEEN 1 AND 100 AND type = 'normal' AND sender = 'Anna' AND read_tab = 0 ``` * A測資:查詢條件如上,當前DB資料量為4583164筆(662MB) * B測資:查詢條件如上,當前DB資料量為9980689筆(1.3GB) ### 沒有索引 > **A.** DB執行時間:1.51620212s / 資料列數:2333 > **B.** DB執行時間:10.3352946s / 資料列數:5041 ### 單個索引 1. 聊天室id > **A.** DB執行時間:1.535088086s / 資料列數:2333 > **B.** DB執行時間:10.9309112s / 資料列數:5041 2. 發言類型 ==更耗時== > **A.** DB執行時間:20.507707785s / 資料列數:2333 > **B.** DB執行時間:1m34.1675092s / 資料列數:5041 3. 發言人(共有20個) > **A.** DB執行時間:1.890655134s / 資料列數:2333 > **B.** DB執行時間:23.2711352s / 資料列數:5041 4. 已讀標記 ==更耗時== > **A.** DB執行時間:22.053848687s / 資料列數:2333 > **B.** DB執行時間:2m59.8551417s / 資料列數:5041 5. 分別建立發言人與類型兩個索引 ==變快?== ```sql ALTER TABLE messages ADD INDEX(sender) ALTER TABLE messages ADD INDEX(type) ``` > **A.** DB執行時間:605.020578ms / 資料列數:2333 > **B.** DB執行時間:9.133879s / 資料列數:5041 ### 複合索引 A容量狀態為: ![](https://i.imgur.com/L5gtsh6.png) 1. 為發言人、類型、已讀標記增加複合索引 ```sql ALTER TABLE `chat_data`.`messages` ADD INDEX `sender_type_read_index` (`sender`, `type`, `read_tab`) ``` > **A.** DB執行時間:178.530348ms / 資料列數:2333 > **B.** DB執行時間:3.100755s / 資料列數:5041 上述複合索引加上聊天室id的單索引 ```sql ALTER TABLE messages ADD INDEX(room_id) ``` > **A.** DB執行時間:176.098068ms / 資料列數:2333 (沒有明顯變化) > **B.** 2. 發言人、類型複合索引(沒有已讀標記的版本) ```sql ALTER TABLE `chat_data`.`messages` ADD INDEX `sender_type_index` (`sender`, `type`) ``` > **A.** DB執行時間:289.66093ms / 資料列數:2333 > **B.** DB執行時間:5.4436063s / 資料列數:5041 3. 發言人、聊天室id、類型、已讀標記4複合索引 ==超快== ```sql ALTER TABLE `chat_data`.`messages` ADD INDEX `sender_room_type_read_index` (`sender`, `room_id`, `type`, `read_tab`) ``` > **A.** DB執行時間:28.988757ms / 資料列數:2333 > **B.** DB執行時間:278.2587ms / 資料列數:5041 4. 聊天室id、發言人、類型3複合索引 ==沒有幫助== ```sql ALTER TABLE `chat_data`.`messages` ADD INDEX `room_sender_type` ( `room_id`,`sender`, `type`) ``` > **A.** DB執行時間:1.523979225s / 資料列數:2333 > **B.** DB執行時間:10.6058784s / 資料列數:5041 (需要掃過所有資料,沒有效率的索引) 5. 發言人、類型、聊天室id 3複合索引 ==快== ```sql ALTER TABLE `chat_data`.`messages` ADD INDEX `sender_type_room_index` (`sender`, `type`,`room_id`) ``` > **A.** DB執行時間:33.912341ms / 資料列數:2333 > **B.** DB執行時間:375.1045ms / 資料列數:5041 ### 分區:Range 1. 依照id 2. 依照room_id (250一區,分四區) 資料總筆數 5059044 ![](https://i.imgur.com/vHttdr7.png) * 同上測試條件 > DB執行時間:767.0391ms / 資料列數:2558 (比原始狀態快一倍) * 搜索跨區資料:聊天室id範圍調整為200-300 > DB執行時間:1.6681611s / 資料列數:2497 (跟原始狀態差不多) * 再加上發言人、類型、已讀標記三複合索引 > DB執行時間:28.4168ms / 資料列數:2497 ### 分區:List **list的分組對象必須是int類型且為主鍵** 1. 依照read_tab 資料總筆數 4958729 ![](https://i.imgur.com/p8r3xuR.png) > DB執行時間:1.7339688s / 資料列數:2396 (沒啥幫助)