[譯] 使用 PostgreSQL 的一些建議

1. 數據庫編碼

1.1 不要使用 SQL_ASCII 編碼

1.1.1 爲何

雖然這個名稱看起來和 ASCII 有關係,但並不是如此。相反,它只是禁止使用空字節。html

更重要的是,SQL_ASCII 對全部的編碼轉換的函數而言意味着「不轉換」。也就是說,原始字節編碼是什麼就是什麼。除非特別當心,不然 SQL_ASCII 編碼的數據庫可能最終會存儲許多不一樣編碼的混合數據,可能沒法可靠地恢復原始字符。git

1.1.2 何時可使用

若是你的輸入數據已是編碼的混合的數據,例如 IRC 的日誌或非 MIME 兼容的電子郵件,那麼 SQL_ASCII 多是最後的方法 - 應該首先考慮使用 bytea 編碼,或者能夠檢測是否爲 UTF8 編碼,若是非 UTF8 編碼,例如 WIN1252 編碼的數據能夠假定爲 UTF8 編碼。github

2. 工具

2.1 不要使用 psql -W 或者 psql --password

不要使用 psql -W 或者 psql --password正則表達式

2.1.1 爲何

若是使用 --password 或 -W 標誌鏈接服務,psql 會提示你須要輸入密碼 - 所以即便服務器不須要密碼,也會提示你輸入密碼。算法

這個選項沒有必要,它會讓人誤覺得服務器須要密碼。若是你登陸的用戶沒有設置密碼,或者你在提示時輸入了錯誤的密碼,你仍然會成功登陸並認爲這就是正確的密碼 - 但你沒法使用這個密碼從其餘客戶端(經過 localhost 鏈接)或以其餘用戶身份登陸。sql

2.1.2 何時可使用

不要使用。數據庫

2.2 不要使用 RULE

不要使用 RULE,(譯者注: CREATE RULE 定義一個適用於特定表或者視圖的新規則)若是想要用,請使用觸發器替代。安全

2.2.1 爲何

RULE 很是強大,但並很差理解。它看起來像是一些條件邏輯,但實際上它會重寫查詢或向查詢中添加其餘查詢。服務器

這意味着全部 non-trivial 的規則都是不正確的。(譯者注:關於 non-trivial 的定義參考引用文章)app

Depesz 對此有更多話要說

2.2.2 何時可使用

不要使用。

2.3 不要使用表繼承

不要使用表繼承,若是真的要用,可使用外鍵來替代。

2.3.1 爲何

表繼承是一個時髦的概念,其中數據庫與面向對象的代碼緊耦合。事實證實,這些耦合的東西實際上並無產生預期的結果。

2.3.2 何時可使用

幾乎不使用……差很少。如今表分區是本地完成的,表繼承的常見場景已經被一些特性所取代。

3. SQL 語句

3.1 不要使用 NOT IN

不要使用 NOT IN,或 NOT 和 IN 的任意組合,如 NOT(x IN (select...))。

(若是你認爲你想要 NOT IN (select...) 那麼你應該使用 NOT EXISTS 替代。)

3.1.1 爲何

兩個理由:

  1. 若是存在 NULL 值,則 NOT IN 會以意外的方式運行:
select * from foo where col not in (1,null);
  -- always returns 0 rows

select * from foo where col not in (select x from bar);
  -- returns 0 rows if any value of bar.x is null
複製代碼

發生這種狀況是由於若是 col = 1 則 col IN(1,null) 返回 TRUE,不然返回 NULL(即它永遠不會返回 FALSE)。因爲 NOT(TRUE) 爲 FALSE,但 NOT(NULL) 仍爲 NULL,所以 NOT(col IN(1,null)) (與 col NOT IN(1,null)相同)沒法返回 TRUE,也就是說 NOT IN (1, NULL) 這種形式永遠不會返回數據。

  1. 因爲上面的第 1 點,NOT IN (SELECT ...) 不能很好地優化。特別是,規劃器(planner 負責生成查詢計劃)沒法將其轉換爲 anti-join,所以它變爲哈希子規劃或普通子規劃。哈希子規劃很快,但規劃器只容許該計劃用於小結果集;普通的子計劃很是慢(事實上是 O(N²)時間複雜度)。這意味着在小規模測試中性能看起來不錯,但一旦數據量大,性能就會減慢 5 個或更多個數量級; 咱們不但願這種狀況發生。

