Home » Posts tagged "primary"

InnoDB 對 Primary Key 的選擇

前幾天 Ant 在「淺入淺出 MySQL & PostgreSQL」剛好有提到,結果 Percona 這兩天也丟出了這個題目,不過這邊討論的是空間的問題:「Illustrating Primary Key models in InnoDB and their impact on disk usage」。

一樣的作法,Primary Key 的選擇有三種:

  • INT + AUTO_INCREMENT
  • BINARY(16) (Ordered UUID)
  • CHAR(36) (Random UUID)

用的是 Jeremy Colespace-lsn-age-illustrate 畫出 LSN 的值 (InnoDB 的 Log Sequence Number,由於嚴格遞增,可以藉由這個值知道每個 page 最新被修改的時間):

I then used the powerful tool innodb_space’s function space-lsn-age-illustrate (from Jeremy Cole’s innodb_ruby project) to plot the LSN (InnoDB’s Log Sequence Number, an always-incrementing value) pages from each table that uses the different Primary Keys via ASCII colour (so hot, right? Thanks Jeremy!!).

測試是 INSERT-only 的 case,雖然不太能理解為什麼要用 CHAR(36) 存 UUID,而非與 BINARY(16),但可以看出一些 pattern。

有順序的 INT + AUTO_INCREMENT 與 BINARY(16) (Ordered UUID) 都可以看出層次 (一直往後寫),而且也看得出來 BINARY(16) 比 INT 大了不少:

而 CHAR(36) 當然是最大的,而且寫入的 i/o 也最隨機:

Amazon DynamoDB 筆記

Amazon DynamoDB 頁面上的介紹:

Amazon DynamoDB is a fast and flexible NoSQL database service for all applications that need consistent, single-digit millisecond latency at any scale.

資料型態的部份就跳過去了,這篇筆記的重點在於 index 的部份 (了解他如何 scale),尤其是對 RDBMS 有了解的人要如何從他所設計的架構理解 DynamoDB 的 index。

理論基礎是 Amazon 在 2007 年丟出的論文「Dynamo: Amazon’s Highly Available Key-value Store」,這篇論文影響了很多 open source project。

DynamoDB 的 index 有 Primary Key、Local Secondary Index Key (LSI) 以及 Global Index Key (GSI),在「DynamoDB Data Model」這篇有介紹。

這邊會拿 Blogger.com 這種多人的 Blog Hosting 當例子:

  • 一個 user 可以有很多 blog。(table user)
  • 一個 blog 可以有很多 post。(table blog)
  • 一篇 post 可以有很多 comment。(table post)

接下來就從 Primary Key 開始講。

Primary Key

Primary Key 保證唯一,這也是 DynamoDB 裡面可以達到 RDBMS 的 UNIQUE KEY 效果的最佳方式。

有兩種 Primary Key 的型態,一種叫做 Hash,另外一種叫做 Hash-Range。

兩種都需要指定某一個欄位是 Hash-based column,當作切割 (partition) 的依據。

第一種:Hash

以 table user 來說,可以拿 user_id 來當作 Hash-based column,裡面有 blog_id 的 list。

以 table blog 來說,可以拿 blog_id 來當作 Hash-based column,裡面有 post_id 的 list。

要注意的是,如果表格 PK 是 Hash,那麼就不能使用 LSI 與 GSI 了。只有另外一種型態 (Hash-Range) 才可以用 LSI 與 GSI。

相對的,Hash-based 的表格因為功能有限,效率通常很好 XDDD

第二種:Hash-Range

其實 Hash-Range 是一種別的 LSI,兩者最大的差異就是唯一性了。

另外一種 Primary Key 是 Hash-Range,他需要指定兩個欄位,其中其中 Hash 的欄位就如同上面的解釋,當作資料切割的依據。這邊的唯一性是指 (Hash column, Range column) 唯一,而非只有 Hash 唯一或是 Range 唯一。

剛剛說到需要指定的另外一個欄位,被稱為「Range」的原因是因為他可以有效率的以 hash + range query 查詢資料。

以 table post 來說,可以拿 blog_id 當作 Hash-based column,再拿 post_id 當作 Range-based column,等下我們介紹 LSI 時再拿發表時間欄位排序。

同理,table comment 可以拿 (post_id, commend_id) 當 PK。

Query

PK 是 Hash 的當然就是指定 Hash-based column 直接查,條件只能是等號。

PK 是 Hash-Range 的除了可以用 Hash-based column 直接查 (還是只能用等號),另外可以用 Hash-based column + Range-based column 查。

以 SQL 的想法就像是 WHERE hash_col = 123 AND range_col BETWEEN (123, 456) 的感覺。反正 Hash-based column 一定要等號。

講到這邊,其實讀過上面提到的 Amazon 那篇論文應該就大概有感覺架構是怎麼搞的了:(這是推敲出來的,未必是實際架構)

  • 用 Hash-based column 切 consistent hash ring 塞到不同機器上。PK 是 Hash 的到這邊就搞定了。
  • PK 是 Hash-Range 的,還是照上面一條提到的,用 Hash-based column 切開,所以同樣的 Hash-based column 的資料都會塞到同一台機器上,於是就可以用有效率的 ordered tree 來存放 Range-based column 的資料,這樣就可以提供 query 了。

當然,考慮到需要實做 rebalance 機制以逐步擴充,這邊 consistent hash ring 的部份的作法可以更細膩,不過就不是這篇要談的重點了。

