Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
「資料表」(table)在關連式資料庫中的角色很接近在紙上畫一個「資料表」:包含了列與欄。欄的數量與次序是固定的,而每個欄位都有一個名稱。列的數量是變動的—它表示在當下有多少資料被存在資料庫中。SQL 並不保證列在資料表中的次序。當讀取資料表的時候,除非明確要求要排序,不然列與列之間是不存在固定的次序。這些將在第 7 章中進一步說明。進一步來說,SQL 並沒有給每一列一個唯一性的識別,所以在資料表中是有可能存在有完全相同內容的列。這是 SQL 架構下的數學模型結果,通常不是理想的結果。在這章之後,我們會說明如何處理這個問題。
每一個欄位都有一個資料型別。資料型別限制了儲存於該欄位的資料內容,同時也設定了資料儲存的型態,使得該資料可以直接用於計算。舉個例子,一個被宣告為數字型別的欄位,就不能放進任何文字字串,而儲存於此欄位中的資料,可用於數學計算。相反地,一個被宣告為字元字串的欄位,可以儲存任何型能的資料,但就無法用於數學計算了,雖然也有其他操作可以進行字串串接。
PostgreSQL 擁有許多內建的資料型別,可以適應許多應用系統。使用者也可以自訂他們所需的資料型別。大多數內建的資料型別都有顯而易見的名稱與用法,所以我們打算在第 8 章再做詳細的說明。有一些常用的資料型別,像是 interger 用於整數,numeric 用於浮點數,text 用於字串,date 則是日期,time 是時間,而 timestamp 則同時包含日期和時間。
要建立一個資料表,你可以使用 CREATE TABLE 指令。這個指令你至少要指定一個名稱給新的資料表,還有每一個欄位的名稱與資料型別。例如:
這個建立一個叫作 my_first_table 的資料表,它包含了兩個欄位。第一個欄位叫作 first_column,其資料型別為 text;第二個欄位名稱為 second_column,資料型別為 integer。表格與欄位名稱的規則依 4.1.1 節中所介紹的識別字語法,但也有一些例外。注意欄位列表是用逗號分隔,並且包含於括號之中。
當然,前面的例子明顯只是做做樣子而已。一般來說,你會將你的資料表欄位以實際用途來命名,所以我們來看一下更實際的例子:
(numeric 資料型別可以儲存浮點數,用於典型的貨幣計量。)
小技巧當你建立了許多相關的資料表時,建立最好選擇一個用於命名表格及欄位的規則。舉例來說,有一個規則是使用單數或複數名詞來取名表格,兩者都有些人喜歡使用。
一個資料表中有多少欄位是有限制的,依欄位型別而定,上限通常是 250 個到 1600 個之間。不過,宣告到這麼多的欄位是非常罕見,而且應該是有問題的設定。
如果你不再需要某個資料表,你可以移除它。請使用 DROP TABLE 指令,如下所示:
企圖要移除一個不存在的資料表,會產生錯誤。不過,在 SQL 腳本中,在建立資料表前嘗試移除是很常見的,通常會忽略錯誤訊息,所以不論資料表是否已經存在,腳本都能如預期執行。(如果你需要的話,你也可以使用 DROP TABLE IF EXISTS 來避免產生錯誤訊息,但這並不是標準 SQL 語法。)
如果你需要變更資料表的結構的話,請參閱本章的 5.5 節。
到目前為止,你已經可以利用工具建立完整功能的資料表。本章接下來的部份會針對附加的功能介紹,像是確保資料完整性、安全性、或方便性。如果你現在急著要將資料存入你的資料表的話,你可以暫時跳過本章,到第 6 章繼續操作。
這一章涵蓋了如何建立資料庫結構。在關連式資料庫中,原始資料儲存在表格之中,所以在這一章裡,主要說明表格如何建立及調整,以及有什麼樣的功能可以操控所存放的資料。再來我們會討論表格如何以結構來管理,以及權限的設定。最後,我們會簡短地看一下其他的功能如何影響資料儲存,像是繼承、表格分割、view、函數、還有觸發函數。
欄位可以指定一個預設值。當新的列被插入,某些欄位卻沒有指定其值時,這些欄位將會被填入相對應的預設值。資料處理的過程中,當有欄位的值不確定時,也會被設定為其預設值。(關於資料處理的詳細內容,請參閱第 6 章。)
如果預設值並沒有明確被指定時,預設值就會是 null。一般來說空值是可接受的情況,因為空值可以表示「未知的資料」的意義。
在表格定義時,預設值接在資料型別後宣告,如下所示:
預設值也可以是運算表示式,會在資料插入的同時進行運算(不是在表格建立時)。常見的例子是 timestamp 欄位,會設定一個 CURRENT_TIMESTAMP 的預設值,使其在資料插入時設定為當下的時間。另一個例子是產生「序列數」,這在 PostgreSQL 中,通常以下列語法來表現:
這裡的 nextval() 函數會從序列物件取得下一個數字(參閱 9.16 節)。這個例子也常簡化為:
有關 SERIAL 的簡寫方式,將在 8.1.4 節中說明。
當你建立了一個表格,而你發現出了點錯,或者應用需求有一些改變,那麼你可以移除它再重新建立。但這可能不會一個好的選擇,當表格中已經儲存了許多資料時,或者表格正在被其他的資料庫物件所參考中(例如外部鍵參考)。所以 PostgreSQL 提供了一系列的指令來修改現存的表格。注意到這和更新表格內資料的概念是不同的:在這裡,我們主要針對的是調整表格的定義或結構。
你可以:
加入欄位
移除欄位
加入限制條件
移除限制條件
改變預設值
改變欄位資料型別
變更欄位名稱
變更表格名稱
所有這些動作都透過 ALTER TABLE 指令來進行,你可以參考該頁面取得詳細資訊。
要加入一個新欄位,請使用下面的指令:
這個新的欄位預設會以預設值填入(如果你沒有使用 DEFAULT 子句來宣告的話,那會使用 NULL)。
你也可以在新增同時建立限制條件:
事實上,所有在 CREATE TABLE 的選項都可以在這裡使用。要記得的是,預設值必須要符合限制條件的設定,否則這個欄位會無法加入。順帶一提的是,你也可以隨後再加入限制條件(隨後說明),在你更新好新的欄位資料內容後。
加入一個欄位,並且設定預設值,會更新表格的裡的每一個資料列(為了存入新的欄位內容)。然而,無預設值的話,PostgreSQL 就不會在實體上真正進行更新的動行。所以如果你的新欄位大多數的內容都不是預設值的話,那麼就建議不要在加入欄位時設定預設值。之後再使用 UPDATE 來分別更新其內容,然後再以隨後的介紹來更新預設值的設定。
要移除一個欄位,請使用下列指令:
不論資料在該欄位是否消滅,表格的限制條件都會同步再次啓動檢查。所以,如果欄位是被外部鍵所參考的話,PostgreSQL 不會就這樣移除它。你可以宣告同步刪去與此欄位相關的物件,加上 CASCADE:
請參閱 5.13 節,瞭解詳細的處理機制。
要加入限制條件,請使用表格限制條件的語法,例如:
要加入 NOT NULL 限制條件的話,就不能寫成表格的限制條件,請使用這樣的語法:
加入的限制條件會立即開始檢查,所以當下的資料內容必須要能符合條件才能加入成功。
要移除限制條件,你需要先知道它的名稱。如果你在宣告時有命名的話,那就使用那個名稱,否則你得找出系統自動命名的名稱。其所使用的指令為「\d tablename」,會列出表格相關的資訊。或使用其他的資料庫工具應該也可以找到它。找到之後請使用下列指令來移除限制條件:
(如果你的限制條件名稱像是「$2」這樣的,不要忘記使用雙引號括住,使其可以正確地被識別為是名稱。)
在移除欄位時,你需要加入 CASCADE,如果你需要同步移除相關的限制條件的話。像是外部鍵就會依賴另一個唯一性限制或主鍵的限制條件。
下面這可以用在移除 NOT NULL 限制的欄位:
(記得 NOT NULL 是沒有名稱的。)
要設定新的欄位預設值,請使用下面指令:
注意這並不會影響到已經存在的資料,只有隨後新增的資料才會使用。
要移除任何預設值,請使用:
這個指令會把預設值設為空值。因為預設值本來就設為空值,所以即使刪去一個未設定預設值欄位的預設值,也不會是一種錯誤。
要變更欄位成為另一個資料型別,請使用下列指令:
這只有在欄位內容可以被自動轉換型別時才會成功。如果存在比較複雜的轉換時,你需要加上 USING 子句來指示如何轉換資料內容。
PostgreSQL 會企圖轉換欄位預設值到任何新的型別,而所有的限制條件也會啓動檢查機制。但這些轉換可能會失敗,也可以產生意外的結果。比較好的作法是,先移除限制條件,再變更資料型別,最後再重新加入適當調整後的限制條件。
要變更某個欄位的名稱:
要變更表格的名稱:
Generated column (自動欄位)是特殊的欄位,它的內容由其他欄位的內容計算得出。相對於資料表來說,就是欄位形態的 View。Generated column 有兩種:stored 和 virtual。 Stored 的自動欄位在寫入(插入或更新)時進行計算,會像正常欄位一樣佔用儲存空間。Virtual 的自動欄位則不佔用任何儲存空間,而是在讀取時會對其進行計算。因此,虛擬的自動欄位類似於檢視表(view),而儲存的自動欄位則類似於具體化檢視表(materialized view)(但會自動更新)。 PostgreSQL 目前僅實作了儲存的自動欄位。
To create a generated column, use the GENERATED ALWAYS AS
clause in CREATE TABLE
, for example:
The keyword STORED
must be specified to choose the stored kind of generated column. See CREATE TABLE for more details.
A generated column cannot be written to directly. In INSERT
or UPDATE
commands, a value cannot be specified for a generated column, but the keyword DEFAULT
may be specified.
Consider the differences between a column with a default and a generated column. The column default is evaluated once when the row is first inserted if no other value was provided; a generated column is updated whenever the row changes and cannot be overridden. A column default may not refer to other columns of the table; a generation expression would normally do so. A column default can use volatile functions, for example random()
or functions referring to the current time; this is not allowed for generated columns.
Several restrictions apply to the definition of generated columns and tables involving generated columns:
自動欄位的表示式只能使用 immutable 函數,不能使用子查詢或以任何方式引用同筆資料以外的任何內容。
自動欄位的表示式不能引用另一個自動欄位。
自動欄位的表示式不能引用系統欄位(tableoid 除外)。
自動欄位不能有欄位預設值或識別定義。
自動欄位不能是分割區主鍵的一部分。
外部資料表可以具有自動欄位。有關詳細資訊,請參閱 CREATE FOREIGN TABLE。
其他注意事項適用於自動欄位的使用。
自動欄位與其一般欄位分開維護存取權限。因此,可以對其進行安排,以便設定可以從自動欄位中讀取,但不能從一般欄位中讀取的特定角色。
從概念上講,在執行事件觸發器之前,會先更新自動欄位。因此,在 BEFORE 觸發器中對基本欄位所做的更新將先反映在自動欄位中。但是相反地,不允許在觸發器之前讀取自動欄位。
每一個表格都有幾個系統欄位,而它們是由資料庫系統預先定義好的,所以使用者在定義欄位名稱時,不能使用這些名字。(這些限制並不是因為它們是保留關鍵字,所以就算用引號括起來也不能使用。)但在一般使用時,你也不需要特別考慮這些欄位,只要瞭解會有這些欄位存在就好。
oid
每一個資料列會有一個 Object ID,不過這個欄位只有在建立表格時,加上 WITH OIDS 語法才能使用。或者也可以藉由參數 default_with_oids 來切換使用。這個欄位的型別是 oid(和欄位名相同)。參閱 8.18 節瞭解詳細資訊。
tableoid
每個表格也有一個 ID 也會記錄在每一個資料列中。這個欄位特別方便在取得表格的繼承結構(參閱 5.9 節),如果沒有這個欄位的話,要去找出資料列的來源就會很麻煩。tableoid 可以參考 pg_class 表格中的 oid 欄位,進一步取得表格的名稱。
xmin
這指的是資料列在插入資料的版本資訊。(每一個資料列的版本,都是一個獨立的資料狀態;每一次資料的更新,都會在邏輯層產生一個新的資料列版本。)
cmin
指令識別碼,會存在於新增資料的交易中。(從 0 開始)
xmax
刪除資料的交易版本資訊,如果是 0 的話,代表讓資料列不是刪除中的資料列版本。這通常是用來指出某個刪除的交易還未被完成,或某個刪除正在被回復。
cmax
指令識別碼,有數字的話表示一個刪除的交易指令,或是 0。
ctid
表示每一個資料列存在於該表格的實體位址。注意到的是,雖然 ctid 可以用來快速找到特定的資料列版本,但 ctid 是會改變的,如果有執行過 VACUUM FULL 的話。所以 ctid 如果要用於固定的資料定位的話,是不應該被考慮的選項。OID 或額外自訂序列數字,更適合用於分別邏輯上的資料列。
OID 是一個 32 位元的數字,以 cluster 為單位配發。在一個大型或長期使用的資料庫中,是有可能出現重覆的情況。所以,假設 OID 是唯一的識別是不正確的觀念,除非你還有搭配其他方法來確保唯一性。如果你需要識別表格中的資料列的話,使用序列數產生器是比較建議的作法。OID 也可以這樣用來得到一些額外的預防性功能:
唯一性的限制應該設定在 OID 欄位上,來確保每一個 OID 可以識別每一個資料列。當有唯一性限制存在的時候,對於已經存在的資料列就不會有重覆的 OID。(當然,這方法只能用於資料筆數在 40 億筆以下的表格。不過實務上的表格多數都少於這個數目,而且太多資料的話,效果也會變得很差。)
OID 在多個表格間就不能假設為是唯一,你應該搭配 tableoid 來識別資料庫層級的唯一性。
當然,在建立表格時必須要加入 WITH OIDS 語法。在 PostgreSQL 8.1 之前,WITHOUT OIDS 是預設值。
交易識別碼也是 32 位元的數字。在一個長期運行的資料庫中,交易識別碼也可能會重覆。只要有適當的管理機制的話,這並不會是什麼嚴重的問題,詳情請參閱第 24 章。然而,長期來說(超過 10 億個交易),假定交易識別碼的唯一性是不明智的作法。
指令識別碼也是 32 位元的數字,其絕對上限是約 40 億個指令在一個交易當中,實務上這個限制並不會是問題。注意到這個限制是 SQL 指令數量的限制,而不是處理資料的限制。只有真正有改變資料庫內容的指令才會有指令識別碼。
除了透過 GRANT 指令設定 SQL 標準的權限系統之外,資料表也可以有資料列層級的安全原則,控制每個使用者在資料查詢或變更時,所能接觸到的資料列。這個功能就稱作資料列安全原則(Row-Level Security)。預設上,資料表並不會有這些安全原則,所以只要使用者能存取該資料表,就表示他能存取所有資料列的內容。
當資料列安全原則在資料表裡被啓動後(使用 ALTER TABLE ... ENABLE ROW LEVEL SECURITY),所有資料表的操作,就必須符合資料列安全原則的設定。(當然,資料表的擁有者並不受限於資料列安全原則。)如果資料表中未設定任何原則,那麼預設就是拒絕存取,意思就是任何資料列都不能被看見或修改。但如果是整個資料表的操作行為,像是 TRUNCATE 或 REFERENCES,就不會受到影響。
資料列安全原則可以被設定在命令,使用者角色,或兩者兼具。安全原則也可以使用 ALL 的修飾字,或具體指出是 SELECT、INSERT、UPDATE、或 DELETE。多重角色可以共用一個安全原則,一般使用者或承繼的角色都會被同步影響到。
要設定一個安全原則來指出哪些資料列可見或可修改,是以一個回傳值為布林值的表示式來決定的。這個表示式會計算每一個資料列的結果,在使用者進行任何操作之前。(這個規則唯一的例外是 leakproof 函數,用來確保沒有洩漏資訊;查詢最佳化元件會選押在確任資料列安全原則前就先執行它。)在這個表示式沒有回傳 true 的資料列,都是不能被存取的。獨立的表示式可用於提供資料列專屬的控制,判斷其是否可供讀取或修改。安全原則表示式是查詢的一部份,和使用者執行查詢時一起執行,不過,安全原則表示式是可以存取到該使用者看不到的資料。
超級使用者因為擁有 BYPASSRLS 的屬性,所以永遠可以通過安全原則檢查而存取資料表。資料表的擁有者一般來說也是可以通過檢查,但可以使用 ALTER TABLE ... FORCE ROW LEVEL SECURITY 來強制適用安全原則。
開啓或關閉資料列安全原則的權限,只屬於資料表擁有者。
使用 CREATE POLICY 指令來建立安全原則;使用 ALTER POLICY 指令來修改;使用 DROP POLICY 指令來移除原則。要開啓或關閉安全原則的功能,請使用 ALTER TABLE 指令。
每一個安全原則都有一個名稱,而一個資料表可以定義多個安全原則。安全原則是資料表專屬的,而每一個安全原則在所屬資料表內必須有一個唯一的名稱。不同的資料表下的安全原則可以取相同的名稱。
當多個安全原則使用者某個查詢上時,可能會使用 OR 串接(開放安全原則 permissive policies,這是預設的狀態),也可能以 AND 串接(嚴格安全原則 restrictive policies)。這類似角色授權的情況。有關於開放安全原則與嚴格安全原則的細節,稍後再進行說明。
先進行一個簡單的範例,我們建立一個安全原則在資料表 account 上,它只允許 managers 的使用者可以存取資料列,並且只能存取他自己帳號的資料列:
如果沒有指定角色或使用者時,就會以 PUBLIC 替代,也就是所有使用者都適用。要允許所有使用者存取他們自己的資料列的話,就可以簡化指令為:
想要定義一個安全原則是有別於可見性權限的話,請使用 WITH CHECK 字句。例如希望讓所有人都可以看到所有資料列,但只能修改自己的資料的話:
資料列安全原則也可以透過 ALTER TABLE 指令關閉。不過關閉資料列安全原則,並不會移除任何已定義的原則,只是單純被忽略而已。然後資料表的所有資料列,就只依標準 SQL 的權限系統,決定查詢及修改的權力。
下面是一個較複雜的例子,展示這個功能如何被應用於產品等級的環境裡。資料表 passwd 模擬 Unix 的密碼檔:
對於任何的安全設定,很重要的是,你必須實際測試來確認系統的行為和你預期的相同。使用上面的例子,下面的測試表現出權限設如預期地運作。
所有的安全原則,目前來說都是開放安全原則,意思是當有多個安全原則被引用時,它們會以 OR 運算串連其結果。開放安全原則用於只允許在計畫內的環境使用的話,它會比和嚴格安全原則(把安全原則用 AND 串連起來判斷)一起使用來得簡單。基於上面的列子,我們建立一個嚴格安全原則,它限制管理者只能透過 unix socket 連線才能存取 passwd 資料表:
我們接下來就可以看到,管理者透過一般網路連線,是看不到任何資料的,因為嚴格安全原則:
資料一致性的檢查,像是唯一性、主鍵、以及外部鍵參考,都會略過資料列安全原則,以維持資料的一致性。在發展資料庫結構時應該要特別小心,以資料列安全原則避免透過一致性檢查而產生隱藏通道洩露資訊。
在某些情況,很重要的是要確認安全原則是否被觸發。舉例來說,當進行資料備份流程時,如果安全原則造成某些資料被備份程式忽略了,那可能就會很糟糕。在這種情況下,你可以把 row_security 這個參數設為 off。這並不是避開安全原則,而是在觸發安全原則時,會出現錯誤訊息,使得我們可以發現進而修正原則。
在上面的例子裡,安全原則表示式只引用了目前資料列中的資料。這是最簡單也是最常見的形式,可以的話,最好以這樣的方式來設計安全原則。如果需要參考其他資料列或資料表來做決定的話,那麼可以使用子查詢或函數的方式達成,也就是包含一個 SELECT 的查詢語句在表示式中。要注意到的是,這種方法可能會造成資料庫內交易競爭(race condition)的狀態,不注意的話也可能產生資訊的洩漏。像這樣的例子,試試下面的資料表設計:
現在假設 alice 想要變更"slightly secret"的資訊,但決定不讓 mallory 看到新的內容,所以她這麼做:
看起來很安全,因為沒有窗口讓 mallory 可以看到"secret from mallory",然而,這裡就存在了交易競爭的情況。如果 mallory 也在同時做了:
因為她的交易是屬於 READ COMMITTED 模式,所以她有可能會看到"secret from mallory"。這會剛好發生在,她在 alice 的交易完成前一刻。mallory 的指令會暫時擋下 alice 的提交完成,而因為 FOR UPDATE,她會取得更新後的資訊。所以她並沒有從隱含的使用者執行 SELECT 取得資訊,因為子查詢沒有 FOR UPDATE,使得其他使用者可以從快照裡取得資訊。因為安全原則是以舊的 mallory 權限允許她看見該筆資料。
這個問題有好幾個面向的解決方式。一個簡單的方式就是使用 SELECT ... FOR SHARE 在安全原則的子查詢裡。但這樣就必須要讓使用者擁有 UPDATE 的權限,可能不太合適。(但也可以用另一個安全原則來做更多的限制,又或是把子查詢封裝進另一個安全的函數裡)同時,大量的引用查詢也可能造成效能的問題,特別是更新資料的時候。另一個解決辦法,如果參考的資料表並不是很常更新的話,那麼可以在資料表更新時強制鎖定該資料表,確保沒有其他交易能在同時進行查詢,也就不會洩漏任何資訊。或是等待其他所有交易都完成後,才提交更新變更新的安全方案。
更多詳細,請參閱 CREATE POLICY 和 ALTER TABLE。
PostgreSQL 實作了 SQL/MED 的部份標準,讓你可以存取不在 PostgreSQL 管理下的資料,重點是,你仍然只需要使用 SQL 語法。這樣的資料我們稱作為外部資料。(注意這部份的使用不要和外部鍵搞混了,外部鍵是資料庫內部的一種條件限制。)
外部資料的存取是透過「Foreign data wrapper」(外部資料封裝技術)。外部資料封裝技術是一組函式庫,用於和外部的資料源溝通,它封裝了資料連線和存取資料的細節。有一些外部資料封裝的套件收錄在 contrib 模組之中,參閱附件 F。其他種類的外部封裝套件則由第三方產品提供。如果沒有適合你的資料源的套件的話,你也可以自己寫一個,參閱第 56 章。
要存取外部資料,你需要建立外部服務物件,用它來連結特定的外部資料源,也可以對套件進行一些設定。然後你還需要建立幾個外部資料表,用於定義外部資料的資料結構。外部資料表的使用就如一般的表格一樣,只不過它沒有實際儲存任何資料罷了。當外部資料表被查詢時,PostgreSQL 會透過外部資料封裝套件,從外部資料源取得資料,或者傳送資料到外部,進行更新資料。
存取外部資料可能需要對外部資料源進行認證。這可以利用使用者映對(user mapping)的方法,讓每個 PostgreSQL 使用者在使用部資料表時,可以傳送自己的認證資訊。
進一步的資訊,請參閱 CREATE FOREIGN DATA WRAPPER、CREATE SERVER、CREATE USER MAPPING、CREATE FOREIGN TABLE、IMPORT FOREIGN SCHEMA 等內容。
表格是關連式資料庫結構裡的主要物件,因為它負責存放資料,但並不是資料庫中唯一的物件。還有許多其他種的物件存在,讓使用上更方便或管理更有效率。這些其他的物件並不在本章中討論,但我們先在這裡列出讓你知道:
視觀
函數與運算子
資料型別和領域
觸發事件和規則覆寫
關於這些物件的詳細說明安排在。
PostgreSQL 實作了資料表的繼承方式,對於資料庫設計人員來說,將會是很好用的功能。(SQL:1999 之後定義了型別繼承的功能,但和這裡所介紹的方向有許多不同。)
我們直接以一個例子作為開始:假設我們嘗試建立「城市(city)」的資料模型。每一個州(state)都會有許多城市(city),但只會有一個首都(capital)。我們想要很快地可以找到某個州的首都。這件事我們需要建立兩個資料表,一個存放首都,而另一個記載非首都的城市。只是,當我們想要取得的是所有城市,不論是否為首都,似乎會有些麻煩?這時候繼承功能就可以幫助我們解決這個問題。我們可以定義一個資料表 capitals,它是由資料表 cities 繼承而來:
在這個例子中,資料表 capitals 會繼承父資料表 cities 的所有欄位。只是 capitals 會多一個欄位 state,表示它是哪個州的首都。
在 PostgreSQL 裡,一個資料表可以繼承多個資料表,而一個查詢可以引用該資料表裡的所有資料列或在其所屬的資料表的資料列,後者的行為是預設的。舉個例子,下面的查詢可以列出所有海沷在 500 英呎以上的城市名稱,州的首都也包含在內:
使用 2.1 節中的範例資料,將會回傳:
換句話說,下面的查詢就會查出非首都且海沷超過 500 英呎以上的城市:
這裡「ONLY」關鍵字指的是查詢只需要包含資料表 cities 就好,而不是任何繼承 cities 的資料表都包含在內。我們先前介紹過的指令:SELECT、UPDATE、和 DELETE,都可以使用 ONLY 關鍵字。
你也可以在資料表名稱後面加上「*」,明確指出繼承的資料表都需要包含在內:
注意這個「*」並不是必要的,因為這個行為本來就是預設的。這個語法用於相容舊的版本,有些版本的預設行為可能不太一樣。
在某些例子裡,也許你會希望知道哪些資料列來自於哪個資料表。有一個系統欄位稱作 tableoid,每一個資料表都會有,而它可告訴你資料列的來源:
這將會回傳:
(如果你嘗試重覆執行這個例子,你可能會得到不同的 OID 值。)藉由和資料表 pg_class 交叉查詢,你可以看到實際的資料表名稱:
將會回傳:
另一個可以得到相同結果的方式是,使用 regclass 別名型別,這個型別會將 OID 轉換成名稱輸出:
在使用 INSERT 或 COPY 指令時,繼承並不會自動轉存資料。在我們的例子中,下面的 INSERT 指令將會失敗:
我們可能會希望資料以某種方式轉送到資料表 capitals 中,但這不會發生:INSERT 指令永遠只會將資料插入到指定的資料表中。在某些情況下,如果設定了存取規則(第 40 章)的話,那有可能做到類似的效果。然而,在這個例子下是沒有辦法執行的,因為資料表 cities 中並沒有一個欄位稱作 state,所以這個指令將會被拒絕執行,如果沒有其他規則被設定的話。
所有限制條件的檢查,還有非空值的限制,都會自動從父資料表繼承下來,除非特別使用 NO INHERIT 子句來設定拋棄繼承。而其他型態的限制條件(唯一性、主鍵、外部鍵)都不會自動繼承。
一個資料表也可以繼承超過一個資料表,也就是說,它會擁有這些資料表全部的欄位,然後再加上自己所宣告的欄位。如果父資料表有相同名稱的欄位的話,或是父資料表和子資料表有同名的欄位,那麼這些欄位會被合併,它們會被合併為一個欄位。合併的時候,他們的資料型別必須要一致,否則會產生錯誤。被繼承的限制條件和無空值的限制也會用類似的方式合併。舉個例子來說,如果要合併的欄位中,任何一個欄位有 not-null 的設定的話,那麼合併後的欄位就會被設定為 not-null。如果有同名的限制條件要被合併,但他們的內容不相同的話,那麼合併也會失敗。
資料表的繼承一般來說是在子資料表建立時進行的,也就是在 CREATE TABLE 中使用 INHERITES 子句。然而,資料表也可以在 ALTER TABLE 中使用 INHERIT 子句來新增新的父資料表。要進行這個動作,新的子資料表必須已經包含所有父資料表的欄位—相同的欄位名稱及資料型別。還有在 ALTER TABLE 時加入 INHERIT 子句來移除某個欄位的繼承。動態地新增或移除繼承欄位通常是在應用分割表格(table partitioning)時特別好用(請參閱 5.10 節)。
還有一個方便的方式去建立一個相容於之後繼承的資料表,就是在 CREATE TABLE 中使用 LIKE 子句。這個方式會建立一個新的資料表,其欄位和另一個資料表完全相同。如果有任何 CHECK 子句的限制條件的話,就應該在 LIKE 子句中加入 INCLUDING CONSTRAINTS 選項,這樣就會和父資料表完全相容了。
父資料表無法在子資料表仍然存在時被移除。子資料表的欄位和限制條件也不能被移除,如果它們是由其他資料表繼承而來的話。如果你想要移除某個資料表,包含其相關的物件的話,一個簡單的方式就是在移除時加上 CASCADE 選項(請參閱 5.13 節)。
ALTER TABLE 將會讓欄位型態和限制條件的改變,衍生至繼承它的資料表之中。一樣地,移除某個欄位,如果它有被其他資料表繼承的話,那麼就必須要加上 CASCADE 選項才行。ALTER TABLE 會遵循和 CREATE TABLE 一樣的規則,決定重覆的欄位要合併還是拒絕。
指令的繼承權限是依父資料表的權限。舉個例子,當你存取資料表 cities 時,在 cities 上給予 UPDATE 的權限,同時也隱含了賦予 capitals 更新資料的權限。這考量到這些資料也會出現在父資料表,但如果你沒有特別給予 capitals 權限的話,你還是無法直接存取 capitals。類似的情況也會發生在資料列的安全原則(5.7 節),在繼承查詢時,同樣是參考父資料表的安全原則。而子資料表額外的安全原則,只在直接查詢該資料表時有效,同時任何父資料表的安全原則會失效。
外部資料表(5.11 節)也可以是繼承的一部份,父資料表或子資料表,就如同一般的資料表一樣。只是,如果整個繼承結構中,有任何外部資料不支援的操作的話,那麼整個繼承結構就都不支援。
注意,並非所有的 SQL 指令都可以在繼承結構中執行。一般常用的資料查詢,資料更新,或結構調整(像是 SELECT、UPDATE、DELETE,還有多數 ALTER TABLE 的功能,但不包括 INSERT 或 ALTER TABLE ...... RENAME),基本上預設都是包含子資料表,也支援使用 ONLY 指示字來排除子資料表。如果是資料庫維護或調教的指令,如 REINDEX、VACUUM,一般就只支援特定且實體的資料表,就不會在繼承結構中衍生其他的動作。這些個別指令相關的行為,請參閱 SQL Commands 內的說明。
繼承功能比較嚴格的限制是索引(包含唯一性索引),還有外部鍵的限制條件,都只能用在單一資料表,而不會衍生至他們的子資料表中。對外部鍵來說,無論引用資料表或是參考資料表的情況都一樣。下面是一些例子說明:
如果我們宣告 cities.name 具備唯一性或是主鍵,這不會限制到 capitals 中有重覆的項目。而這些重覆的資料列就會出現在 cities 的查詢結果中。事實上,預設的 capitals 就沒有唯一性的限制,所以就可能有多個資料列記載相同的名稱。你可以在 capitals 中也加入唯一性索引,但這也無法避免 capitals 和 cities 中有重覆的項目。
同樣地,如果我們指定 cities.name 以外部鍵的方式,參考另一個資料表,而這個外部鍵也不會衍生到 capitals 中。這種情況你就必須在 capitals 中也以 REFERENCES 設定同樣外部鍵的引用。
如果有另一個資料表的欄位設定了 REFERENCES cities(name) 就會允許其他的資料表包含城市名稱,但就沒有首都名稱。在這個情況下,沒有好的解決辦法。
這些缺點可能會在後續的版本中被修正,但在此時此刻,當你需要使用繼承功能讓你的應用設計更好用時,你就必須要同時考慮這些限制。
當一個資料庫物件被建立時,它會先指定存取權限給擁有者,而擁有者一般來說就是執行建立指令的使用者。對大多數的資料庫物件來說,其預設的狀態就是只有擁有者(或超級使用者)可以對該物件進行所有操作。要讓給其他使用者來操作的話,就必須進行授權的動作。
有很多不同種類的權限:SELECT、INSERT、UPDATE、DELETE、TRUNCATE、REFERENCES、TRIGGER、CREATE、CONNECT、TEMPORARY、EXECUTE、USAGE。這些權限對於不同物件的效果,會因為是哪一種物件而有所差別(表格、函式...等等)。要瞭解完整在 PostgreSQL 中所支援的各種物件權限,請參考 GRANT 語法頁面。這裡的內容主要說明如何使用。
修改和移除一個資料庫物件,是只有擁有者才具備的權力。
要把一個物件被指派給一個新的擁有者的話,使用該物件的 ALTER 指令,例如:ALTER TABLE。超級使用者也可以做指派的動作;原來的擁有者如果它仍是該物件的管理群組一員的話,當然也可以;再來就管理群組新的成員。
要進行授權行為的話,請使用 GRANT 指令。舉例來說,如果 joe 是一個使用者,而 accounts 是一個表格,要讓他可以獲得更新表格資料的權力:
使用 ALL 的權限,就代表授權所有可設定的權限。
有一個特別的使用者是 PUBLIC,代表的是系統內的所有使用者。當資料庫內有很多使用者時,可以制定「群組(group)」來簡化管理。這部份詳細的說明請參閱第 21 章。
要移除權限,請使用 REVOKE 指令:
物件擁有者的特殊權限(例如DROP、GRANT、REVOKE...等)都是和擁有者一起設定,而無法單獨授權。不過,擁有者可以選擇移除自己的權限,例如建立一個唯讀的表格,讓自己和其他人一樣。
回到前面所說的,只有物件的擁有者(或超級使用者)可以變更該物件的權限。然而,也可以使用「with grant option」讓另一個使用者可以代授權給其他使用者。不過如果這個「grant option」被移除時,所有被代授權的使用者都會同時喪失該權限。更詳細的說明請參閱 GRANT 及 REVOKE 說明頁面。
資料型別是一種限制資料如何被儲存在表格中的方式。然而,對許多應用來說,這樣的限制還是不夠細膩。舉個例子,一個欄位包含了產品價格,當然它必須只能是正整數,但並沒有標準的資料型別可以只限制在正整數。另一個需求是,你可能想要限制的條件是依據其他的資料而定。舉例來說,在表格中的產品資訊,每一個產品編號都不能重覆。
所以,SQL 允許你在表格和欄位上定義額外的限制條件,它幫助你對資料有更多的控制能力。當某個使用者輸入資料時,違反了限制條件,錯誤訊息就會產生。這些限制條件也會限制預設值的設定。
使用 CHECK 是最普遍的限制條件製定方式,它可以允許你指定某個欄位必須符合某個布林條件式的判斷。舉個例子,要滿足產品價格是正數的話,你可以使用這樣的語法:
如同你所看到的,限制條件會接在資料型別之後,就像是預設值的設定一樣。預設值和限制條件的設定,在語法撰寫上沒有先後次序。檢查限制條件使用關鍵字 CHECK,然後接著是一組以括號括起來的條件式。其條件式應該要包含被限制的欄位,不然就沒有任何意義。
你也可以讓該限制條件擁有另一個名稱,這樣的好處是,當錯誤訊息發生時,你可以明確得到是哪一個限制被違反了:
如上,給予這個限制條件一個名稱,使用關鍵字 CONSTRAINT,緊接著一個限制條件的定義。(如果你沒有自行命名,系統也會自動取一個名字)
一個限制條件可以參考多個欄位。例如你設定了標準價格和優惠價格,而你需要確保優惠價格一定是比標準價格要便宜的話:
前兩個限制條件和前述很類似,而第三個是新的語法。它並不是只參考某個特定的欄位,而是以逗號分隔列出所有需要遵守的條件。欄位的定義和限制條件的定義,撰寫上沒有規定次序。
我們會說前兩個是欄位的限制,而第三個是表格的限制,因為它是獨立於其他的欄位定義的。欄位限制也可以寫成表格的限制方式,不過反過來通常就不行,因為一個欄位的限制,指的就是只參考到語法上它所接續的欄位而已。(PostgreSQL 並沒有強制這樣做,但如果你的語法與其他資料庫共用的話,最好還是依這樣的語法避免混用。)上面的例子也可以改寫成如此:
或等同於:
都可以照你所喜愛的語法撰寫。
命名表格的限制條件和欄位限制條件的命名是一樣的:
應該要注意的是,檢查限制條件是否成立,端看條件表示式在運算後是真值(true)還是空值(null)。因為當有運算元是空值時,多數的運算結果都是空值,所以可能會有空值產生在想要限制條件的欄位之中。要確保欄位中不會出現空值的話,請參閱下一段的說明。
限制無空值只要以下方的語法設定,就可以限制欄位不得存在空值的輸入:
限制無空值的語法,只能使用在欄位限制上。而限制無空值等效於以 CHECK 建立一個限制條件式為(IS NOT NULL),但在 PostgreSQL 明確使用 NOT NULL 語法的話,處理會更快速。只是它的缺點是你無法給予這樣的限制一個自訂的名稱。
當然,一個欄位可以有一個以上的限制條件。只要一個接著一個即可:
撰寫的次序沒有關係,也不需要去計較限制被檢查的次序。
NOT NULL 有一個相反的語法:NULL。這並非表示欄位裡只能是空值,如果這樣的話就完全沒用處了。其實這是一種簡化,將預設值設定為空值。NULL 語法並不是 SQL 標準的一部份,所以請不要用在可移植式的應用程式裡。(這僅是 PostgreSQL 為了相容其他資料庫而增加的功能)然而,有一些使用者喜歡使用它,因為在程序檔的撰寫上,很容易利用這個語法來切換限制條件。舉個例子,你可以先寫下:
然後在需要的時候再適時加入 NOT 關鍵字即可。
在多數資料庫設計原則上,主要欄位都應該被標示為 NOT NULL。
限制唯一性,確保在某個欄位或某一群欄位的資料,是在該表格中獨一無二的。語法如下:
這是欄位限制的語法。而:
則是表格限制的寫法。
如果想要限制一群欄位的唯一性的話,請使用表格限制的語法,欄位名稱以逗號分隔:
這表示這些欄位所包含的內容組合,在整個表格中是具有唯一性的,但任何一個欄位本身並不一定具備唯一性。
你可以命名唯一性的限制條件,語法如下:
加入唯一性的限制條件,將會自動建立一個具唯一性的 B-tree 索引,其包含的欄位就如限制條件中所條列的欄位。這樣唯一性限制的語法並不能只限制某部份列的唯一性,但如果使用「部份索引 (partial index) 」的話就可以做到。
一般來說,唯一性被違反的情況是,所限制的欄位在表格中,有超過一列的資料是相等的。不過,空值並不會被計算在內。這表示說,即使設定了唯一性的限制,在被限制的欄位中,還是有可能會有多個列的資料是空值。這個設計源自 SQL 標準,但聽說有其他的 SQL 資料庫並不是這樣的規則。所以,如果要移植這個語法到其他資料庫的話,要注意這項設計有無差異。
主鍵的意思是,某一個欄位或某一群欄位,在整個表格中,其每一列的組合都是唯一的,且有宣告唯一性的限制條件,並且也包含了非空值的條件(UNIQUE 及 NOT NULL)。所以,下面的兩種語法對資料的意義相同:
主鍵也可以包含多個欄位,語法和宣告唯一限制條件類似:
加入主鍵時,會自動建立一個具唯一性的 B-tree 索引,範圍為 PRIMARY KEY 語法所定義的欄位,並且會強制將這些欄位設定為非空值(NOT NULL)。
一個表格只能有一個主鍵。(你可以使用 UNIQUE 及 NOT NULL 設定多個同樣的限制條件,在功能上幾乎是相同的,但只能有一組條件是由 PRIMARY KEY 所定義。)關連式資料庫的理論指出,每一個表格都必須要有一個主鍵。這個規則在 PostgreSQL 中並不是強制的,但通常建議最好遵循這個理論。
主鍵在用戶端文件式的資料處理上是很有用的。舉個例子,一個圖型化介面讓使用者可以修改資料,那麼可能就需要主鍵來確認每一列的唯一性,而不致於產生混淆。也有一些用途是在資料庫系統的管理上,例如,主鍵會用於外部鍵(Foreign Keys)的處理,使其可以處理表格與表格間的資料對應問題。
外部鍵指的是某個欄位或某一群欄位的內容,必須在另一個表格相對欄位之中,存在相同內容的資料。我們會說這樣的行為是在維護兩個表格之間的關連性。
就使用我們已經使用多次的產品表格吧:
讓我們假設你有一個表格用來儲存這些產品的訂單,我們要確保這些訂單內的產品確實存在。所以我們定義一個外部鍵來關連訂單的表格和產品的表格:
這樣的話,如果 product_no 沒有出現在產品表格的話,就無法建立資料了。
我們會說像這樣的情況是,訂單表格是引用表格(referencing table),而產品表格是參考表格(referenced table)。相對地,欄位也稱為引用欄位(referencing columns)及參考欄位(referenced columns)。
你可以將這個語法簡化為:
因為在參考表格中,不在主鍵欄位組合中的欄位,就是參考欄位。
外部鍵也可以參考一組欄位。一般來說,這樣要寫成表格限制條件形式,如下:
當然,組合外部鍵的欄位數量,彼此之間必須要相等。
你可以給外部鍵一個名稱,使用語法與限制條件相同。
一個表格可以有許多個外部鍵,這用於表格之間多對多的關係。例如你有一些表格記錄了很多產品和訂單,但現在你要讓每一筆訂單也可以訂購多項產品(這在先前的語法並不允許)。你也許可以試試這個表格宣告:
注意到這裡的主鍵和外部鍵是重覆的。
我們知道外部鍵不允許沒有關連到產品的訂單,但如果企圖移除一個有訂單的產品會如何呢?SQL 有幾個選項讓你直覺進行這項操作:
不允許移除被參考到的產品
同時也刪去訂單
其他?
要描繪這些情況,讓我們建立如上需求的多對多關連的結構:當某人要移除一個有訂單的產品(以 order_items 關連)時,我們不允許執行。而如果某人移除了一筆訂單,訂單內的項目也會同步被移除:
引用和同步刪除有兩個常見的作法。用「RESTRICT」防止參考的資料被刪除;「NO ACTION」表示當限制條件被違反時,引用欄位的資料仍會留存,然後回傳錯誤訊息,如果未指定處理方式的話,這會是預設的行為(這兩個語法根本上的不同是「NO ACTION」允許延遲檢查到交易事務的最後,而「RESTRICT」則不會。);「CASCADE」指的是當參考的資料列被刪除時,引用的資料列也會同步被刪除。刪除時還有兩個其他的選項:SET NULL 和 SET DEFAULT,表示引用的資料會被更新為空值或其預設值。注意到,這並不是說你就可以違反限制條件。舉個例來說,如果使用了 SET DEFAULT,但預設值卻違反了外部鍵的限制,這個操作將會失敗。
類似的於 ON DELETE 的情況是 ON UPDATE,也就是在參考欄位的資料內容被更新時的情況。可以設定的動作關鍵字是相同的。在這個情況的 CASCADE 指的就是更新參考欄位的資料內容時,引用欄位的內容也會同步被更新為相同的內容。
一般來說,引用的資料列不需要滿足外部鍵的定義,如果其任一欄位內容為空值的話。而如果「MATCH FULL」加到宣告的語法之中的話,引用的資料列就必須要全部都是空值才不受外部鍵的限制(也就是部份空值的資料列就不受限制)。如果要避免空值使得外部鍵失效的話,就應該宣告相關欄位為 NOT NULL。
外部鍵所參考的欄位必須要是主鍵或是宣告其唯一性限制,這表示參考欄位會有索引存在,這使得檢查關連的過程會是很有效率的。因為在刪除或更新參考資料表時,需要檢查引用資料表的情況,所以在引用表格的欄位建立索引,也是常見的作法。因為這並不是一定需要,而還有許多的選擇在於如何索引,所以宣告外部鍵時並不會自行以引用欄位組合建立索引。
關於更新資料與刪除資料的細節在第 6 章。也可以在 CREATE TABLE 語法說明中,找到更多外部鍵的說明。
除外宣告要確保的是,如果任意兩個資料列在指定的欄位或表示式被比較時,用於特定的運算子,至少有一個比較會回傳假(false)或空值(null)。語法如下:
詳情請參考 CREATE TABLE 中,CONSTRAINT 到 EXCLUDE 的段落。
加入除外宣告時,將會自動建立相對應的索引。
當你建立了一個複雜的資料庫結構,包含了許多資料表,也設計了許多外部索引鍵、檢視表、觸發事件、函數.....等等。也就是說,其實你建立了一堆物件之間的關連性。舉例來說,資料表的外部索引鍵就與另一個資料表有著參考的關連性。
要維護整個資料庫結構的完整性,PostgreSQL 得確保你不能在有關連性的情況下,隨意刪去物件。舉例來說,企圖刪去在 5.3.5 節中,我們所使用過的產品資料表,而訂單資料表與其有相依的關連性,那就會產生如下的錯誤訊息:
這個錯誤訊息包含了很有用的指引:如果你不想要一個個處理其相依關連性,那可以一次刪去他們:
如此所有相依的物件就會被刪除了,所有相互依存的物件都會,是遞迴式的處理流程。在這個例子中,它不會移除訂單資料表,只會移除外部索引鍵的限制條件,因為沒有其他物件與該外部索引鍵相依。(如果你要確認 DROP ... CASCADE 會處理哪些物件,你可以用 DETAIL 取代 CASCADE,就會輸出其相依的物件。)
幾乎所有 PostgreSQL 的 DROP 指令都支援 CASCADE 的用法。當然,有些自然的關連性是和物件型別有關。你也可以使用 RESTRICT 來取代 CASCADE 的位置,以強制以預設的行為來處理,也就是絕對不會刪去其他相關的物件。
根據 SQL 標準,不論是 RESTRICT 或 CASCADE,都必須要在 DROP 指令中明確表示,但沒有任何一套資料庫系統真的這樣設計。不過,都會內定預設行為是 RESTRICT 或 CASCADE,每個資料庫系統的情況可能會不同。
如果 DROP 指令列出了多個物件,CASCADE 只有在這些物件之外還有相依性時才會需要。舉個例子,當執行「DROP TABLE tab1, tab2」時,即使 tab1 與 tab2 之間有外部索引鍵的相依關係,而沒有指定 CASCADE,這個操作也會完成。
對於使用者自訂的函數來說,PostgreSQL 會引用函數的外顯屬性來判斷其相依性,例如函數的參數或輸出型態,但函數內部執行的相依關係就無法追蹤了。舉個列子:
(參閱 37.4 節,瞭解 SQL 語言的函數。)PostgreSQL 會知道 get_color_note 函數相依於 rainbow 資料型別:也就是刪去該資料型別時,也會強制要刪去該函數,因為它的參數將不再合法。但 PostgreSQL 就無法發現 get_color_note 和 my_colors 之間的關連性,當該資料表被移除時,此函數並不會跟著被移除。這種情況有好有壞,函數基本上還是合法的,即使內含的資料表不存在的話,頂多就是執行會出錯就是了,只要再建立該名稱的資料表就可以讓這個函數重新正常運作。
Schema 在台灣並沒有習慣的中文說法,所以仍使用原文,而不翻譯。
PostgreSQL 資料庫叢集(cluster)可以包含一個或多個資料庫。使用者和群組則是共用於叢集的層次,但沒有任何資料面是在資料庫之間能共用的。任何用戶端連到資料庫服務,都只能存取單一資料庫,你必須在連線時指定一個資料庫。
在叢集內的使用者並不需要對每個資料庫都有使用權。使用者共用指的是它們不能有同名的情況,例如在同一個叢集內,不能有兩個使用者名稱都叫 joe。但系統可以只允許 joe 使用某些叢集內的資料庫。
一個資料庫可以包含一個或多個 schema,它會包含一些資料表。Schema 也可以包含一些資料庫物件,像是資料型別、函數、和運算子。同樣的物件名稱在不同的 schema 中是不會衝突的。舉例來說,schema1 和 myschema 都可以擁有一個叫作 mytable 的資料表。和資料庫不同, schema 並不是完全隔離的:使用者可以直接取用他們連接的資料庫中的任何 schema,只要他們擁有足夠的權限。
使用 schema 有幾個好處:
允許多個使用者存取相同資料庫,而不會互相干擾。
將資料庫物件建立邏輯上的管理層,它們會更有彈性。
第三方的應用結構可以放在不同的 schema 中,避免有撞名的情況產生。
Schema 和作業系統裡的資料夾是類似的,只是它不能使用巢狀結構。
要建立 schema,請使用 CREATE SCHEMA 指令。給予一個自訂的名稱。例如:
要在 schema 中建立或存取某個物件,請使用句點(.)將兩者名稱串連起來:
這個形式在任何可以使用資料表的地方都是可以的,包含資料表結構更新指令,以及在接下來章節會討論到的資料處理指令。(我們只提到資料表的部份,但相同的概念用於其他資料庫物件都是一樣的,像是資料型別和函數。)
實際上,更一般化的語法是:
也可以這樣使用,但目前這只是為了符合 SQL 標準而已。如果你填上了資料庫的名稱,也必須填上你所連線的資料庫而已。
所以,要在新的 schema 中建立一個資料表,請使用:
要移除一個 schema,它必須要是空的,也就是所有所屬物件都已經被移除了,請使用:
但你也可以同步移除 schema 及其所屬物件,請使用:
這個部份的機制請參閱 5.13 節,會深入介紹移除時的問題。
通常你會想要建立一個 schema 給某個使用者使用(這是一種藉由命名空間規畫來限制使用者權限的方法)。可以使用下列語法:
你甚至可以省略 schema 名稱,省略的話,schema 名稱會與使用者名稱相同。請參閱後續的 5.8.6 節來瞭解如何使用。
Schema 名稱以「pg_」開頭的,是系統的保留名稱,使用者不能使用這樣的名稱建立 schema。
在前面我們所建立的資料表都沒有指定 schema 名稱。預設使用的 schema 是「public」,每一個資料庫都會有這個 schema。所以,下面兩種寫法是一樣的:
以及:
完整的名稱寫法是冗長而不容易使用的,通常最好不要把一些特別的 schema 名稱寫到應用程式裡。而資料表時常是以簡要的寫法引用,也就是只寫資料表本身的名稱。資料庫系統依據搜尋路徑的規則找到該資料表。在搜尋路徑上所遇到的第一個資料表就會被使用。如果整個搜尋路徑走完都沒有符合的資料表,那麼才會回報錯誤,即使該資料表名稱有出現在資料庫裡的其他 schema 中。
第一個會被搜尋的 schema,就是目前的 schema。除此之外也用於新的資料表建立,當 CREATE TABLE 未指定 schema 名稱的話,也會依搜尋路徑的 schema 建立。
要顯示目前的搜尋路徑,請使用下面的指令:
預設的情況是:
第一個項目指的就是和目前使用者同名的 schema 會被使用,而如果沒有同名的,它就會被忽略。第二個項目則是先前介紹過的公開 schema。第一個被找到的 schema,就會是新建物件時預設的位置,這就是為什麼預設都會被建立在公開的 schema。當某個物件在使用(資料表結構調整、資料更新、或查詢指令)時沒有註明 schema 的話,那也會使用搜尋路徑來找到符合的物件。不過,預設上只會搜尋公開的 schema。
要設定新的搜尋路徑,請使用:
(我們在這邊暫時忽略掉 $user,因為還沒有立即性的需要。)然後我們就可以試著存取資料表而不用加上 schema:
因為 myschema 在搜尋路徑裡是第一個項目,所以新的物件就會被建立在該處。
我們也可以這樣寫:
這樣的話,不指定的話就不再能夠再使用公開的 schema 了。「public」schema 並沒有比較特別,除了它一開始就會存在之外,它也可以被移除。
請參閱 9.25 節,將會介紹其他設定 schema 搜尋路徑的方式。
搜尋路徑也用於資料型別、函數、及運算子的搜尋,就如同在資料表上的行為一樣。資料型別和函數名稱完整的寫法也和資料表相同。如果你需要特別指出運算子的完整路徑的話,它比較特別,你必須這樣寫:
這是為了避免語法上的混淆。如下所示:
實務上我們都還是依賴路徑搜尋來使用運算子,這樣可以避免使用冗長且低可讀性的程式碼。
預設的情況,使用者無法存取任何不屬於他們的 schema 中的物件。要允許存取的話,該 schema 的擁有者必須要授予 USAGE 權限給其他使用者。要允許其他使用者使用某個 schema 中的物件,通常需要額外給予適當的權限。
使用者想要在其他使用者的 schema 中建立新物件的話,就必須要授予 CREATE 的權限。注意,預設上,所有的使用者在 public schema 中,都具備 CREATE 和 USAGE 權限。這使得所有的使用者在連線到某個資料庫之後,就可以在 public schema 上新增物件。如果你不希望這樣,你可以移除這些權限:
前面的「public」指的是 schema,是一個物件識別器;而後面的「PUBLIC」指的是所有使用者,是一個關鍵字。所以使用不同的大小寫,可以再複習 4.1.1 節的內容。
除了 public 以及使用者自行建立的 schema 之外,每一個資料庫還有一個稱作 pg_catalog 的 schema,它包含了系統資訊的資料表和內建的資料型別、函數、及運算子。 pg_catlog 永遠都都是搜尋路徑裡的有效項目。它沒有明確地顯示在搜尋路徑裡,但卻是隱含優先搜尋,在那些明定的搜尋項目之前。這是為了確保內建的物件的名稱都能被找到。然而,你可以把 pg_catlog 放在搜尋路徑的最後面,如果你希望自訂的同名物件能優先被使用的話。
系統用的資料表都以「pg_」開頭,為的就是確保不會有衝突的情況出現,以免將來新的系統資料表和你現在所定義的資料表同名。(以預設的搜尋路徑來說,一個簡單的資料表使用,會直接被同名的系統資料表取代。)系統資料表會一直遵循這個命名規則,就不會產生衝突,只要使用者不使用「 pg_」開頭的命名方式就好了。
Schema 可以在許多方面協助你組織你的資料。有一些巧妙的樣版值得推薦,也很方便以預設的方式支援:
如果你沒有建立任何 schema 的話,那麼所有使用者就是隱含著都使用 public schema。這種情況指的是都沒有設定任何 schema,而主要推薦給在一個資料庫中,只有一個使用者的情況。這樣的樣版設定也適合之後轉換到無 schema 設計的資料庫環境。
你可以為每一個使用者建立一個同名的 schema。回想一下先前介紹的預設搜尋路徑,第一個項目就是 $user,表示該使用者的名稱。所以,每一個使用者有一個專屬的 schema,預設上,他們就只存取他們所擁有的 schema。 如果你使用這個情境樣版,你也許會需要移除 public schema 的權限,甚至直接移除它,讓使用者真正被隔離在他們自己的 schema 中。
要安裝共享的應用程式(每個人共享資料表,有一些第三方提供的延伸套件,或其他的東西。),把他們放到不同的 schema 裡,然後記得要設定好適當的存取權限。使用者可以使用完整的名稱來存取這些共享的應用程式,或把他們加入到搜尋路徑中,由使用者自己來決定。
在標準 SQL 中,在同一個 schema 中的物件,分別被不同使用者擁有,是不被允許的。然而,有一些實作系統甚至不允許使用者建立和自己不同名的 schema。事實上,schema 和使用者的概念,對於只支援基本 schema 的資料庫系統本身而言,幾乎是相同的。所以,許多使用者會認為完整名稱指的是 user_name.table_name。這也就是為什麼 PostgreSQL 建議你這樣為每一個使用者建立他們同名的 schema。
再者,在標準 SQL 裡,也沒有所謂 public schema 的概念。極致相容標準的話,你就不應該使用,或移除 public schema。
當然,也有些 SQL 資料庫並沒有實作 schema,或提供其他跨資料庫存取的命名方式。如果你需要和這些系統共同運作,要提高可攜性的方式就是不要使用任何 schema。
PostgreSQL 支援基本的分割資料表。本節描述了為什麼以及如何在資料庫設計中實現分割資料表。
分割資料表指的是將一個大型資料表以邏輯規則實體拆分為較小的資料庫。分割資料表可以帶來以下好處:
在某些情況下,尤其是當資料表中大多數被頻繁存取的資料位於單個分割區或少量的分割區之中時,查詢效能可以顯著地提高。分割區替代了索引的前幾個欄位,從而縮減了索引的大小,並使索引中頻繁使用的部分更有可能都放入記憶體之中。
當查詢或更新存取單個分割區的很大一部分時,可以透過對該分割區進行循序掃描而不是使用索引和遍及整個資料表的隨機讀取來提高效能。
如果計劃程序將這種需求計劃在分割區的設計中,則可以透過增加或刪除分區來完成批次加入和刪除。使用 ALTER TABLE DETACH PARTITION 或使用 DROP TABLE 刪除單個分割區比批次操作要快得多。這些命令還完全避免了由批次 DELETE 所增加的 VACUUM 成本。
很少使用的資料可以遷移到慢一些,但更便宜的儲存媒體上。
通常只有在資料表很大的情況下,這些好處才是值得的。資料表可以從分割區中受益的確切評估點取決於應用程式,儘管經驗法則是資料表的大小超過資料庫伺服器的記憶體大小的時候。
PostgreSQL 內建支援以下形式的分割方式:
此資料庫表的分割區以一個欄位為鍵或一組欄位定義的「range」來分配,分配給不同分割區的範圍之間沒有重疊。例如,可以按日期範圍或特定業務對象的標識範圍進行分割。
透過明確列出哪些鍵值出現應該在哪個分割區中來對資料表進行分割。
透過為每個分割區指定除數和餘數來對資料表進行分割。每個分割區將保留其分割鍵的雜湊值除以指定的除數所產生指定的餘數的資料列。
如果您的應用程式需要使用上面未列出的其他分割區形式,則可以使用替代方法,例如繼承和 UNION ALL 檢視表。此類方法提供了靈活性,但沒有內建宣告分割區的效能優勢。
你可以在 PostgreSQL 上宣告一個資料表實際上被劃分為多個分割區。被劃分的資料表稱為分割資料表。此宣告包括如上所述的分割區方法,以及要用作分割區主鍵的欄位或表示式的列表。
分割資料表本身是一個「虛擬」資料表,沒有自己的儲存空間。而是儲存屬於分割區,分割區是與分割資料表相關聯的基本資料表。每個分割區都儲存由其分割區範圍定義的資料子集合。插入分割區資料表中的所有的資料都將根據分割主鍵欄位的值被重新導向到相應的其中一個分割區之中。如果某筆資料的分割主鍵不再滿足其原始分割區的分割區範圍,所以 UPDATE 該筆資料將可能導致其遷移至其他分割區。
分割區本身也可以定義為分割資料表,從而形成子分割區。儘管所有分割區都必須與其分割區的父親具有相同的欄位,但是分割區可以擁有自己的索引、限制條件和預設值,與其他分割區的索引、限制條件和預設值不同。有關建立分割區表和分割區的更多詳細說明,請參閱 CREATE TABLE。
It is not possible to turn a regular table into a partitioned table or vice versa. However, it is possible to add an existing regular or partitioned table as a partition of a partitioned table, or remove a partition from a partitioned table turning it into a standalone table; this can simplify and speed up many maintenance processes. See ALTER TABLE to learn more about the ATTACH PARTITION
and DETACH PARTITION
sub-commands.
Partitions can also be foreign tables, although they have some limitations that normal tables do not; see CREATE FOREIGN TABLE for more information.
Suppose we are constructing a database for a large ice cream company. The company measures peak temperatures every day as well as ice cream sales in each region. Conceptually, we want a table like:
We know that most queries will access just the last week's, month's or quarter's data, since the main use of this table will be to prepare online reports for management. To reduce the amount of old data that needs to be stored, we decide to only keep the most recent 3 years worth of data. At the beginning of each month we will remove the oldest month's data. In this situation we can use partitioning to help us meet all of our different requirements for the measurements table.
To use declarative partitioning in this case, use the following steps:
Create measurement
table as a partitioned table by specifying the PARTITION BY
clause, which includes the partitioning method (RANGE
in this case) and the list of column(s) to use as the partition key.
You may decide to use multiple columns in the partition key for range partitioning, if desired. Of course, this will often result in a larger number of partitions, each of which is individually smaller. On the other hand, using fewer columns may lead to a coarser-grained partitioning criteria with smaller number of partitions. A query accessing the partitioned table will have to scan fewer partitions if the conditions involve some or all of these columns. For example, consider a table range partitioned using columns lastname
and firstname
(in that order) as the partition key.
Create partitions. Each partition's definition must specify the bounds that correspond to the partitioning method and partition key of the parent. Note that specifying bounds such that the new partition's values will overlap with those in one or more existing partitions will cause an error. Inserting data into the parent table that does not map to one of the existing partitions will cause an error; an appropriate partition must be added manually.
Partitions thus created are in every way normal PostgreSQL tables (or, possibly, foreign tables). It is possible to specify a tablespace and storage parameters for each partition separately.
It is not necessary to create table constraints describing partition boundary condition for partitions. Instead, partition constraints are generated implicitly from the partition bound specification whenever there is need to refer to them.
To implement sub-partitioning, specify the PARTITION BY
clause in the commands used to create individual partitions, for example:
After creating partitions of measurement_y2006m02
, any data inserted into measurement
that is mapped to measurement_y2006m02
(or data that is directly inserted into measurement_y2006m02
, provided it satisfies its partition constraint) will be further redirected to one of its partitions based on the peaktemp
column. The partition key specified may overlap with the parent's partition key, although care should be taken when specifying the bounds of a sub-partition such that the set of data it accepts constitutes a subset of what the partition's own bounds allows; the system does not try to check whether that's really the case.
Create an index on the key column(s), as well as any other indexes you might want, on the partitioned table. (The key index is not strictly necessary, but in most scenarios it is helpful.) This automatically creates one index on each partition, and any partitions you create or attach later will also contain the index.
Ensure that the enable_partition_pruning configuration parameter is not disabled in postgresql.conf
. If it is, queries will not be optimized as desired.
In the above example we would be creating a new partition each month, so it might be wise to write a script that generates the required DDL automatically.
Normally the set of partitions established when initially defining the table are not intended to remain static. It is common to want to remove old partitions of data and periodically add new partitions for new data. One of the most important advantages of partitioning is precisely that it allows this otherwise painful task to be executed nearly instantaneously by manipulating the partition structure, rather than physically moving large amounts of data around.
The simplest option for removing old data is to drop the partition that is no longer necessary:
This can very quickly delete millions of records because it doesn't have to individually delete every record. Note however that the above command requires taking an ACCESS EXCLUSIVE
lock on the parent table.
Another option that is often preferable is to remove the partition from the partitioned table but retain access to it as a table in its own right:
This allows further operations to be performed on the data before it is dropped. For example, this is often a useful time to back up the data using COPY
, pg_dump, or similar tools. It might also be a useful time to aggregate data into smaller formats, perform other data manipulations, or run reports.
Similarly we can add a new partition to handle new data. We can create an empty partition in the partitioned table just as the original partitions were created above:
As an alternative, it is sometimes more convenient to create the new table outside the partition structure, and make it a proper partition later. This allows the data to be loaded, checked, and transformed prior to it appearing in the partitioned table:
Before running the ATTACH PARTITION
command, it is recommended to create a CHECK
constraint on the table to be attached matching the desired partition constraint. That way, the system will be able to skip the scan to validate the implicit partition constraint. Without the CHECK
constraint, the table will be scanned to validate the partition constraint while holding an ACCESS EXCLUSIVE
lock on that partition and a SHARE UPDATE EXCLUSIVE
lock on the parent table. It may be desired to drop the redundant CHECK
constraint after ATTACH PARTITION
is finished.
As explained above, it is possible to create indexes on partitioned tables and they are applied automatically to the entire hierarchy. This is very convenient, as not only the existing partitions will become indexed, but also any partitions that are created in the future will. One limitation is that it's not possible to use the CONCURRENTLY
qualifier when creating such a partitioned index. To overcome long lock times, it is possible to use CREATE INDEX ON ONLY
the partitioned table; such an index is marked invalid, and the partitions do not get the index applied automatically. The indexes on partitions can be created separately using CONCURRENTLY
, and later attached to the index on the parent using ALTER INDEX .. ATTACH PARTITION
. Once indexes for all partitions are attached to the parent index, the parent index is marked valid automatically. Example:
This technique can be used with UNIQUE
and PRIMARY KEY
constraints too; the indexes are created implicitly when the constraint is created. Example:
以下是分割區資料表的限制:
無法建立跨所有分割區的限制條件。只能單獨對每個分割區設定。
分割區資料表上的唯一性限制條件必須包含所有分割主鍵欄位。存在此限制是因為 PostgreSQL 只能在每個分割區中個別實施唯一性。
必要時,必須在單個分割區(而不是分割資料表)上定義 BEFORE ROW 觸發器。
不允許在同一分割區中混合臨時和永久關連。因此,如果分割資料表是永久性的,則分割區也必須是永久性的,或者都臨時的。使用臨時關連時,分割資料表的所有成員必須來自同一個資料庫連線。
While the built-in declarative partitioning is suitable for most common use cases, there are some circumstances where a more flexible approach may be useful. Partitioning can be implemented using table inheritance, which allows for several features not supported by declarative partitioning, such as:
For declarative partitioning, partitions must have exactly the same set of columns as the partitioned table, whereas with table inheritance, child tables may have extra columns not present in the parent.
Table inheritance allows for multiple inheritance.
Declarative partitioning only supports range, list and hash partitioning, whereas table inheritance allows data to be divided in a manner of the user's choosing. (Note, however, that if constraint exclusion is unable to prune child tables effectively, query performance might be poor.)
Some operations require a stronger lock when using declarative partitioning than when using table inheritance. For example, adding or removing a partition to or from a partitioned table requires taking an ACCESS EXCLUSIVE
lock on the parent table, whereas a SHARE UPDATE EXCLUSIVE
lock is enough in the case of regular inheritance.
We use the same measurement
table we used above. To implement partitioning using inheritance, use the following steps:
Create the “master” table, from which all of the “child” tables will inherit. This table will contain no data. Do not define any check constraints on this table, unless you intend them to be applied equally to all child tables. There is no point in defining any indexes or unique constraints on it, either. For our example, the master table is the measurement
table as originally defined.
Create several “child” tables that each inherit from the master table. Normally, these tables will not add any columns to the set inherited from the master. Just as with declarative partitioning, these tables are in every way normal PostgreSQL tables (or foreign tables).
Add non-overlapping table constraints to the child tables to define the allowed key values in each.
Typical examples would be:
Ensure that the constraints guarantee that there is no overlap between the key values permitted in different child tables. A common mistake is to set up range constraints like:
This is wrong since it is not clear which child table the key value 200 belongs in.
It would be better to instead create child tables as follows:
For each child table, create an index on the key column(s), as well as any other indexes you might want.
We want our application to be able to say INSERT INTO measurement ...
and have the data be redirected into the appropriate child table. We can arrange that by attaching a suitable trigger function to the master table. If data will be added only to the latest child, we can use a very simple trigger function:
After creating the function, we create a trigger which calls the trigger function:
We must redefine the trigger function each month so that it always points to the current child table. The trigger definition does not need to be updated, however.
We might want to insert data and have the server automatically locate the child table into which the row should be added. We could do this with a more complex trigger function, for example:
The trigger definition is the same as before. Note that each IF
test must exactly match the CHECK
constraint for its child table.
While this function is more complex than the single-month case, it doesn't need to be updated as often, since branches can be added in advance of being needed.
Note
In practice, it might be best to check the newest child first, if most inserts go into that child. For simplicity, we have shown the trigger's tests in the same order as in other parts of this example.
A different approach to redirecting inserts into the appropriate child table is to set up rules, instead of a trigger, on the master table. For example:
A rule has significantly more overhead than a trigger, but the overhead is paid once per query rather than once per row, so this method might be advantageous for bulk-insert situations. In most cases, however, the trigger method will offer better performance.
Be aware that COPY
ignores rules. If you want to use COPY
to insert data, you'll need to copy into the correct child table rather than directly into the master. COPY
does fire triggers, so you can use it normally if you use the trigger approach.
Another disadvantage of the rule approach is that there is no simple way to force an error if the set of rules doesn't cover the insertion date; the data will silently go into the master table instead.
Ensure that the constraint_exclusion configuration parameter is not disabled in postgresql.conf
; otherwise child tables may be accessed unnecessarily.
As we can see, a complex table hierarchy could require a substantial amount of DDL. In the above example we would be creating a new child table each month, so it might be wise to write a script that generates the required DDL automatically.
To remove old data quickly, simply drop the child table that is no longer necessary:
To remove the child table from the inheritance hierarchy table but retain access to it as a table in its own right:
To add a new child table to handle new data, create an empty child table just as the original children were created above:
Alternatively, one may want to create and populate the new child table before adding it to the table hierarchy. This could allow data to be loaded, checked, and transformed before being made visible to queries on the parent table.
The following caveats apply to partitioning implemented using inheritance:
There is no automatic way to verify that all of the CHECK
constraints are mutually exclusive. It is safer to create code that generates child tables and creates and/or modifies associated objects than to write each by hand.
Indexes and foreign key constraints apply to single tables and not to their inheritance children, hence they have some caveats to be aware of.
The schemes shown here assume that the values of a row's key column(s) never change, or at least do not change enough to require it to move to another partition. An UPDATE
that attempts to do that will fail because of the CHECK
constraints. If you need to handle such cases, you can put suitable update triggers on the child tables, but it makes management of the structure much more complicated.
If you are using manual VACUUM
or ANALYZE
commands, don't forget that you need to run them on each child table individually. A command like:
will only process the master table.
INSERT
statements with ON CONFLICT
clauses are unlikely to work as expected, as the ON CONFLICT
action is only taken in case of unique violations on the specified target relation, not its child relations.
Triggers or rules will be needed to route rows to the desired child table, unless the application is explicitly aware of the partitioning scheme. Triggers may be complicated to write, and will be much slower than the tuple routing performed internally by declarative partitioning.
Partition pruning (分割區修剪)是一種查詢最佳化技術,可提高分割資料表的效能。 舉個例子:
如果不進行分割區修剪,則上面的查詢將掃描 measurement 資料表的每個分割區。啟用分割區修剪後,計劃程序將檢查每個分割區的定義並證明不需要掃描該分割區,因為該分割區不會包含滿足查詢 WHERE 子句的資料。當計劃程序可以證明這一點時,它將從查詢計劃中排除(修剪)分割區。
透過使用 EXPLAIN 指令和 enable_partition_pruning 組態參數,可以顯示已修剪分割區的計劃與未修剪分割區的計劃之間差異。對於這種類型的資料表設定,典型未最佳化的計劃是:
有一部份的分割區可能使用索引掃描而不是全資料表的循序掃描,但是這裡的要點是根本不需要掃描較舊的分區來回應此查詢。啟用 partition pruning 之後,我們將獲得更為簡單的查詢計劃,該計劃能夠提供相同的回應:
請注意,partition pruning 僅由分割主鍵隱含定義的內容而來,而不會參考索引。因此,不需要在相關欄位上定義索引。是否需要為該分割區建立索引取決於您是否希望掃描分割區的查詢會掃描大部分分割區還是僅掃描一小部分。在後者情況下,索引將有所幫助,但對於前者則無濟於事。
Partition pruning can be performed not only during the planning of a given query, but also during its execution. This is useful as it can allow more partitions to be pruned when clauses contain expressions whose values are not known at query planning time, for example, parameters defined in a PREPARE
statement, using a value obtained from a subquery, or using a parameterized value on the inner side of a nested loop join. Partition pruning during execution can be performed at any of the following times:
During initialization of the query plan. Partition pruning can be performed here for parameter values which are known during the initialization phase of execution. Partitions which are pruned during this stage will not show up in the query's EXPLAIN
or EXPLAIN ANALYZE
. It is possible to determine the number of partitions which were removed during this phase by observing the “Subplans Removed” property in the EXPLAIN
output.
During actual execution of the query plan. Partition pruning may also be performed here to remove partitions using values which are only known during actual query execution. This includes values from subqueries and values from execution-time parameters such as those from parameterized nested loop joins. Since the value of these parameters may change many times during the execution of the query, partition pruning is performed whenever one of the execution parameters being used by partition pruning changes. Determining if partitions were pruned during this phase requires careful inspection of the loops
property in the EXPLAIN ANALYZE
output. Subplans corresponding to different partitions may have different values for it depending on how many times each of them was pruned during execution. Some may be shown as (never executed)
if they were pruned every time.
可以使用 enable_partition_pruning 設定來停用 partition pruning。
目前僅會在 Append 和 MergeAppend 節點類型上執行 partition pruning。尚未為 ModifyTable 節點類型實作此功能,但是在將來的 PostgreSQL 版本中可能會有所改進。
Constraint exclusion is a query optimization technique similar to partition pruning. While it is primarily used for partitioning implemented using the legacy inheritance method, it can be used for other purposes, including with declarative partitioning.
Constraint exclusion works in a very similar way to partition pruning, except that it uses each table's CHECK
constraints — which gives it its name — whereas partition pruning uses the table's partition bounds, which exist only in the case of declarative partitioning. Another difference is that constraint exclusion is only applied at plan time; there is no attempt to remove partitions at execution time.
The fact that constraint exclusion uses CHECK
constraints, which makes it slow compared to partition pruning, can sometimes be used as an advantage: because constraints can be defined even on declaratively-partitioned tables, in addition to their internal partition bounds, constraint exclusion may be able to elide additional partitions from the query plan.
The default (and recommended) setting of constraint_exclusion is neither on
nor off
, but an intermediate setting called partition
, which causes the technique to be applied only to queries that are likely to be working on inheritance partitioned tables. The on
setting causes the planner to examine CHECK
constraints in all queries, even simple ones that are unlikely to benefit.
The following caveats apply to constraint exclusion:
Constraint exclusion is only applied during query planning, unlike partition pruning, which can also be applied during query execution.
Constraint exclusion only works when the query's WHERE
clause contains constants (or externally supplied parameters). For example, a comparison against a non-immutable function such as CURRENT_TIMESTAMP
cannot be optimized, since the planner cannot know which child table the function's value might fall into at run time.
Keep the partitioning constraints simple, else the planner may not be able to prove that child tables might not need to be visited. Use simple equality conditions for list partitioning, or simple range tests for range partitioning, as illustrated in the preceding examples. A good rule of thumb is that partitioning constraints should contain only comparisons of the partitioning column(s) to constants using B-tree-indexable operators, because only B-tree-indexable column(s) are allowed in the partition key.
All constraints on all children of the parent table are examined during constraint exclusion, so large numbers of children are likely to increase query planning time considerably. So the legacy inheritance based partitioning will work well with up to perhaps a hundred child tables; don't try to use many thousands of children.
The choice of how to partition a table should be made carefully as the performance of query planning and execution can be negatively affected by poor design.
One of the most critical design decisions will be the column or columns by which you partition your data. Often the best choice will be to partition by the column or set of columns which most commonly appear in WHERE
clauses of queries being executed on the partitioned table. WHERE
clause items that match and are compatible with the partition key can be used to prune unneeded partitions. However, you may be forced into making other decisions by requirements for the PRIMARY KEY
or a UNIQUE
constraint. Removal of unwanted data is also a factor to consider when planning your partitioning strategy. An entire partition can be detached fairly quickly, so it may be beneficial to design the partition strategy in such a way that all data to be removed at once is located in a single partition.
Choosing the target number of partitions that the table should be divided into is also a critical decision to make. Not having enough partitions may mean that indexes remain too large and that data locality remains poor which could result in low cache hit ratios. However, dividing the table into too many partitions can also cause issues. Too many partitions can mean longer query planning times and higher memory consumption during both query planning and execution. When choosing how to partition your table, it's also important to consider what changes may occur in the future. For example, if you choose to have one partition per customer and you currently have a small number of large customers, consider the implications if in several years you instead find yourself with a large number of small customers. In this case, it may be better to choose to partition by HASH
and choose a reasonable number of partitions rather than trying to partition by LIST
and hoping that the number of customers does not increase beyond what it is practical to partition the data by.
Sub-partitioning can be useful to further divide partitions that are expected to become larger than other partitions, although excessive sub-partitioning can easily lead to large numbers of partitions and can cause the same problems mentioned in the preceding paragraph.
It is also important to consider the overhead of partitioning during query planning and execution. The query planner is generally able to handle partition hierarchies with up to a few thousand partitions fairly well, provided that typical queries allow the query planner to prune all but a small number of partitions. Planning times become longer and memory consumption becomes higher when more partitions remain after the planner performs partition pruning. This is particularly true for the UPDATE
and DELETE
commands. Another reason to be concerned about having a large number of partitions is that the server's memory consumption may grow significantly over a period of time, especially if many sessions touch large numbers of partitions. That's because each partition requires its metadata to be loaded into the local memory of each session that touches it.
With data warehouse type workloads, it can make sense to use a larger number of partitions than with an OLTP type workload. Generally, in data warehouses, query planning time is less of a concern as the majority of processing time is spent during query execution. With either of these two types of workload, it is important to make the right decisions early, as re-partitioning large quantities of data can be painfully slow. Simulations of the intended workload are often beneficial for optimizing the partitioning strategy. Never assume that more partitions are better than fewer partitions and vice-versa.