# 38.7. 函數易變性類別

每個函數都有易變性的分類，可能為 VOLATILE、STABLE 或 IMMUTABLE。如果 CREATE FUNCTION 指令沒有指定類別，則 VOLATILE 是預設值。易變性類別用於是函數最佳化時的依據：

* 一個 VOLATILE 函數可以做任何事情，包括修改資料庫。它可以使用相同的參數在連續呼叫中回傳不同的結果。優化器不對這些函數的行為做任何假設。使用 volatile 函數的查詢將在需要其值的每一個資料列重新運算該函數。
* STABLE 函數不能修改資料庫，並且保證在單個語句中給予所有資料列相同參數的情況下回傳相同的結果。此類別允許優化器將函數的多個呼叫優化為單個呼叫。 特別是，在索引掃描條件下使用包含這種函數的表示式是安全的。（由於索引掃描只會計算一次比較值，而不是每個資料列一次，因此在索引掃描條件下使用 VOLATILE 函數無效）。
* IMMUTABLE 函數不能修改資料庫，並且保證相同輸入永遠回傳相同的結果。這個類別允許最佳化時在查詢用常數參數呼叫函數時預先運算函數。例如，像 SELECT ... WHERE x = 2 + 2 這樣的查詢可以簡化為 SELECT ... WHERE x = 4，因為整數加法運算子下的函數被標記為 IMMUTABLE。

為獲得最佳化結果，您應該使用對他們有效的最嚴格的易變性類別來標記您的函數。

任何會有預期以外結果的函數必須標註為 VOLATILE，以便對其進行優化時不能被優化。即使是不會有預期以外結果的函數，如果它的值可能在單個查詢中改變，也需要標記為 VOLATILE；一些例子是 random()，currval()，timeofday()。

另一個重要的例子是 current\_timestamp 函數家族被限定為 STABLE，因為它們的值在交易事務中不會改變。

在考慮到查詢計劃並且立即執行的簡單交互式查詢時，STABLE 和 IMMUTABLE 類別之間的差別相對較小：函數在計劃期間執行一次，或者在查詢執行啟動期間執行一次並不重要。但是，如果查詢計劃保存並稍後再使用，則會有很大差異。如果標記一個函數 IMMUTABLE，那麼它可能會在查詢計劃過程中過早地將其簡化為常數，導致在隨後的計劃使用過程中重新使用舊值。使用預準備語句或使用暫存計劃的函數語言（如PL/pgSQL）時，這會是一種風險。

對於使用 SQL 或任何標準程序語言撰寫的函數，由易變性類別確定的第二個重要屬性，即由正在呼叫該函數的 SQL 指令所做的任何資料變更的可見性。一個 VOLATILE 函數會看到這樣的變化，一個 STABLE 或 IMMUTABLE 函數則不會。此行為是使用 MVCC 的快照行為實現的（請參閱[第 13 章](https://docs.postgresql.tw/11/the-sql-language/concurrency-control)）：STABLE 和 IMMUTABLE 函數使用從呼叫查詢開始時所建立的快照，而 VOLATILE 函數在執行每個查詢的開始時獲取新的快照。

> 注意\
> 用 C 語言撰寫的函數可以想要的方式管理快照，不過以本節的方式運用 C 函數也是一個好的作法。

由於此快照行為，即使從可能正在透過平行查詢進行變更的資料表中選擇，只包含 SELECT 指令的函數也可以安全地標記為 STABLE。PostgreSQL將使用為呼叫查詢建立的快照執行 STABLE 函數的所有命令，因此它將在該查詢中看到資料庫的固定檢視內容。

IMMUTABLE 函數中的 SELECT 指令使用相同的快照行為。根據 IMMUTABLE 函數從資料庫資料表中進行選擇通常是不明智的，因為如果資料表內容發生變化，不變性將被破壞。但是，PostgreSQL並沒有強制你不能這樣做。

當一個函數的結果取決於一個配置參數時，一個常見的錯誤是標記一個函數 IMMUTABLE。 例如，一個操縱時間戳記的函數可能具有取決於 [TimeZone](https://docs.postgresql.tw/11/server-administration/server-configuration/19.11.-yong-hu-duan-lian-xian-yu-she-can-shu#19-11-2-xi-ge-shi) 設定的結果。為了安全起見，這些功能應該標記為 STABLE。

> 注意\
> PostgreSQL 要求 STABLE 和 IMMUTABLE 函數不能包含 SELECT 以外的 SQL 指令以防止資料修改。（這不是一個完全防彈的要求，因為這些函數仍然可以呼叫修改資料庫的 VOLATILE 函數，如果這樣做，你會發現 STABLE 或 IMMUTABLE 函數並沒有注意到被呼叫函數應用的資料庫更改，因為它們會其快照是隱藏的。）
