[英]MySQL performance: multiple tables vs. index on single table and partitions
我想知道什么在性能上更高效、更快:
在一個大表或多個沒有索引的小表上有一個索引?
由於這是一個非常抽象的問題,讓我讓它更實用:
我有一張關於用戶統計信息的表(20,000 個用戶和大約 3000 萬行)。 該表有大約 10 列,包括user_id
、 actions
、 timestamps
等。
最常見的應用是: 按user_id
插入數據並按user_id
檢索數據( SELECT
語句從不包含多個user_id's
)。
現在到目前為止,我在user_id
上有一個INDEX
,查詢看起來像這樣
SELECT * FROM statistics WHERE user_id = 1
現在,隨着行越來越多,表格變得越來越慢。 INSERT
語句變慢,因為INDEX
越來越大; SELECT
語句會變慢,因為要搜索的行數更多。
現在我想知道為什么不為每個用戶有一個統計表,而是將查詢語法更改為這樣的:
SELECT * FROM statistics_1
其中1
顯然代表user_id
。
這樣,不需要INDEX
並且每個表中的數據少得多,因此INSERT
和SELECT
語句應該快得多。
現在我的問題再次:
處理如此多的表(在我的情況下為 20,000)而不是使用一個帶有INDEX
表是否有任何現實世界的缺點?
我的方法是否真的會加快速度,或者表格的查找最終會比一切都減慢速度嗎?
創建 20,000 個表是個壞主意。 不久您將需要 40,000 張桌子,然后更多。
我在我的書SQL Antipatterns中將這種綜合症稱為元數據 Tribbles 。 每次您計划創建“每個 X 的表”或“每個 X 的列”時,您都會看到這種情況。
當您擁有數萬個表時,這確實會導致真正的性能問題。 每個表都需要 MySQL 來維護內部數據結構、文件描述符、數據字典等。
還有實際的操作后果。 您真的想創建一個系統,每次新用戶注冊時都需要您創建一個新表嗎?
相反,我建議您使用MySQL Partitioning 。
以下是對表進行分區的示例:
CREATE TABLE statistics (
id INT AUTO_INCREMENT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY (id, user_id)
) PARTITION BY HASH(user_id) PARTITIONS 101;
這為您提供了定義一個邏輯表的好處,同時還將該表划分為許多物理表,以便在查詢分區鍵的特定值時更快地訪問。
例如,當您像示例一樣運行查詢時,MySQL 僅訪問包含特定 user_id 的正確分區:
mysql> EXPLAIN PARTITIONS SELECT * FROM statistics WHERE user_id = 1\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: statistics
partitions: p1 <--- this shows it touches only one partition
type: index
possible_keys: NULL
key: PRIMARY
key_len: 8
ref: NULL
rows: 2
Extra: Using where; Using index
分區的 HASH 方法意味着將行按整數分區鍵的模數放置在分區中。 這確實意味着許多 user_id 映射到同一個分區,但每個分區平均只有 1/N 的行數(其中 N 是分區數)。 並且您使用恆定數量的分區定義表,因此您不必在每次獲得新用戶時擴展它。
您可以選擇最多 1024 個(或 MySQL 5.6 中的 8192 個)的任意數量的分區,但有些人報告說當分區數達到如此高時會出現性能問題。
建議使用質數分區。 如果您的 user_id 值遵循某種模式(例如僅使用偶數),則使用質數分區有助於更均勻地分布數據。
在評論中回復您的問題:
如何確定合理數量的分區?
對於 HASH 分區,如果您使用 101 個分區,就像我在上面的示例中顯示的那樣,那么任何給定的分區平均大約有 1% 的行。 你說你的統計表有 3000 萬行,所以如果你使用這種分區,你每個分區只有 30 萬行。 這對 MySQL 來說更容易閱讀。 您也可以(並且應該)使用索引——每個分區都有自己的索引,並且它只有整個未分區表的索引大小的 1%。
因此,如何確定合理的分區數量的答案是:整個表有多大,以及您希望分區平均有多大?
分區的數量不應該隨着時間的推移而增長嗎? 如果是這樣:我怎樣才能自動化?
如果您使用 HASH 分區,則分區數量不一定需要增加。 最終您可能總共有 300 億行,但我發現當您的數據量以數量級增長時,無論如何都需要一個新的架構。 如果您的數據增長那么大,您可能需要在多個服務器上進行分片以及分區到多個表中。
也就是說,您可以使用 ALTER TABLE 重新分區表:
ALTER TABLE statistics PARTITION BY HASH(user_id) PARTITIONS 401;
這必須重構表(就像大多數 ALTER TABLE 更改一樣),因此預計需要一段時間。
您可能希望監視分區中數據和索引的大小:
SELECT table_schema, table_name, table_rows, data_length, index_length
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE partition_method IS NOT NULL;
與任何表一樣,您希望活動索引的總大小適合您的緩沖池,因為如果 MySQL 必須在 SELECT 查詢期間交換部分索引進出緩沖池,則性能會受到影響。
如果您使用 RANGE 或 LIST 分區,則添加、刪除、合並和拆分分區更為常見。 見http://dev.mysql.com/doc/refman/5.6/en/partitioning-management-range-list.html
我鼓勵您閱讀有關分區的手冊部分,並查看這個不錯的演示文稿: 使用 MySQL 5.1 分區提高性能。
這可能取決於您計划經常進行的查詢類型,確定的最佳方法是實現兩者的原型並進行一些性能測試。
話雖如此,我希望帶有索引的單個(大)表總體上會做得更好,因為大多數 DBMS 系統都經過了大量優化,可以處理查找數據並將數據插入到大表中的確切情況。 如果您嘗試制作許多小表以希望提高性能,那么您就在與優化器(通常更好)作斗爭。
另外,請記住,一張桌子在未來可能更實用。 如果您想獲得所有用戶的匯總統計信息怎么辦? 擁有 20 000 個表會使執行起來非常困難且效率低下。 也值得考慮這些模式的靈活性。 如果你這樣划分你的桌子,你可能會把自己設計成未來的一個角落。
具體例子:
我有一張關於用戶統計信息的表(20,000 個用戶和大約 3000 萬行)。 該表有大約 10 列,包括 user_id、動作、時間戳等。最常見的應用是:通過 user_id 插入數據和通過 user_id 檢索數據(SELECT 語句從不包括多個 user_id)。
做這個:
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
...
PRIMARY KEY(user_id, id),
INDEX(id)
在 PK開始時使用user_id
為您提供“參考位置”。 也就是說,一個用戶的所有行都聚集在一起,從而最大限度地減少 I/O。
PK末尾的id
是因為 PK 必須是唯一的。
看起來很奇怪的INDEX(id)
是為了讓AUTO_INCREMENT
開心。
抽象問題:
PARTITIONing
PARTITIONed
表需要的索引集與未分區的等效表不同。Bill Karwins 的回答沒有什么可補充的。 但一個提示是:檢查是否始終需要完整詳細地提供用戶的所有數據。
如果您想提供使用情況統計信息或訪問次數或這些內容,您通常不會得到單個操作和秒的粒度,例如,從今天的角度來看 2009 年。 因此,您可以構建聚合表和存檔表(當然不是引擎存檔),以獲取有關操作庫的最新數據和舊操作的概覽。
我認為,舊的行為不會改變。
例如,您仍然可以使用存檔表中的 week_id 來詳細了解聚合。
代替每個用戶從 1 個表到 1 個表,您可以使用分區來達到中間某處的多個表/表大小比率。
您還可以保留用戶的統計信息,以嘗試將“活躍”用戶移動到 1 個表中,以減少隨着時間的推移您必須訪問的表的數量。
最重要的是,您可以做很多事情,但主要是您必須構建原型和測試,並且只需評估您所做的各種更改對性能的影響。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.