PL/Python、PL/Perl、PL/v8:PostgreSQL 的多語言程序擴展 | PostgreSQL
2026/07/15
PostgreSQL 的程序語言架構具有高度可擴展性,除了內建的 PL/pgSQL 之外,還支援 PL/Python、PL/Perl、PL/v8(JavaScript) 等多種語言。本文完整解析各語言的安裝、語法、SPI 存取、效能特性與選擇策略。
可擴展程序語言架構
PostgreSQL 採用語言 Handler機制支援多種程序語言。每種語言本質上是一個共享函式庫,透過 CREATE EXTENSION 安裝後,PostgreSQL 即知道如何將函式原始碼委派給對應的執行環境:
使用者呼叫函式
↓
PostgreSQL 函式呼叫框架
↓
語言 Handler(e.g. plpython3)
↓
外部語言直譯器(Python / Perl / V8 Engine)
↓
透過 SPI 回呼 PostgreSQL 執行 SQL
↓
回傳結果給 PostgreSQL
所有語言函式的呼叫方式對 SQL 層完全一致,使用者感知不到底層語言差異。
支援語言總覽
| 語言 | 套件名稱 | 信任等級 | 典型應用 |
|---|---|---|---|
| PL/pgSQL | 內建 | Trusted | 一般商業邏輯 |
| PL/Python 3 | plpython3u | Untrusted | 資料科學、ML 整合 |
| PL/Perl | plperl / plperlu | 兩者皆有 | 文字處理、正則表達式 |
| PL/Tcl | pltcl / pltclu | 兩者皆有 | 輕量腳本 |
| PL/v8 | plv8 | Trusted | JSON 處理、JavaScript 生態 |
| PL/Java | pljava | Untrusted | 企業 Java 整合 |
| PL/R | plr | Untrusted | 統計運算 |
Trusted vs Untrusted 安全模型
這是選擇程序語言時最關鍵的概念。
受信任語言(Trusted):
- 無法存取檔案系統、網路、作業系統呼叫
- 任何資料庫使用者都可以建立函式
- 代表:PL/pgSQL、plperl、pltcl、plv8
不受信任語言(Untrusted):
- 可存取檔案系統、執行系統命令、開啟網路連線
- 只有 superuser 才能建立函式
- 名稱後綴加
u:plpython3u、plperlu、pltclu
-- 確認語言的信任屬性
SELECT lanname, lanpltrusted
FROM pg_language
ORDER BY lanname;
安全性建議:優先使用受信任語言;不受信任語言的函式務必由 DBA 審查,且設置 SECURITY DEFINER 時必須指定 search_path。
PL/Python(plpython3u)
安裝
# Ubuntu / Debian
sudo apt install postgresql-plpython3-16
# 在資料庫中啟用
psql -U postgres -d mydb -c "CREATE EXTENSION plpython3u;"
基本語法
-- 最簡單的 PL/Python 函式
CREATE OR REPLACE FUNCTION py_hello(name text)
RETURNS text
LANGUAGE plpython3u
AS $$
return f"Hello, {name}!"
$$;
SELECT py_hello('World');
-- 輸出:Hello, World!
-- 使用 Python 標準函式庫
CREATE OR REPLACE FUNCTION py_json_parse(raw_text text)
RETURNS jsonb
LANGUAGE plpython3u
AS $$
import json
data = json.loads(raw_text)
return json.dumps(data)
$$;
存取表資料(plpy.execute)
-- 使用參數化查詢(防止 SQL Injection)
CREATE OR REPLACE FUNCTION get_user_safe(user_id integer)
RETURNS text
LANGUAGE plpython3u
AS $$
plan = plpy.prepare(
"SELECT username, email FROM users WHERE id = $1",
["integer"]
)
result = plpy.execute(plan, [user_id], 1)
if not result:
return None
return result[0]["username"]
$$;
回傳集合(SETOF)
CREATE OR REPLACE FUNCTION py_generate_series(start_val int, end_val int)
RETURNS SETOF integer
LANGUAGE plpython3u
AS $$
return range(start_val, end_val + 1)
$$;
SELECT * FROM py_generate_series(1, 5);
-- 輸出:1, 2, 3, 4, 5
共享資料(SD 與 GD)
CREATE OR REPLACE FUNCTION py_cached_lookup(key text)
RETURNS text
LANGUAGE plpython3u
AS $$
# GD(Global Data):同一 Session 內所有 PL/Python 函式共享
if "cache" not in GD:
GD["cache"] = {}
if key in GD["cache"]:
return GD["cache"][key]
result = plpy.execute(
plpy.prepare("SELECT value FROM config WHERE key = $1", ["text"]),
[key], 1
)
if result:
GD["cache"][key] = result[0]["value"]
return result[0]["value"]
return None
$$;
錯誤處理
CREATE OR REPLACE FUNCTION py_safe_divide(a numeric, b numeric)
RETURNS numeric
LANGUAGE plpython3u
AS $$
try:
if b == 0:
plpy.error(
"Division by zero",
detail="Divisor cannot be zero",
hint="Use NULLIF(b, 0) to handle this"
)
return a / b
except Exception as e:
plpy.warning(f"Unexpected error: {e}")
return None
$$;
PL/Perl
安裝
# Ubuntu / Debian
sudo apt install postgresql-plperl-16
# 啟用受信任版本
psql -c "CREATE EXTENSION plperl;"
字串處理與正則表達式
PL/Perl 的強項在於 Perl 強大的字串處理能力:
-- 正則表達式提取 email
CREATE OR REPLACE FUNCTION extract_emails(text_content text)
RETURNS text[]
LANGUAGE plperl
AS $$
my $content = $_[0];
my @emails;
while ($content =~ /([a-zA-Z0-9._%+-]+\@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g) {
push @emails, $1;
}
return \@emails;
$$;
SELECT extract_emails('Contact info@example.com or support@company.org');
-- 字串轉 slug
CREATE OR REPLACE FUNCTION perl_slugify(input text)
RETURNS text
LANGUAGE plperl
AS $$
my $str = lc($_[0]);
$str =~ s/[^a-z0-9\s-]//g;
$str =~ s/\s+/-/g;
$str =~ s/-+/-/g;
$str =~ s/^-|-$//g;
return $str;
$$;
SELECT perl_slugify('Hello World! This is a Test.');
-- 輸出:hello-world-this-is-a-test
SPI 存取
CREATE OR REPLACE FUNCTION perl_find_user(uid integer)
RETURNS text
LANGUAGE plperl
AS $$
my $plan = spi_prepare(
'SELECT username FROM users WHERE id = $1',
'INTEGER'
);
my $rv = spi_exec_prepared($plan, $_[0]);
if ($rv->{processed} > 0) {
return $rv->{rows}[0]->{username};
}
return undef; # 回傳 SQL NULL
$$;
回傳集合
CREATE OR REPLACE FUNCTION perl_split_words(sentence text)
RETURNS SETOF text
LANGUAGE plperl
AS $$
my @words = split(/\s+/, $_[0]);
for my $word (@words) {
return_next($word);
}
return; # 必須以空 return 結束
$$;
SELECT * FROM perl_split_words('the quick brown fox');
PL/v8(JavaScript)
安裝
# Ubuntu
sudo apt install postgresql-16-pgv8
# 啟用
psql -c "CREATE EXTENSION plv8;"
基本語法
CREATE OR REPLACE FUNCTION js_hello(name text)
RETURNS text
LANGUAGE plv8
AS $$
return `Hello, ${name}!`;
$$;
-- JavaScript 物件操作
CREATE OR REPLACE FUNCTION js_parse_config(config_str text)
RETURNS jsonb
LANGUAGE plv8
AS $$
const config = JSON.parse(config_str);
const defaults = { timeout: 30, retry: 3, debug: false };
return Object.assign({}, defaults, config);
$$;
JSON 處理優勢
PL/v8 最突出的優勢在於 JSON/JSONB 的原生處理能力:
-- 複雜 JSON 扁平化
CREATE OR REPLACE FUNCTION js_flatten_json(nested jsonb, prefix text DEFAULT '')
RETURNS jsonb
LANGUAGE plv8
AS $$
function flatten(obj, prefix, result) {
result = result || {};
for (let key in obj) {
if (!obj.hasOwnProperty(key)) continue;
const fullKey = prefix ? `${prefix}.${key}` : key;
const val = obj[key];
if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
flatten(val, fullKey, result);
} else {
result[fullKey] = val;
}
}
return result;
}
return flatten(nested, prefix || '');
$$;
SELECT js_flatten_json(
'{"user": {"name": "Alice", "addr": {"city": "Taipei"}}}'
);
-- 輸出:{"user.name": "Alice", "user.addr.city": "Taipei"}
SPI 存取
CREATE OR REPLACE FUNCTION js_aggregate_sales(product_id integer)
RETURNS jsonb
LANGUAGE plv8
AS $$
const rows = plv8.execute(
'SELECT order_date, quantity, unit_price FROM order_items WHERE product_id = $1',
[product_id]
);
const summary = rows.reduce((acc, row) => {
const month = row.order_date.toISOString().substring(0, 7);
if (!acc[month]) acc[month] = { quantity: 0, revenue: 0 };
acc[month].quantity += row.quantity;
acc[month].revenue += row.quantity * row.unit_price;
return acc;
}, {});
return summary;
$$;
PL/Tcl
PL/Tcl 是 PostgreSQL 最早支援的外部程序語言之一,通常已包含在 postgresql-contrib 中:
CREATE EXTENSION pltcl;
CREATE OR REPLACE FUNCTION tcl_hello(name text)
RETURNS text
LANGUAGE pltcl
AS $$
return "Hello, $1!"
$$;
CREATE OR REPLACE FUNCTION tcl_word_count(sentence text)
RETURNS integer
LANGUAGE pltcl
AS $$
set words [split $1]
return [llength $words]
$$;
除非既有技術棧包含大量 Tcl 程式碼,否則不建議選擇 PL/Tcl 作為新專案的程序語言。
跨語言效能比較
| 語言 | 啟動開銷 | 執行效率 | SQL 互動開銷 | 記憶體使用 |
|---|---|---|---|---|
| C 函式 | 極低 | 最高 | 低 | 最低 |
| PL/pgSQL | 低 | 高 | 低 | 低 |
| PL/Perl | 中等 | 中高 | 中等 | 中等 |
| PL/v8 | 中高 | 中高 | 中等 | 中高 |
| PL/Python | 高 | 中等 | 中等 | 高 |
效能關鍵觀察:
- Python 啟動成本高:高頻小型函式呼叫場景影響顯著,用
GD快取已初始化物件 - SPI 呼叫有開銷:盡量使用
plpy.prepare()/spi_prepare()預編譯 - 型別轉換成本:大型 JSONB 或陣列傳遞時尤為明顯
- 直譯器複用:同一 backend process 生命週期內共享,第一次呼叫最慢
語言選擇決策表
| 需求情境 | 推薦語言 | 理由 |
|---|---|---|
| 一般業務邏輯 | PL/pgSQL | 最佳效能、無額外依賴 |
| 資料科學、ML 推論 | PL/Python | numpy、scipy、sklearn 生態 |
| 複雜文字處理、正則 | PL/Perl | Perl 正則最強大 |
| JSON/JSONB 複雜轉換 | PL/v8 | JavaScript 原生 JSON |
| 需要受信任環境 | PL/pgSQL / plperl / plv8 | 無需 superuser |
| 需要存取檔案/外部 API | plpython3u / plperlu | Untrusted 允許系統存取 |
| 統計分析 | PL/R | R 語言統計生態 |
| 高頻效能關鍵路徑 | PL/pgSQL 或 C | 最低執行開銷 |
選擇流程:
- 優先考慮 PL/pgSQL,覆蓋 80% 以上場景
- 若需特定語言函式庫或生態,再評估對應語言
- 安全性優先時選擇 Trusted 語言
- 效能瓶頸時考慮 C 語言函式
常見陷阱
| 陷阱 | 說明 | 解法 |
|---|---|---|
| PL/Python GIL 限制 | 不支援多執行緒,高並行場景可能瓶頸 | 減少函式呼叫頻率 |
| 大型模組載入慢 | 每次呼叫 import 開銷高 | 用 GD 快取已載入模組 |
| Perl undef 與 NULL | undef 對應 SQL NULL | 用 defined 檢查 |
| PL/v8 記憶體管理 | GD 中大型物件佔用整個 Session | 只快取輕量查詢計畫 |
| SECURITY DEFINER 風險 | 以函式擁有者權限執行 | 必須設定 search_path |
| 版本相依性 | PG 升級後共享函式庫路徑可能改變 | 定期檢查 pg_available_extensions |
版本演進
| PostgreSQL 版本 | 重要變更 |
|---|---|
| 9.x | plpython3u 正式取代 plpython2u |
| 11 | Stored Procedure(CALL)支援事務控制 |
| 14 | plpython 對 Python 3.x 相容性全面改善 |
| 16 | 函式 search_path 預設行為更嚴格 |
| 17 | PL/v8 社群持續維護以跟上 V8 引擎更新 |
總結
PostgreSQL 的多語言程序擴展讓你根據需求選擇最合適的語言:
- PL/pgSQL 是首選——效能最佳、無需額外安裝、完整 SQL 整合
- PL/Python 適合資料科學場景——但注意 Untrusted 等級與啟動開銷
- PL/Perl 在正則表達式和文字處理上無可匹敵
- PL/v8 是 JSON 處理的最佳選擇——且為 Trusted 語言
- Trusted vs Untrusted 決定了誰能建立函式以及能存取什麼資源
下一篇,我們將探討 PostgreSQL 的安裝與初始化——從原始碼編譯到套件管理器安裝、initdb 初始化、pg_hba.conf 設定到第一次連線的完整流程。