3.1.2 何時可使用

NOT IN (list, of, values, ...)只是在列表中有 null 值(參數或其餘方式)時,纔會有問題。因此在排除沒有 null 值時,是能夠用的。

3.2 不要使用大寫字母命名

不要使用 NamesLikeThis,而是使用 names_like_this 的命名方式。

3.2.1 爲何

PostgreSQL 將表,列,函數等名稱轉換爲小寫,除非它們使用「雙引號」擴起來纔不會被轉換。

因此 create table Foo() 將會建立一個表名爲 foo 的表,執行 create table "Bar"() 纔會建立表名爲 Bar 的表。

這些查詢語句將會正常執行:select * from Foo, select * from foo, select * from "Bar"

這些查詢語句會報錯 「no such table」:select * from "Foo", select * from Bar, select * from bar

這意味着若是在表名或列名中使用大寫字母,則查詢時必須使用雙引號。這很煩人,可是當你使用其餘工具訪問數據庫時,其中一些名稱使用雙引號,而另外一些則不使用,這會讓人感到困惑。

堅持使用 a-z,0-9 和下劃線來表示名稱,就沒必要再擔憂了。

3.2.2 何時可使用

若是在輸出中顯示好看的名稱很重要,那麼你可能想使用大寫字母。可是你也可使用列別名,也能夠在查詢中輸出好看的名稱:select character_name as "Character Name" from foo

3.3 不要使用 BETWEEN(尤爲是時間戳)

3.3.1 爲何

BETWEEN 使用閉區間比較:範圍兩端的值會包含在結果中。

這是一個查詢的問題

SELECT * FROM blah WHERE timestampcol BETWEEN '2018-06-01' AND '2018-06-08'
複製代碼

這將包括時間戳剛好爲 2018-06-08 00:00:00.000000 的結果。查詢能夠工做,可是因爲是閉區間,可能在下一次查詢會包含這個時刻值(例如 '2018-06-08' AND '2018-06-09' 就會包含那一刻的值)。

用下面的語句替換

SELECT * FROM blah WHERE timestampcol >= '2018-06-01' AND timestampcol < '2018-06-08'
複製代碼

3.3.2 何時可使用

BETWEEN 對於整數或日期等離散類型的數據是安全的,須要記住 BETWEEN 是閉區間。但使用 BETWEEN 多是一個壞習慣。

4. 日期/時間 的存儲

