快取資料庫查詢結果能大幅降低延遲與資料庫負載,但快取失效策略是核心難題:
DBMS_CHANGE_NOTIFICATION.REG_START),指定回呼處理程序(PL/SQL 程序或 OCI Callback)-- SYS 權限授予
GRANT CHANGE NOTIFICATION TO oe;
GRANT EXECUTE ON DBMS_CHANGE_NOTIFICATION TO oe;
-- 確保 JOB_QUEUE_PROCESSES > 0
ALTER SYSTEM SET JOB_QUEUE_PROCESSES = 2;
CONNECT oe/oe;
CREATE TABLE nfresults (
operdate DATE,
tblname VARCHAR2(60),
rslt_msg VARCHAR2(100)
);
CREATE OR REPLACE PROCEDURE sendNotification(
url IN VARCHAR2, tblname IN VARCHAR2, order_id IN VARCHAR2
) IS
req UTL_HTTP.REQ;
resp UTL_HTTP.RESP;
err_msg VARCHAR2(100);
tbl VARCHAR2(60);
BEGIN
tbl := SUBSTR(tblname, INSTR(tblname, '.', 1, 1) + 1, 60); -- 移除 schema 前綴
BEGIN
req := UTL_HTTP.BEGIN_REQUEST(url || order_id || '&table=' || tbl);
resp := UTL_HTTP.GET_RESPONSE(req);
INSERT INTO nfresults VALUES (SYSDATE, tblname, resp.reason_phrase);
UTL_HTTP.END_RESPONSE(resp);
EXCEPTION WHEN OTHERS THEN
err_msg := SUBSTR(SQLERRM, 1, 100);
INSERT INTO nfresults VALUES (SYSDATE, tblname, err_msg);
END;
COMMIT;
END;
/
-- 測試:EXEC sendNotification('https://webhost/phpcache/dropResults.php?order_no=', 'OE.ORDERS', '2408');
CREATE OR REPLACE PROCEDURE orders_nf_callback(ntfnds IN SYS.CHNF$_DESC) IS
tblname VARCHAR2(60);
numtables NUMBER;
event_type NUMBER;
row_id VARCHAR2(20);
numrows NUMBER;
ord_id VARCHAR2(12);
url VARCHAR2(256) := 'https://webhost/phpcache/dropResults.php?order_no=';
BEGIN
event_type := ntfnds.event_type;
numtables := ntfnds.numtables;
IF event_type = DBMS_CHANGE_NOTIFICATION.EVENT_OBJCHANGE THEN
FOR i IN 1..numtables LOOP
tblname := ntfnds.table_desc_array(i).table_name;
-- 僅處理行級變更(非 DDL/截斷)
IF BITAND(ntfnds.table_desc_array(i).opflags, DBMS_CHANGE_NOTIFICATION.ALL_ROWS) = 0 THEN
numrows := ntfnds.table_desc_array(i).numrows;
IF tblname = 'OE.ORDERS' THEN
FOR j IN 1..numrows LOOP
row_id := ntfnds.table_desc_array(i).row_desc_array(j).row_id;
SELECT order_id INTO ord_id FROM orders WHERE ROWID = row_id;
sendNotification(url, tblname, ord_id);
END LOOP;
END IF;
END IF;
END LOOP;
END IF;
COMMIT;
END;
/
-- 註冊查詢
DECLARE
regds SYS.CHNF$_REG_INFO;
regid NUMBER;
ord_id NUMBER;
qosflags NUMBER;
BEGIN
qosflags := DBMS_CHANGE_NOTIFICATION.QOS_RELIABLE + DBMS_CHANGE_NOTIFICATION.QOS_ROWIDS;
regds := SYS.CHNF$_REG_INFO('orders_nf_callback', qosflags, 0, 0, 0);
regid := DBMS_CHANGE_NOTIFICATION.NEW_REG_START(regds);
SELECT order_id INTO ord_id FROM orders WHERE ROWNUM < 2; -- 觸發註冊的任意查詢
DBMS_CHANGE_NOTIFICATION.REG_END;
END;
/
-- 測試:UPDATE orders SET order_mode='direct' WHERE order_id=2421; COMMIT;
-- 查詢 nfresults 驗證通知發送
-- 1. 查詢現有註冊 ID
SELECT regid, table_name FROM user_change_notification_regs;
-- 假設 regid=241
-- 2. 建立 ROW 描述陣列型別(供 CAST 使用)
CREATE TYPE rdesc_tab AS TABLE OF SYS.CHNF$_RDESC;
-- 3. 修改回呼程序,新增 ORDER_ITEMS 處理邏輯
-- 在 orders_nf_callback 中原有 ORDERS 處理迴圈後加入:
IF tblname = 'OE.ORDER_ITEMS' THEN
FOR rec IN (
SELECT DISTINCT o.order_id AS o_id
FROM TABLE(CAST(ntfnds.table_desc_array(i).row_desc_array AS rdesc_tab)) t
JOIN order_items d ON t.row_id = d.ROWID
JOIN orders o ON d.order_id = o.order_id
) LOOP
sendNotification(url, tblname, rec.o_id);
END LOOP;
END IF;
-- 重新編譯程序後生效
-- 4. 將 ORDER_ITEMS 加入現有註冊
DECLARE
ord_id NUMBER;
BEGIN
DBMS_CHANGE_NOTIFICATION.ENABLE_REG(241);
SELECT order_id INTO ord_id FROM order_items WHERE ROWNUM < 2;
DBMS_CHANGE_NOTIFICATION.REG_END;
END;
/
-- 測試:UPDATE order_items SET quantity=160 WHERE order_id=2421; COMMIT;
// getOrderFields.php
function getOrderFields($order_no) {
$conn = oci_connect('oe','oe','//dbhost:1521/orcl');
$stid = oci_parse($conn, 'SELECT TO_CHAR(order_date) order_date, customer_id, order_total FROM orders WHERE order_id = :oid');
oci_bind_by_name($stid, ':oid', $order_no, 12);
oci_execute($stid);
return oci_fetch_assoc($stid); // 關聯陣列
}
// getOrderItems.php
function getOrderItems($order_no) {
$conn = oci_connect('oe','oe','//dbhost:1521/orcl');
$stid = oci_parse($conn, 'SELECT * FROM order_items WHERE order_id = :oid ORDER BY line_item_id');
oci_bind_by_name($stid, ':oid', $order_no, 12);
oci_execute($stid);
$nrows = oci_fetch_all($stid, $results);
return array($nrows, $results);
}
// testCache.php
require_once 'Cache/Lite/Function.php';
require_once 'getOrderFields.php';
require_once 'getOrderItems.php';
$cache = new Cache_Lite_Function(array('cacheDir'=>'/tmp/', 'lifeTime'=>86400));
$order_no = $_GET['order_no'];
// 訂單基本資料
if ($fields = $cache->call('getOrderFields', $order_no)) {
echo "ORDER #$order_no
";
foreach (['ORDER_DATE','CUSTOMER_ID','ORDER_TOTAL'] as $k)
echo "$k: {$fields[$k]} ";
echo "
";
} else { $cache->drop('getOrderFields', $order_no); }
// 訂單明細
if (list($nrows, $items) = $cache->call('getOrderItems', $order_no)) {
echo "";
foreach ($items as $k=>$v) echo "$k ";
echo " ";
for ($i=0; $i<$nrows; $i++) {
echo "";
foreach ($items as $col=>$vals) echo "{$vals[$i]} ";
echo " ";
}
echo "
";
} else { $cache->drop('getOrderItems', $order_no); }
// dropResults.php
require_once 'Cache/Lite/Function.php';
$cache = new Cache_Lite_Function(array('cacheDir'=>'/tmp/'));
if (isset($_GET['order_no'], $_GET['table'])) {
$order_no = $_GET['order_no'];
if ($_GET['table'] === 'ORDERS') {
$cache->drop('getOrderFields', $order_no);
// 可選:立即重建 $cache->call('getOrderFields', $order_no);
} elseif ($_GET['table'] === 'ORDER_ITEMS') {
$cache->drop('getOrderItems', $order_no);
// 可選:立即重建 $cache->call('getOrderItems', $order_no);
}
}
testCache.php?order_no=2408 → 首次查詢資料庫,結果寫入 /tmp/cache_*UPDATE orders ... WHERE order_id=2408; COMMIT;orders_nf_callback → 呼叫 sendNotification → HTTP GET dropResults.php?order_no=2408&table=ORDERSdrop() 刪除對應快取檔案testCache.php?order_no=2408 → 快取未命中 → 重新查詢資料庫 → 顯示最新資料QOS_RELIABLE 確保通知不遺失(資料庫端持久化佇列),但需監控 USER_CHANGE_NOTIFICATION_REGS 狀態Oracle DCN 實現了資料庫驅動的精準快取失效,解決了傳統 TTL 快取的「資料不一致」與「資源浪費」雙重痛點。結合 PHP Cache_Lite(或 Redis),只需少量 PL/SQL 與 PHP 程式碼,即可構建高效能、強一致性的讀取加速層。此模式適用於讀多寫少、資料變更頻率不可預測、對一致性要求高的場景(如產品目錄、訂單查詢、配置中心)。