接下來要講重頭戲 LSI 與 GSI 了。

Local Secondary Index (LSI) 與 Global Secondary Index (GSI)

前面有提到 LSI 與 GSI 必須 PK 是 Hash-Range 的情況下能用,兩者都不強制唯一性。

LSI 與 GSI 都是 (Hash-column based, Range-column based) 的形式,差別在 LSI 的 Hash-column based column 必須跟 PK 的相同,GSI 的可以不用一樣。

所以對於 table post 可以加一個 LSI (blog_id, post_datetime),就可以用 WHERE blog_id = 123 ORDER BY post_datetime DESC 拉出對應的文章了。

同理,table comment 是 (post_id, comment_datetime)。

資料庫裡 Primary Key 的選擇...

Ingram Chen 寫的「Obvious Choice」這篇舉的例子剛好是在討論資料庫裡 Primary Key 的設計。

這個問題之前在 interview 的時候曾經拿出來問。問到資料庫相關的題目時,有些人在設計 schema 會多放一個 id 欄位,有些人則不會放。

有放 id 欄位的我就會問「為什麼要放」,沒有 id 欄位的我就會問「為什麼不這樣設計」。重點不在於哪個才是正確的,而是在於要能夠說明為何這樣選擇。

不過大多數人都是「看別都這樣做,所以我就這樣做」,可惜...

用 InnoDB 時關於 PRIMARY KEY 的建議

Percona 的「InnoDB scalability issues due to tables without primary keys」這篇文章在討論 InnoDB 在沒有 PRIMARY KEY 時的效能問題。

在討論效能問題前,應該先讀過 MySQL 官方文件裡提到 InnoDB index 架構的文章,其中就有提到 PRIMARY KEY 以及其他的 INDEX KEY 的底層架構:「InnoDB Table and Index Structures」。

InnoDB 是 clustered index 架構 (關於 clustered index 的完整說明,可以參考維基百科的「Database index」條目),也就是說,資料本身 (row data) 存放時會按照某個順序存放,這邊的順序是按照這樣的方式定義的:

  • 如果你有指定 PRIMARY KEY,那麼就會直接用 PRIMARY KEY 當作 clustered index。
  • 如果沒有指定 PRIMARY KEY,但有 UNIQUE INDEX,而且所有欄位都是 NOT NULL,那麼就用這組 UNIQUE INDEX (NOT NULL) 當作 clustered index。
  • 如果都沒有指定 PRIMARY KEY,那麼就會產生一個隱藏的欄位,是一個 6 bytes 的 auto increment 的數字,用這個欄位當 clustered index。也因為如此,在這種情況下,資料會依照建立的順序存放。

另外,InnoDB 的 secondary index 會指到 PRIMARY KEY (B+Tree 的 value 部分是放 PRIMARY KEY)。

所以,一般在規劃資料庫時建議的作法是:

  • 所有的表格都要有 PRIMARY KEY。
  • PRIMARY KEY 必須是 INT UNSIGNED (4 bytes),只有在需要 BIGINT UNSIGNED (8 bytes)的時候才用 BIGINT UNSIGNED。

對於有大量 secondary index 的表格更應該這樣做 (因為可以省下大量空間)。而對於現代的 ORM 來說,也都幾乎要求要有 PRIMARY KEY,甚至有些 ORM 要求 PRIMARY KEY 必須是 single column。

如果你都能了解後,再去看 Percona 討論沒有 PRIMARY KEY 的情況時,才能了解他們想要討論什麼事情... 裡面還包含了 InnoDB 格式的差異。

把 MySQL MyISAM 換到 Galera Cluster 的 InnoDB 上...

在「Switching from MySQL/MyISAM to Galera Cluster」這邊看到一個 script 可以檢查 MySQL MyISAM 換到 InnoDB,而且預定要換成 Galera Cluster 時的問題。

常見的問題都有檢查到,還蠻有用的:

  • 針對沒有 Primary Key 的表格提出警告,讓管理者規劃補上 Primary Key。
  • 針對 MyISAM 換成 InnoDB 後造成 Primary Key 太長的表格提出警告,讓管理者想辦法修改。

Galera Cluster 無法處理沒有 Primary Key 表格的刪除動作:(可以參考「MariaDB Galera Cluster - Known Limitations」這邊的說明)

DELETE operation is unsupported on tables without primary key. Also rows in tables without primary key may appear in different order on different nodes. Don't use tables without primary key.

不過每個表格都要有 Primary Key 並不難,因為如果有正規化時通常都會達到目標。就算不去用他也還是可以設計一個 INT UNSIGNED PRIMARY KEY AUTO_INCREMENT 放著。(過大的時候再換成 BIGINT UNSIGNED)

用 Percona XtraBackup 備份時用 compact 模式節省空間...

在「Feature preview: Compact backups in Percona XtraBackup」看到的,2.1 版會導入 compact backup 節省備份出來的空間 (目前是 2.0):

As you may know InnoDB PK (Primary Key) contains all data, and all secondary indexes are only subset of columns of Primary Key. So in theory we can store only PK, and re-build secondary indexes as we need. Well, now it is possible not only in theory.

secondary index 可以事後再建,所以有兩種表格會省下很多資源:

  • Index 很多的表格。
  • PK 欄位空間很大的表格 (像是用 VARCHAR(255) 當 PK)。

等出了再來研究看看對 Percona XtraDB Cluster (PXC) 重新同步可以加快多少...

Archives