生產環境常見現象:ORA-04031: unable to allocate XXX bytes of shared memory,Shared Pool 耗盡。根因多為應用程式產生大量字面值硬編碼 SQL,每組參數產生一個唯一 Cursor,佔用大量 Shared Pool、引發大量硬解析、CPU 飆高、甚至資料庫掛起。
-- 掃描 Sharable Mem > 100KB 的 SQL
SELECT sql_text, sharable_mem
FROM v$sql
WHERE sharable_mem > 100000
ORDER BY sharable_mem DESC;
sharable_mem:該 Cursor 在 Shared Pool 佔用的記憶體 (bytes)-- 檢視 SGA 記憶體分配細目
SELECT * FROM V$SGASTAT;
// ❌ 錯誤:每次拼接產生新 SQL 文本
$sql = "SELECT * FROM table_name WHERE id = ";
$id = 1;
$stmt = ociparse($conn, $sql . $id);
ociexecute($stmt);
$id = 2;
$stmt = ociparse($conn, $sql . $id); // 又是新 SQL!
ociexecute($stmt);
// ❌ 錯誤:Statement + 字串拼接
String sql = "SELECT * FROM table_name WHERE id = ";
Statement stmt = conn.createStatement();
ResultSet rset = stmt.executeQuery(sql + "1"); // SQL 1
// ...
rset = stmt.executeQuery(sql + "2"); // SQL 2 (完全不同的 Cursor)
SELECT ... WHERE id = 1 vs SELECT ... WHERE id = 2// ✅ 正確:Prepare 一次,Bind 變數,多次 Execute
$stmt = ociparse($conn, "SELECT * FROM table_name WHERE id = :id");
ocibindbyname($stmt, ":id", $id, 12); // 12 = 最大長度
$id = 1;
ociexecute($stmt);
$id = 2;
ociexecute($stmt); // 同一 Cursor,零硬解析
// ✅ 正確:PreparedStatement + 綁定變數
PreparedStatement pstmt = conn.prepareStatement(
"SELECT * FROM table_name WHERE id = ?");
pstmt.setInt(1, 1);
ResultSet rset = pstmt.executeQuery();
pstmt.setInt(1, 2);
rset = pstmt.executeQuery(); // 同一 Cursor
-- 綁定變數 SQL 內部呈現
SELECT * FROM table_name WHERE id = :1
-- 解析一次,執行計劃快取,後續僅替換綁定值 → 瞬間執行
-- 動態 WHERE 條件:用 CASE/DECODE 或動態構建但最終用綁定變數
-- 範例:可選條件查詢
StringBuilder sql = new StringBuilder("SELECT * FROM t WHERE 1=1");
List
MEMBER OF 或 TABLE()SYS.ODCIVARCHAR2LIST + TABLE() (10g+ 內建)
SELECT * FROM t WHERE id IN (SELECT COLUMN_VALUE FROM TABLE(SYS.ODCIVARCHAR2LIST('1','2','3')));
-- 找出相同結構但不同字面值的 SQL 群組 (簡易版:正規化後分組)
SELECT
REGEXP_REPLACE(sql_text, '[0-9]+', ':BIND') AS normalized_sql,
COUNT(*) AS variations,
SUM(sharable_mem) AS total_mem,
MAX(sharable_mem) AS max_mem
FROM v$sql
WHERE sql_text NOT LIKE '%:BIND%' -- 排除已用綁定變數的
GROUP BY REGEXP_REPLACE(sql_text, '[0-9]+', ':BIND')
HAVING COUNT(*) > 10
ORDER BY total_mem DESC;
| 規範 | 要求 | 違規後果 |
|---|---|---|
| 所有參數化查詢 | 必須使用綁定變數 (PreparedStatement / OCIBindByName) | Shared Pool 耗盡、硬解析風暴、SQL 注入風險 |
| 動態條件構建 | 構建 SQL 文本後 Prepare 一次,參數綁定 | 同上 |
| IN 列表 | 超過 100 元素改用臨時表/集合類型 | SQL 過長解析失敗、Cursor 爆炸 |
| 代碼審查檢查點 | grep 專案代碼:拼接 SQL 關鍵字 (+、concat、||、format) | CI/CD 管道自動攔截 |