對 MySQL 表分區與 MERGE分表的個人理解

問題

最近數據庫表數據接近3000萬,查詢性能有點慢

分析

對於MySQL數據庫我已經做的:

  • 經常查詢的字段,已經建了索引,遵循左前綴原則。
  • 表已經分區,按照數據時間戳劃分,每年的數據一個分區。

那爲什麼還慢呢?排查發現,雖然分了區,但是有一個常用的查詢語句,默認並沒有帶時間戳這個字段,分區是按時間戳來劃分的,你又不帶時間戳這個字段,那不就得全分區檢索了嗎?所以慢啊,分區並沒有被有效利用。

就好像網上看到的一些例子,用日期分區後,想測試一下性能:

SELECT COUNT(*) FROM mytable;
SELECT * FROM mytable WHERE user_id = 34;

效果並不明顯……廢話,你查全部數據,並沒有使用時間戳來篩選,當然是全分區檢索了。

我認爲表分區和編程語言裏中的字典/Map是相似的,按某種規則建立起一個個存放數據桶,數據按照你給定的規則放進不同的桶裏。依據 關鍵字 來確定數據在哪個桶裏,這樣就一下子過濾掉了大部分無效數據。在我的例子裏,時間戳就是這個 關鍵字,如果你不帶這個 關鍵字,那就和未分區一樣,還是要在全部分區裏查找。

在網上看到的例子,大多是按時間/日期來分區的,所以心裏也有了一份執念——按時間劃分區比較合理,每年/月一個區,多合適。
其實,這個要看你的實際場景,按時間不一定就是好的。
我所查詢的數據,是按設備來查詢的,時間戳不是必要條件,但設備ID通常是必要條件。
那麼我就把分區改爲按照設備ID劃分,分爲20個表:

ALTER TABLE mytable 
DROP PRIMARY KEY,
ADD PRIMARY KEY (`id`, `device_id`) 
PARTITION BY LIST (device_id % 20)
(PARTITION p0 VALUES IN (1) ENGINE = InnoDB,
 PARTITION p1 VALUES IN (2) ENGINE = InnoDB,
 PARTITION p2 VALUES IN (3) ENGINE = InnoDB,
 PARTITION p3 VALUES IN (4) ENGINE = InnoDB,
 PARTITION p4 VALUES IN (5) ENGINE = InnoDB,
 PARTITION p5 VALUES IN (6) ENGINE = InnoDB,
 PARTITION p6 VALUES IN (7) ENGINE = InnoDB,
 PARTITION p7 VALUES IN (8) ENGINE = InnoDB,
 PARTITION p8 VALUES IN (9) ENGINE = InnoDB,
 PARTITION p9 VALUES IN (10) ENGINE = InnoDB,
 PARTITION p10 VALUES IN (11) ENGINE = InnoDB,
 PARTITION p11 VALUES IN (12) ENGINE = InnoDB,
 PARTITION p12 VALUES IN (13) ENGINE = InnoDB,
 PARTITION p13 VALUES IN (14) ENGINE = InnoDB,
 PARTITION p14 VALUES IN (15) ENGINE = InnoDB,
 PARTITION p15 VALUES IN (16) ENGINE = InnoDB,
 PARTITION p16 VALUES IN (17) ENGINE = InnoDB,
 PARTITION p17 VALUES IN (18) ENGINE = InnoDB,
 PARTITION p18 VALUES IN (19) ENGINE = InnoDB,
 PARTITION p19 VALUES IN (0) ENGINE = InnoDB);

這樣就可以每次查詢的時候,先按分區規則定位到分區,只在分區中檢索數據了,很快。

MySQL 表分區與 MERGE 分表的比較

MERGE 分表

在學習過程中,也看到了 MERGE 分表的方法。

  • 只能使用MyISAM引擎,
  • 需建立多個子表
  • 建立一個總表,總表中把幾個子表聯合起來,定義數據插入到First/Last子表
  • 子表、總表表結構必須相同
  • 總表不存儲數據
  • 所有子表中的數據ID 不是唯一的,會重複(可建個專門生成ID的中間表解決)
  • 插入、查詢數據只需要操作總表

看到有人說 MERGE 分表比較簡單,應用程序無需改動,只要操作總表即可。
是這樣的,插入是沒問題,但如果查詢仍然操作總表的話,是不會提升性能的,因爲還是要檢索所有子表內的數據。
所以MERGE拆表,應該主要是做數據拆分,如果要提升查詢性能,還是要改程序,直接定位要查詢的子表,才能快。比較適合按日期劃分子表的場景吧。

我想它的優點應該是靈活:

  • 可以方便地增加子表
  • 更新總表,把新的子表聯合進去。因爲總表不存儲數據,所以表更新很容易。很容易就擴充了
表分區

表分區是真正的對上層應用程序隱形,不需要改動程序。
查詢時帶上關鍵字字段,大大縮小查詢範圍。我覺得它的缺點是不靈活:

  • 插入更新會變慢,因爲要確定分區
  • 分區一旦創建就固定了,要想更改分區,會導致表內數據的重新分配調整。如果數據量很大,將花費巨量時間。
    先創建一個重新分區的新表,再把舊錶的數據轉移過去是個更好、更快的方案,轉移時可以加上 id 篩選部分數據,這樣就可以分次、分批轉移了:
INSERT INTO mytable_new SELECT * FROM mytable WHERE id >= 1 AND id < 1000000;

轉移成功後,再把新表、舊錶重命名一下,就完成了表替換。

可以用 explain 來確認查詢的分區

EXPLAIN SELECT * FROM mytable WHERE device_id = 36;

在這裏插入圖片描述
即使直接使用ID進行查詢,加上關鍵字字段,也能縮小查詢範圍

EXPLAIN SELECT * FROM mytable WHERE id = 3672 AND device_id = 36;

上面如果不帶device_id ,只帶id查詢,也很快,但仍然是在所有分區檢索。帶上device_id 就直接定位到了一個分區,更快。

如果查詢條件中沒帶device_id,那分區就根本沒有被利用到了。下面語句會在所有分區中篩選數據

SELECT * FROM mytable WHERE class_id = 32 AND ts BETWEEN 1593234321 AND 1594526324;

所以表分區後,上層應用程序查詢時,也要使用 關鍵字 字段,使分區能夠有效

總結

對於提升查詢性能:
其實表分區就是數據庫按既定規則幫你定位分區,縮小了查詢範圍。
而 MERGE 分表需要你在應用程序層面,按規則定位到子表,縮小查詢範圍。

對數據庫的優化不只是數據庫,還要『優化』使用它的人。數據庫優化好了,某一個開發人員寫了一個SQL語句,索引全部未命中,分區條件字段未使用……那好了,就是全表掃描,對於大表的查詢SQL,還是要仔細斟酌,瞭解表結構、索引設計纔能有好的效率,避免全表掃描。

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