(譯者注:日期/時間 中文文檔

4.1 不要使用 timestamp(without time zone)

不要使用 timestamp 類型來存儲時間戳,而是使用 timestamptz(也稱爲帶時區的時間戳)來存儲。

4.1.1 爲何

timestamptz 記錄 UTC 的微秒數,你能夠插入任什麼時候區的值。默認狀況下,它將顯示當前時區的時間,但你能夠轉換成其餘時區。

由於它存儲了時間戳信息,能夠用算法來轉換成不一樣時區的時間戳。

timestamp(也稱爲沒有時區的時間戳)不會執行任何操做,它只會存儲你提供的日期和時間。你能夠將其視爲日曆和時鐘的圖片,而不是時間點,沒有時區信息。所以,沒有時區的時間戳無法轉換時區。

所以,若是你要存儲的是時間點,而不是時鐘圖片,請使用 timestamptz

更多有關 timestamptz 的信息

4.1.2 何時可使用

若是你以抽象方式處理時間戳,或者只是爲了 app 的保存和檢索,而不對它們進行時間計算,那麼 timestamp 多是合適的。

4.2 不要使用 timestamp(without time zone)來存儲 UTC 時間

將 UTC 值存儲在沒有時區的 timestamp ,一般是從其餘缺少可用時區支持的數據庫繼承數據的作法。

改成使用 timestamp with time zonetimestamptz

4.2.1 爲何

由於數據庫沒法知道是不是 UTC 時區。

這讓時間計算變得複雜。例如,「給定時區 u.timezone 的最後一個午夜」的計算語句爲:

date_trunc('day', now() AT TIME ZONE u.timezone) AT TIME ZONE u.timezone AT TIME ZONE 'UTC'
複製代碼

而且「u.timezone 中 x.datecol 日期以前的午夜」的計算語句爲:

date_trunc('day', x.datecol AT TIME ZONE 'UTC' AT TIME ZONE u.timezone)
  AT TIME ZONE u.timezone AT TIME ZONE 'UTC'
複製代碼

4.2.2 何時可使用

若是與非時區支持數據庫的兼容性賽過全部其餘考慮因素。

4.3 不要使用 timetz

不要使用 timetz 類型,可使用 timestamptz 代替。

4.3.1 爲何

甚至手冊也告訴你它只是爲了遵照 SQL 標準而實現的。

帶有時區的時間類型由 SQL 標準定義,但定義顯示的屬性讓人懷疑它的可用性。在大多數狀況下,日期,時間,沒有時區的時間戳和帶時區的時間戳的組合應該能夠提供任何應用程序所需的日期/時間功能。

4.3.2 何時可使用

從不使用。

4.4 不要使用 CURRENT_TIME

不要使用 CURRENT_TIME 函數。使用如下是合適的:

  • CURRENT_TIMESTAMP 或者 now() 若是你想要 timestamp with time zone
  • LOCALTIMESTAMP 若是你想要 timestamp without time zone
  • CURRENT_DATE 若是你想要 date
  • LOCALTIME 若是你想要 time

4.4.1 爲何

它返回 timetz 類型的值,關於 timetz 請看上一條解釋。

4.4.2 何時可使用

從不使用。

4.5 不要使用 timestamp(0) 或者 timestamptz(0)

不要使用 timestamp() 或者 timestamptz() 進行時間戳的轉換(尤爲是 0)。

使用 date_trunc('second', blah) 替換。

4.5.1 爲何

由於它會將小數部分四捨五入截斷它。這可能會致使意外問題;考慮到當你將 now() 存儲到這樣一個列中時,你可能會在未來存儲一個小數秒的值。

4.5.2 何時可使用

從不使用。

5. 文本存儲

5.1 不要使用 char(n)

不要使用 char(n),使用 text 可能更適合。

5.1.1 爲何

使用 char(n) 類型的字段,若是長度不夠會使用空格填充到聲明的長度。這可能不是你想要的。

名字 描述
character varying(n), varchar(n) 變長,有長度限制
character(n), char(n) 定長,不足補空白
text 變長,無長度限制

手冊上說:

char 類型的數值物理上都用空白填充到指定的長度 n, 而且以這種方式存儲和顯示。不過,在比較兩個 char 類型的值時,尾隨的空白是可有可無的,不須要理會。 在空白比較重要的排序規則中,這個行爲會致使意想不到的結果, 好比 SELECT 'a '::CHAR(2) collate "C" < 'a\n'::CHAR(2)返回真。 在將 char 值轉換成其它字符串類型的時候, 它後面的空白會被刪除。請注意, 在 varchar 和 text 數值裏, 結尾的空白是有語意的。 而且當使用模式匹配時,如 LIKE,使用正則表達式。

空格填充確實浪費空間,也不會讓操做變得更快;事實上,不少狀況下咱們還要去掉空格。

提示: 這三種類型之間沒有性能差異,除了當使用填充空白類型時的增長存儲空間, 和當存儲長度約束的列時一些檢查存入時長度的額外的 CPU 週期。 雖然在某些其它的數據庫系統裏,char(n) 有必定的性能優點,但在 PostgreSQL 裏沒有。 事實上,char(n) 一般是這三個中最慢的, 由於額外存儲成本。在大多數狀況下,應該使用 text 或 varchar。

5.1.2 何時可使用

當你移植使用了固定寬度字段的很是很是舊的軟件時。或者當你閱讀上面手冊中的片斷並認爲「是的,這是徹底合理的,而且符合個人要求」 時可使用。

5.2 即便對於固定長度的標識符,也不要使用 char(n)

有時候人們用「個人值恰好是 N 個字符」(例如國家代碼,哈希值或來自其餘系統的標識符)來回應「爲何不要使用 char(n)」。其實,即便在這些場景下使用 char(n) 也不是一個好主意。

5.2.1 爲何

對於過短的值,char(n) 會用空格填充它們。所以,帶有肯定長度的 char(n) 比較 text 而言沒有任何實際好處。

5.2.2 何時可使用

從不使用。

5.3 默認狀況下不要使用 varchar(n)

默認狀況下不要使用 varchar(n) 類型。考慮使用 varchar(沒有長度限制)或 text 替代。

5.3.1 爲何

varchar(n) 是一個帶長度的文本字段,若是你嘗試將長度超過 n 個字符(而不是字節)的字符串插入其中,則會引起錯誤。

varchar(沒有 (n) )或 text 是類似的,沒有長度限制。若是在三種字段類型中插入相同的字符串,它們將佔用徹底相同的空間,而且性能基本沒有差別。

若是你想要一個長度限制的文本字段,那麼 varchar(n) 很不錯,可是若是你定義姓氏字段爲 varchar(20),那麼當 Hubert Blaine Wolfeschlegelsteinhausenbergerdorff 註冊到你的服務時,將會報錯。

有些數據庫沒有長 text 的類型,或者它們沒有像 varchar(n) 那樣被良好支持。那這些數據庫的用戶一般會使用相似 varchar(255) 的表示方法,但他們真正想要的是 text

若是你須要約束字段中的值,好比說約束最大長度 - 或者是最小長度,或者是一組有限制的字符串 - 檢查約束能夠作到這些。

5.3.2 何時可使用

若是你想要一個文本字段,並且插入太長的字符串須要拋出錯誤,而且不想使用顯式檢查約束,那麼 varchar(n) 是一個很是好的類型。只是使用時須要多考慮。

6. 其餘數據類型

6.1 不要使用 money

money 數據類型實際上不太適合存儲貨幣值。數字或整數可能更好。

6.1.1 爲何

大量理由

money 類型存儲帶有固定小數精度的貨幣金額。 lc_monetary 用來設置格式化數字。但它的四捨五入的行爲可能不是你想要的。

名字 存儲容量 描述 範圍
money 8 字節 貨幣金額 -92233720368547758.08 到 +92233720368547758.07

若是你更改了 lc_monetary 設置,則全部 money 列都將包含錯誤的值。這意味着若是你插入'$ 10.00'而 lc_monetary 設置爲 en_US.UTF-8,你檢索的值多是'10,00 Lei'或'¥1,000'。

6.1.2 何時可使用

若是你只是用單一貨幣工做,不處理小數美分而且只作加法和減法運算,那麼 money 類型多是正確的。

6.2 不要使用 serial

對於新的應用程序,應使用 identity

6.2.1 爲何

serial 類型有一些奇怪的行爲,使結構,依賴和權限管理更繁瑣。

6.2.2 何時可使用

  • 若是你須要支持 10.0 版以前的 PostgreSQL。
  • 在表繼承的某些組合中
  • 更通常地說,若是你以某種方式使用來自多個表的相同序列,儘管在這些狀況下,顯式聲明可能優於 serial 類型。
相關文章
相關標籤/搜索