在每個會話中第一次調用 PL/pgSQL 函數時,調用處理器分析函數源文本生成二進製指令樹。該指令樹完全轉換了 PL/pgSQL 語句結構,但是在函數內使用的獨立 SQL 表達式和 SQL 命令並未立即轉換。
在每個函數中用到的表達式和 SQL 命令在函數裡首次使用的時候,PL/pgSQL 解釋器創建一個預備執行規劃(使用 SPI 管理器的 SPI_prepare
和 SPI_saveplan
函數)。隨後對該表達式或者命令的訪問都將使用已準備好的規劃。因此,一個在條件代碼中有許多語句並且可能需要執行規劃的函數,隻需要準備和保存那些真正在數據庫連接期間使用到的規劃。這樣可以有效地減少為 PL/pgSQL 函數裡的語句生成分析和執行規劃的總時間。不過有個缺點是在特定表達式或者命令中的錯誤可能要到函數中的那部分被執行的時候才能發現。
一旦在 PL/pgSQL 函數裡為一個命令製定了執行計劃,那麼它將在該次數據庫連接的生命期內複用該規劃。這麼做在性能上通常會更好,但是如果你動態地修改數據庫模式,那麼就可能有問題。比如:
CREATE FUNCTION populate() RETURNS integer AS $$ DECLARE -- 聲明段 BEGIN PERFORM my_function(); END; $$ LANGUAGE plpgsql;
如果你執行上麵的函數,那麼它將在為 PERFORM 語句生成的執行計劃中引用 my_function()
的 OID 。之後,如果你刪除然後重新創建 my_function()
,那麼 populate()
就會再也找不到 my_function()
。這時候你隻能重新創建 populate()
或者至少是重新開始一個新的數據庫會話,好讓該函數能重新編譯一次。另外一個避免這種問題的方法是在更新 my_function
定義的時候使用 CREATE OR REPLACE FUNCTION (如果一個函數被"替換",那麼它的 OID 將不會變化)。
因為 PL/pgSQL 用這種方法保存執行規劃,所以那些在 PL/pgSQL 裡直接出現的 SQL 命令必須在每次執行的時候引用相同的表和字段;也就是說,你不能拿一個參數用做 SQL 命令中的表或者字段的名稱。要繞開這個限製,你可以用 PL/pgSQL 的 EXECUTE 語句動態地構造命令,代價是每次執行的時候都構造一個新的命令計劃。
【注意】PL/pgSQL 的 EXECUTE 語句和 PostgreSQL 服務器支持的 EXECUTE 語句冇有關係。服務器的 EXECUTE 語句不能在 PL/pgSQL 函數中使用(而且也冇必要)。
SQL 是 PostgreSQL 和大多數其它關係型數據庫的命令語言。它是可以移植的,並且容易學習使用。但是所有 SQL 語句都必須由數據庫服務器獨立地執行。
這就意味著你的客戶端應用必須把每條命令發送到數據庫服務器,等待它處理這個命令,接收結果,對結果進行一些處理,然後再給服務器發送另外一條命令。所有這些東西都會產生進程間通訊,並且如果你的客戶端在另外一台機器上甚至還會導致網絡開銷。
如果使用了 PL/pgSQL ,那麼你可以把一塊運算和一係列命令在數據庫服務器內部組成一個塊,這樣就擁有了過程語言的力量並且簡化 SQL 的使用,因而節約了大量的時間,因為不需要進行客戶端/服務器通訊。
消除了服務器和客戶端之間的往返通信
客戶端不需要的中間結果無需在服務器端和客戶端來回傳遞。
不需要額外的語法分析步驟。
比起不使用存儲函數來,這樣做能夠產生明顯的性能提升。
同樣,在 PL/pgSQL 裡,仍然可以使用 SQL 的所有數據類型,操作符和函數。
使用 PL/pgSQL 所寫的函數能夠接受服務器支持的任何標量或數組數據類型作為參數,並且同樣能夠返回這些類型的結果,它們還可以接受或者返回任意用名字聲明的複合類型(行類型)。還可以聲明一個返回 record 類型(行類型)的 PL/pgSQL 函數,這個行的字段是在調用它的查詢中指定的,就像在節7.2.1.4裡討論的那樣。
PL/pgSQL 函數還可以聲明為接受並返回多態的 anyelement 和 anyarray 類型。一個多態函數實際操作的數據類型可以在不同的調用環境中變化,如在節33.2.5裡討論的那樣。一個例子是節37.4.1。
PL/pgSQL 還可以聲明為返回一個任何單個實例的"集"(set),或者表。這樣的函數通過為結果集每個需要返回的元素執行一個 RETURN NEXT 生成它的輸出。
最後,PL/pgSQL 函數可以聲明為返回 void ,如果它冇啥有用的東西可以返回的話。
PL/pgSQL 函數也可以聲明為輸出某種類型的參數,代替明確的返回類型聲明。這麼做並未給該語言增加任何基礎設施,隻是通常更方便些,特彆是返回多行數值的時候。