最近在思考~如何在資料庫裡設計一個PK。
是數字好呢?還是字串好?
是GUID好呢?還是自行定義的字串+流水號好呢?
GUID是沒有重複性的問題,但拿來做PK好像又太長!
那直接拿流水號或是自行定義的字串外加流水號呢?
因為字串+流水號是一個固定長度,但直接使用流水號長度會比較短!
都不知道該怎麼拿捏才好?
所以請教在大家,你們取PK都是考慮了哪些才決定的呢?
謝謝!
這個問題可以分成三個層次來回答:
從理論方面,在資料庫的教材中題到,primary key 是該 table 中具有唯一性與識別性的資料,若沒有單一資料具有此特性,也可以是兩個以上的資料合成具有此特性的 key 值,即可做為 primary key。是故,比如身份證號可以是個人資料的 PK,比如時間日期與發生標的物兩個欄位可以合成 Log 的 PK。
從實作方面,牽涉到開發工具、效能、使用的 DB 是否支援以及其最佳化的依據,通常會是以數值欄位、避免複合欄位等等為實作考量,因此很常見的是每個 table 都會開一個 auto increment 的欄位來做為 PK,不過這也違反資料庫理論中的《不在一個 table 中使用兩個具有重覆屬性的欄位》這個原則。比如個人資料 table 中,身份證號已經可以當 PK,但你又加一個 auto increment 的欄位來做為 PK,這就是浪費。
而從我的觀點,怎麼選 PK 只有一個原則:我的經驗。
意思就是要請wiseguy到家裡泡茶的意思
我這邊想提問的PK比較偏向資料庫內部的KEY,也就是此PK對使用者而言是沒有什麼意義的。
若是以會員資料表跟學生資料做比較的話,
會員表的帳號會是唯一值,但我會另外給一個member_key當作PK用來操作DML,避免一些大小寫或是邏輯上的問題。
而學生資料表當然就學號而言就是個很好的PK了,而學號對學生是有意義的。
是想請教類似這種需要自行定義PK的時候,該怎麼去選擇一個好的key值?
看你這個系統要求的是什麼?效率?程式好維護?table資料量大不大?....
所以我才說,到最後決定 PK 的關鍵,通常是開發者自己的經驗。
可否簡單說一下當初你規畫PK的時候,所考量的問題及最後選擇哪一類型的PK?
這只是我最近突然想瞭解的問題,我也沒什麼CASE可以提問。
以下是我的經驗,不是標準,也不是絕對正確,僅供參考:
第一考量:簡單就好。通常會先思考資料本身就能做 PK,那當然就用資料欄位。
第二考量:效率至上。資料量巨大、且有大量 join,那就考慮使用 auto increment 欄位當PK。
第三考量:程式單純且易維護。資料本身需要三個以上的欄位才能做 PK,那也考慮用 auto increment 欄位當PK。
第四考量:資料庫對PK或其它 index 有最佳化,那就看它 prefer 用什麼欄位。
比較好奇的問一下,有人會選擇GUID做為PK的嗎?
在什麼樣的情況下會選擇GUID做為PK呢?
就我看來,GUID長度太長又沒有什麼規則性,
但若是把GUID存成binary(假設是DB internal key),
變成電腦可以讀的語言,這樣子會比較有好嗎?
沒比較好。對DB而言,它就是個字串。除了固定長度是個微不足道的優點之外,其它跟使用一般字串當 PK 幾乎是相同的成本。
PK 完再決定....(大誤)
個人會考慮:
大約這樣去考慮.
要替Table加上PK,原則很簡單,就是您最常用那個欄位(或那幾個欄位,大部份情形是兩個,最好不要超過3個)作條件來查詢這個Table內的某一筆特定資料。
例如:員工資料表就是用員工編號(或身份證字號),訂單主檔就是訂單編號,訂單明細檔就是用訂單編號+項次。
因為大部份資料庫都會以PK作為資料存取優化的條件,例如在SQL Server中會依PK值作排序來存放每一筆記錄的順序,讓您在用PK作查詢條件時,能以最快的速度將資料取出。
學過的經驗告訴我 . 身份證字號理論上是 唯一性 但是 實務上不是唯一
故以系統的 PK 當作唯一是比較保險的做法
很多事情難保不會有例外
前面幾位已經說明很多,敝人在此不贅述.
樓主似乎是對UUID 與一般自動增加的方式,想做個了解.
在此作個測試.
產生測試資料,先參考敝人之前鐵人賽裡的測試資料的產生.
http://ithelp.ithome.com.tw/question/10133871
<pre class="c" name="code">SET SESSION max_heap_table_size = 1024 * 1024 * 200;
CALL prc_filler(1000000);
Query OK, 1 row affected (11.30 sec)
select count(1) from filler;
+----------+
| count(1) |
+----------+
| 1000000 |
+----------+
這樣就建立了一個一百萬筆的Table.
接著來建立幾個測試Table
CREATE TABLE ithelp1220a(
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
pad CHAR(200),
instime TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE ithelp1220b(
id INT UNSIGNED NOT NULL PRIMARY KEY,
pad CHAR(200),
instime TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE ithelp1220c(
id CHAR(36) NOT NULL PRIMARY KEY,
pad CHAR(200),
instime TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE ithelp1220d(
id BINARY(16) NOT NULL PRIMARY KEY,
pad CHAR(200),
instime TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
利用filler來產生測試資料.
INSERT INTO ithelp1220a(pad)
SELECT REPEAT('a', 200)
FROM filler;
Query OK, 1000000 rows affected (37.02 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
INSERT INTO ithelp1220b(id, pad)
SELECT id, REPEAT('a', 200)
FROM filler;
Query OK, 1000000 rows affected (31.47 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
INSERT INTO ithelp1220c(id, pad)
SELECT UUID(), REPEAT('a', 200)
FROM filler;
Query OK, 1000000 rows affected (42.51 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
INSERT INTO ithelp1220d(id, pad)
SELECT UNHEX(REPLACE(UUID(),'-','')), REPEAT('a', 200)
FROM filler;
Query OK, 1000000 rows affected (35.02 sec)
Records: 1000000 Duplicates: 0 Warnings: 0
觀察空間大小
SELECT table_name
, data_length / 1024 AS 'data(K)'
, row_format
FROM INFORMATION_SCHEMA.TABLES
WHERE table_name LIKE 'ithelp1220%'
ORDER BY table_name;
+-------------+-------------+------------+
| table_name | data(K) | row_format |
+-------------+-------------+------------+
| ithelp1220a | 243456.0000 | Compact |
| ithelp1220b | 243456.0000 | Compact |
| ithelp1220c | 277504.0000 | Compact |
| ithelp1220d | 233376.0000 | Compact |
+-------------+-------------+------------+
接著來觀察 1000萬筆資料的情形.
依樣葫蘆,建立3個空的Table,比較自動增加與兩種UUID的.
CREATE TABLE ithelp1220a1 LIKE ithelp1220a;
CREATE TABLE ithelp1220c1 LIKE ithelp1220c;
CREATE TABLE ithelp1220d1 LIKE ithelp1220d;
INSERT INTO ithelp1220a1(pad)
SELECT REPEAT('a', 200)
FROM ( SELECT id
FROM filler) a,
( SELECT id
FROM filler
LIMIT 10) b;
Query OK, 10000000 rows affected (6 min 9.34 sec)
Records: 10000000 Duplicates: 0 Warnings: 0
INSERT INTO ithelp1220c1(id, pad)
SELECT UUID(), REPEAT('a', 200)
FROM ( SELECT id
FROM filler) a,
( SELECT id
FROM filler
LIMIT 10) b;
Query OK, 10000000 rows affected (10 min 19.08 sec)
Records: 10000000 Duplicates: 0 Warnings: 0
INSERT INTO ithelp1220d1(id, pad)
SELECT UNHEX(REPLACE(UUID(),'-','')), REPEAT('a', 200)
FROM ( SELECT id
FROM filler) a,
( SELECT id
FROM filler
LIMIT 10) b;
Query OK, 10000000 rows affected (10 min 39.95 sec)
Records: 10000000 Duplicates: 0 Warnings: 0
觀察空間大小
SELECT table_name
, data_length / 1024 AS 'data(K)'
, row_format
FROM INFORMATION_SCHEMA.TABLES
WHERE table_name IN ('ithelp1220a1', 'ithelp1220c1', 'ithelp1220d1')
ORDER BY table_name;
+--------------+--------------+------------+
| table_name | data(K) | row_format |
+--------------+--------------+------------+
| ithelp1220a1 | 2311168.0000 | Compact |
| ithelp1220c1 | 3109888.0000 | Compact |
| ithelp1220d1 | 2820096.0000 | Compact |
+--------------+--------------+------------+
使用UUID方式,消耗時間較久,
空間會佔用較多,這是資料庫系統內部實作的關係.
若有想要進一步了解,篇幅較長,改日再探討吧.
我看了產生測試資料那邊文章,我有個疑問想請教你。
我自己測試了兩種產生資料的方法,
第一種是使用你的方法,先產生filler,再去塞資料。
而第二種是我把塞資料的語法直接寫在SP裡透過迴圈塞。
但這兩個方法的效率有著天差地別的結果,想請教一下是什麼原因?
一般來說,執行速度越接近核心會越快.
以這次的情境來說.我只用一道SQL Command就產生了.
用SP,可能就是很多道SQL Command.
用外部程式,也可能是很多道SQL Command,再加上網路傳輸(也許?).
所以這個情境下,我推測是以下順序.
SQL Command > SP > 外部程式.
當然變數會很多,這也不是絕對的,一切以實際為準.
加上我那個filler是用記憶體引擎,相對會更快.
所以我特別寫那篇,可以提供有需要產生測試資料的人參考.
至於執行速度方面,也是有人有不同的看法.
例如: http://ithelp.ithome.com.tw/question/10120801?tab=opinion#ooa_hash
<pre class="c" name="code">
CREATE PROCEDURE prc_filler2(INT)
BEGIN
DECLARE _cnt INT;
SET _cnt = 1;
WHILE _cnt <= cnt DO
INSERT
INTO ithelp1225(amount, price)
SELECT CEILING(RAND(201310) * 1000) + 100,CEILING(RAND(201310) * 1000) + 30.00;
SET _cnt = _cnt + 1;
END WHILE;
END;
這是我照你的CODE抄寫,只是把INSERT的地方直接改成ithelp1225而不填入filler。
這差異應該是若是使用SP直接做INSERT,那就是進行了100萬次的insert動作,
而使用filler則只是進行一次的insert的動作。
我想會造成效率有著天差地遠的原因應該就是在這了。
產生 filler的目的,很單純,就是產生一個table,有一定數量.
好方便後續用來join,就可以產生其他資料.
例如產生1萬筆,就可以用來再與自己join加上limit,這樣就可以產生自己想要的數量.
如例子中,並未直接產生1000萬筆,而是利用100萬筆的fille,join產生的.
把這種需要許多次insert,用一個Table先存放,再來join產生,
速度就會變快.
也可以用來產生日曆,這對系統開發來說,就會很方便.
1.系統OO化.別人都用正規化.正規化我不會,我習慣用物件導向方式
2.小系統用Int,流水號,不要用字串,int 是電腦處理基礎單位,所以排序,索引..最快
3.分散式DB用GUID 全世界每一個資料拋過來都不會沖到,而且是Client 端,App端產生
4.PK給App 給 DB 關聯看,所以只要不重複,容易電腦計算,如果給Users識別用,我會再用一組有規律的Key讓Users記