資料庫

supabase-postgres-best-practices

依優先級檢查 Postgres 查詢、索引、連線、RLS、結構與鎖定問題,提供可直接採用的 SQL 修正與安全理由,不需連上真實資料庫。

查看來源 ↗
作者/來源supabase★ 2,589 GitHub Stars
測試版本1.1.1更新日期:2026-08-12T07:36:50Z
支援平台OpenAI · Claude本次實測平台
安裝方式npx skills add supabase/agent-skills --skill supabase-postgres-best-practices
公平比較方式

三個問題,分別測載入前與載入後。

每次回答都在全新環境中產生。兩邊使用相同模型與設定,唯一差別是右側載入了這個 Skill。

模型版本gpt-5.6-sol帳號方案ChatGPT Plus思考強度medium執行工具codex-cli-native-skill
真實測試使用本機已登入的 ChatGPT Plus 實際執行,完成於 2026年8月31日。所有檢查通過後才公開,回答未經人工改寫。
三階段測試3 個問題全部完成版本核對3/3 通過環境清理9/9 通過測試隔離每回合全新環境
01
基礎題

修正缺少索引的查詢

測試問題

Postgres 有 orders(id bigint primary key, customer_id bigint not null, status text not null, created_at timestamptz not null),目前查詢 SELECT * FROM orders WHERE customer_id = $1 AND status = 'paid' ORDER BY created_at DESC LIMIT 50;資料 2,000 萬列且沒有額外索引。請提出最適合的索引 SQL、說明欄位順序與是否考慮 partial index,並給驗證用 EXPLAIN 指令。不得聲稱已執行。

載入前 · 0 個 Skill載入後 · 版本已核對再次確認 · 0 個 Skill已確認模型套用 Skill三個環境皆已清理
未載入 Skill438 tokens · 20684 ms
```sql -- 通用且適合此查詢模式 CREATE INDEX CONCURRENTLY idx_orders_customer_status_created_at ON orders (customer_id, status, created_at DESC); -- 驗證 EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE customer_id = $1 AND status = 'paid' ORDER BY created_at DESC LIMIT 50; ``` 欄位順序: - `customer_id`、`status` 是等值篩選欄位,置於前方。 - `created_at DESC` 緊接其後,使 PostgreSQL 能依索引順序直接取得最新 50 筆,避免排序。 - 此查詢使用 `SELECT *`,不建議為覆蓋索引而 `INCLUDE` 所有欄位;索引找到 50 筆後回表通常較合理。 - 前兩個等值欄位互換對此單一查詢通常不影響排序能力;`customer_id` 通常選擇性較高,且此前綴也較可能支援其他按客戶查詢,因此放首位。 若查詢幾乎固定只查 `status = 'paid'`,且已付款資料只占整表較小比例,可採較小的 partial index: ```sql CREATE INDEX CONCURRENTLY idx_orders_paid_customer_created_at ON orders (customer_id, created_at DESC) WHERE status = 'paid'; EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE customer_id = $1 AND status = 'paid' ORDER BY created_at DESC LIMIT 50; ``` Partial index 通常空間更小、維護成本更低,但不能支援其他 `status`。若 `paid` 占比很高,節省有限;若還有相同模式查詢其他狀態,優先使用通用索引。以上 SQL 僅為建議,未實際執行。
已載入 Skill +1521 tokens · 64490 ms
```sql CREATE INDEX CONCURRENTLY orders_paid_customer_created_at_idx ON orders (customer_id, created_at DESC) WHERE status = 'paid'; ``` - 採用 partial index:此查詢固定篩選 `status = 'paid'`,因此無須把 `status` 放入索引鍵;只索引已付款訂單可降低索引大小及維護成本。 - `customer_id` 放前面,因為它是等值條件;`created_at DESC` 放後面,讓 PostgreSQL 直接依需要的順序讀取並在取得 50 筆後停止,避免額外排序。 - 若其他重要查詢也會以不同 `status` 執行相同模式,則改用通用複合索引: ```sql CREATE INDEX CONCURRENTLY orders_customer_status_created_at_idx ON orders (customer_id, status, created_at DESC); ``` 驗證前應以實際且具代表性的客戶 ID 取代 `12345`。先用不執行查詢的 `EXPLAIN`: ```sql EXPLAIN (COSTS, VERBOSE) SELECT * FROM orders WHERE customer_id = 12345 AND status = 'paid' ORDER BY created_at DESC LIMIT 50; ``` 需要實測時再使用以下指令;`ANALYZE` 會實際執行查詢: ```sql EXPLAIN (ANALYZE, BUFFERS, VERBOSE) SELECT * FROM orders WHERE customer_id = 12345 AND status = 'paid' ORDER BY created_at DESC LIMIT 50; ``` 預期計畫使用 `orders_paid_customer_created_at_idx` 的 `Index Scan`,且不出現額外 `Sort`。以上 SQL 未實際執行。
02
實務題

設計多租戶 RLS

測試問題

Supabase 的 documents(id uuid, organization_id uuid, owner_id uuid, body text) 必須讓登入者只能讀寫自己所屬 organization 的資料。JWT custom claim organization_id 可能缺失。請提供啟用 RLS、SELECT 與 INSERT policy 的完整 SQL,避免 claim 缺失時意外放行,並指出需要的索引。不得連線或執行。

載入前 · 0 個 Skill載入後 · 版本已核對再次確認 · 0 個 Skill已確認模型套用 Skill三個環境皆已清理
未載入 Skill458 tokens · 22113 ms
```sql ALTER TABLE public.documents ENABLE ROW LEVEL SECURITY; CREATE POLICY "documents_select_same_organization" ON public.documents FOR SELECT TO authenticated USING ( organization_id IS NOT NULL AND organization_id = NULLIF(auth.jwt() ->> 'organization_id', '')::uuid ); CREATE POLICY "documents_insert_same_organization" ON public.documents FOR INSERT TO authenticated WITH CHECK ( organization_id IS NOT NULL AND organization_id = NULLIF(auth.jwt() ->> 'organization_id', '')::uuid ); CREATE INDEX IF NOT EXISTS documents_organization_id_idx ON public.documents (organization_id); ``` claim 缺失或空字串時,運算結果為 `NULL`,policy 不會放行;格式不是合法 UUID 時会拒絕該查詢。`organization_id` 索引支援 RLS 篩選;若 `id` 已是主鍵,其主鍵索引已足夠。
已載入 Skill +1992 tokens · 80454 ms
```sql alter table public.documents enable row level security; create policy "documents_select_same_organization" on public.documents for select to authenticated using ( organization_id = ( select nullif(auth.jwt() ->> 'organization_id', '')::uuid ) ); create policy "documents_insert_same_organization" on public.documents for insert to authenticated with check ( organization_id = ( select nullif(auth.jwt() ->> 'organization_id', '')::uuid ) ); grant select, insert on public.documents to authenticated; create index if not exists documents_organization_id_idx on public.documents (organization_id); ``` `organization_id` claim 缺失或為空字串時,右側為 `NULL`;SQL 的 `organization_id = NULL` 不會為 `TRUE`,因此 SELECT 與 INSERT 都會被拒絕。非 UUID 的 claim 會造成轉型錯誤,同樣不會放行。 `organization_id` 是 RLS 條件使用的欄位,因此需要上述索引。`id` 若已是 primary key,已有索引;`owner_id` 未用於此存取規則,不需僅為這組 policy 增加索引。此設定亦假設資料表上沒有其他可能放寬權限的 permissive policy。
03
壓力題

診斷連線與鎖定壓力

測試問題

一個 Serverless API 在尖峰時出現 connection slots exhausted,偶爾更新 invoices 時等待 20 秒;每個請求都建立新連線,交易內還呼叫外部付款 API。請依影響優先順序提出診斷與修正計畫,包含 pooling、交易邊界、鎖定查詢與安全的 rollout 驗證;不得假裝看到真實監控資料。

載入前 · 0 個 Skill載入後 · 版本已核對再次確認 · 0 個 Skill已確認模型套用 Skill三個環境皆已清理
未載入 Skill3318 tokens · 128760 ms
前提:以下是基於症狀的假設與計畫;未取得真實監控、慢查詢、鎖等待或部署設定,因此不能斷言 20 秒等待一定由鎖造成。最高概率的鏈條是「Serverless 連線暴增+交易內等待付款 API,使連線與 invoice row lock 長時間不釋放」,但必須用資料驗證。 ## P0:先止血並取得證據 1. **立即縮短交易** - 禁止在 `BEGIN`~`COMMIT` 之間呼叫付款 API、重試、sleep 或做其他網路 I/O。 - 先不要靠提高 `max_connections` 解決;它可能只是把瓶頸移到 CPU、記憶體或 I/O。 - 對 invoice 寫入交易設定局部上限,例如: ```sql BEGIN; SET LOCAL lock_timeout = '1s'; SET LOCAL statement_timeout = '5s'; -- invoice update COMMIT; ``` - 數值是 rollout 起始建議,不是由現有監控推導;應依既有 SLO 調整。鎖逾時應回傳可重試錯誤,搭配有限次數、指數退避與 jitter。 2. **保護資料庫連線額度** - Serverless 執行流量改用 Supabase **transaction-mode pooler**(通常為 Supavisor、port `6543`),不要讓每個 invocation 直接連 Postgres。這正是官方建議給 Serverless/Edge 的模式。[Supabase:連線方式](https://supabase.com/docs/guides/database/connecting-to-postgres) - 每個 Serverless instance 的 client-side pool 設為極小值,通常 `1`;若函式一次只做一段 DB 工作,可不建立額外 application pool,而由 transaction pooler 管理。 - 限制函式最大併發,讓最壞情況下的 DB 需求不超過可分配額度: ```text 所有應用 pool 上限總和 + Supabase/PostgREST/Auth 等保留連線 + 維運與遷移緩衝 < max_connections ``` - 盤點不同的 `database × role × pooling mode` 組合;它們可能各自形成 pool,不能只看單一 pool size。[Supabase:connection management](https://supabase.com/docs/guides/database/connection-management) 3. **先驗證相容性** - Supavisor transaction mode 不支援部分 session-level 行為;prepared statements 也需依 driver/ORM 關閉或改成相容模式。不要把 migrations、`LISTEN/NOTIFY`、跨交易 temporary tables、session advisory locks 直接切到 transaction mode。[Supabase:停用 prepared statements](https://supabase.com/docs/guides/troubleshooting/disabling-prepared-statements-qL8lEL) - migrations、備份及需要 session 語意的工作保留獨立連線設定,且不與 API runtime 共用連線預算。 ## P1:確認 20 秒究竟在等什麼 在尖峰期間取樣;以下查詢本身不表示目前真的存在 blocker: ```sql SELECT pid, application_name, usename, state, wait_event_type, wait_event, now() - query_start AS query_age, now() - xact_start AS transaction_age, pg_blocking_pids(pid) AS blocking_pids, left(query, 500) AS query FROM pg_stat_activity WHERE datname = current_database() ORDER BY transaction_age DESC NULLS LAST; ``` 針對阻塞鏈: ```sql SELECT blocked.pid AS blocked_pid, blocked.application_name AS blocked_app, now() - blocked.query_start AS blocked_for, blocker.pid AS blocker_pid, blocker.application_name AS blocker_app, now() - blocker.xact_start AS blocker_xact_age, blocked.wait_event, left(blocked.query, 300) AS blocked_query, left(blocker.query, 300) AS blocker_query FROM pg_stat_activity AS blocked CROSS JOIN LATERAL unnest(pg_blocking_pids(blocked.pid)) AS b(blocker_pid) JOIN pg_stat_activity AS blocker ON blocker.pid = b.blocker_pid ORDER BY blocked_for DESC; ``` PostgreSQL 官方建議以 `pg_locks` 搭配 `pg_stat_activity` 檢視持鎖與等待 session;權限不足時可能看不到其他角色的完整 query。[PostgreSQL:pg_locks](https://www.postgresql.org/docs/current/view-pg-locks.html) 同時記錄並關聯: - API request ID、invoice ID、DB `application_name`。 - 取得連線耗時、交易耗時、SQL 耗時、付款 API 耗時。 - active、idle、`idle in transaction`、等待連線數。 - `53300 too_many_connections`、pool timeout、lock timeout、deadlock。 - invoice 更新的 p50/p95/p99,以及付款 API latency。 - `pg_stat_statements` 中 invoice SQL 的 calls、mean/max execution time;先確認擴充已啟用。 判讀方式: - **先等到連線才開始 SQL**:pool/併發或連線預算問題。 - `wait_event_type = 'Lock'` 且有 `blocking_pids`:鎖競爭。 - blocker 為 `idle in transaction`,交易時間接近付款 API latency:強烈支持外部呼叫延長持鎖假設。 - SQL 自身 active 很久、沒有 blocker:檢查執行計畫、索引、I/O 或資料量,不能歸因於 row lock。 ## P1:重畫付款與 invoice 的交易邊界 資料庫與外部付款服務無法用一般 Postgres transaction 原子提交,應採狀態機、idempotency key 與補償/對帳: ```text 短交易 A 驗證 invoice 條件式更新為 payment_pending 建立 payment_attempt/outbox,保存唯一 idempotency_key COMMIT 交易外 呼叫付款 API,傳同一 idempotency_key 短交易 B 依 payment_attempt ID 條件式寫入 succeeded/failed 更新 invoice 狀態 COMMIT ``` 必要控制: - `payment_attempt.idempotency_key` 加 `UNIQUE` constraint。 - 最終更新使用狀態或版本條件: ```sql UPDATE invoices SET status = 'paid', version = version + 1 WHERE id = $1 AND status = 'payment_pending' AND version = $2; ``` `row_count = 0` 代表狀態已變,必須重新讀取,不可盲目覆寫。 - API timeout 或程序崩潰造成「結果未知」時,不可立刻換新 key 再扣款;先用原 idempotency key 查詢/重試。 - worker 需重試 outbox,另設 reconciliation job 對帳「付款成功但 DB 未完成」及其反向情況。 - 所有重試均假設至少一次執行,因此 DB 寫入和付款請求都必須冪等。 ## P2:修正鎖定查詢與存取路徑 1. 確認 invoice 更新透過主鍵或高選擇性索引定位;在 staging 用 `EXPLAIN (ANALYZE, BUFFERS)` 驗證,避免生產環境直接分析可能產生副作用的寫入。 2. 多列、多表更新固定鎖定順序,例如永遠先 invoice、再 payment attempt,並依 ID 排序,降低 deadlock。 3. 只有確實需要「先讀後寫且防止並發修改」時才用: ```sql SELECT ... FROM invoices WHERE id = $1 FOR UPDATE; ``` 取得鎖後只做本地驗證與 DB 寫入,立即提交。 4. 互動式 invoice 更新可用 `NOWAIT` 或短 `lock_timeout`,快速回傳 conflict/retry;不要讓使用者請求沉默等待 20 秒。 5. `SKIP LOCKED` 只適合多 worker 從工作佇列領取任務;不適合一般 invoice 更新,否則可能把「未更新」誤當成功。 6. 檢查外鍵欄位索引、觸發器與級聯更新;它們也可能擴大鎖範圍或延長交易。 ## 安全 rollout 與驗證 1. **先建立基線** - 至少涵蓋一個代表性尖峰窗:連線占用、pool wait、lock wait、交易時間、invoice latency、錯誤率與付款重複/漏單數。 - 若目前拿不到這些資料,先加 telemetry,不能宣稱修正有效。 2. **預備環境驗證** - 模擬低於、等於及高於預期尖峰的併發。 - 注入付款 API 慢 20 秒、timeout、500、回應後程序崩潰等故障。 - 驗證付款 API 變慢時 DB 交易與 row lock 仍快速結束。 - 驗證同一 invoice 的並發請求只產生一個有效 payment attempt,重試不會重複扣款。 - 驗證 transaction pooler 下 ORM、prepared statements、timeouts 與 migrations 的連線設定。 3. **分階段上線** - 先切少量無付款或低風險流量到 transaction pooler。 - 再以 feature flag 啟用新交易邊界,例如 1% → 5% → 25% → 50% → 100%;每階段至少跨過預先定義的代表性負載窗。 - pooler 切換與付款狀態機分成兩個可獨立回滾的變更,以便定位效果。 4. **每階段通過條件** - connection exhaustion 與 pool timeout 不增加,DB 保留連線仍有緩衝。 - invoice p95/p99、lock wait、最長 transaction age 朝既定 SLO 改善。 - DB latency 不再隨付款 API latency 同步上升。 - duplicate charge、漏記付款、卡在 `payment_pending` 的數量為零或低於事先核准門檻,且 reconciliation 能收斂。 - ORM/driver 沒有新增 prepared-statement 或 session-state 錯誤。 5. **回滾條件** - 付款重複、狀態不一致或無法對帳時,立即停止新付款流量並保留 reconciliation worker;不要刪除 attempt/outbox 紀錄。 - pool timeout、DB errors 或 latency 超過預先設定門檻時,透過 feature flag 回切舊連線路徑或降低併發。 - 回滾應只切換程式與連線設定;schema 先採向後相容的 additive migration,待完整觀察期結束後再清理舊欄位與流程。
已載入 Skill +3728 tokens · 143680 ms
## 已知與待驗證 已知: - 尖峰時 PostgreSQL connection slots exhausted。 - 每個請求建立新連線。 - 更新 `invoices` 偶爾等待約 20 秒。 - 交易內呼叫外部付款 API。 尚不能斷言: - 20 秒是資料庫鎖等待、取得連線的排隊時間,或外部 API 延遲。 - 是否存在連線洩漏、缺少索引、deadlock 或特定熱點 invoice。 - 下列數值門檻需依實際容量與基準調整。 ## 1. P0:先恢復連線可用性 ### 診斷 同時量測並拆分請求時間: - pool connection acquire - `BEGIN` 到 `COMMIT` - 外部付款 API - `UPDATE invoices` - commit/rollback 在尖峰期間採樣連線狀態: ```sql select application_name, state, wait_event_type, wait_event, count(*) as connections from pg_stat_activity where datname = current_database() group by application_name, state, wait_event_type, wait_event order by connections desc; ``` 檢查長交易及 idle-in-transaction: ```sql select pid, application_name, state, now() - xact_start as transaction_age, now() - state_change as state_age, wait_event_type, wait_event, pg_blocking_pids(pid) as blocking_pids from pg_stat_activity where datname = current_database() order by xact_start nulls last; ``` 這些查詢只能建立診斷證據,不能由目前描述推導出真實連線數或阻塞者。 ### 修正 - 將 Serverless API 接到支援 **transaction pooling** 的外部/託管 pooler;只在每個 Serverless instance 內建立 pool,仍可能因 instance 數暴增而耗盡資料庫連線。 - 每個請求從 pool 借用連線並在 `finally` 中釋放,不再建立實體 PostgreSQL 連線。 - 設定全域連線預算: ```text DB 可分配額度 = max_connections - 管理/故障排除保留 - migrations 與背景工作 - 其他服務額度 ``` 所有 Serverless instance、背景工作及 pooler 對資料庫的實體連線上限總和必須低於此預算。不要單純提高 `max_connections`;過多 backend 可能進一步耗盡記憶體並降低吞吐量。 - 設定短的 pool acquire timeout、API concurrency 上限與 backpressure;額度滿時快速回傳可重試錯誤,避免請求無界排隊 20 秒。 - 設定合理的 idle、連線存活與 idle-in-transaction timeout。 - 使用 transaction pooling 前,確認 driver/ORM 不依賴 session 狀態、temporary table 或跨交易的 named prepared statement;必要時改用 unnamed statements,或只讓確有需求的工作走 session pooling。 ## 2. P0:把外部付款呼叫移出資料庫交易 外部網路呼叫位於交易內,很可能延長 row lock 與連線占用時間,但仍需由上述時間與阻塞資料確認。 建議改為三階段、可恢復的狀態機: 1. 短交易:以條件式更新把 invoice 從 `pending` 改為 `payment_processing`,建立唯一的 `payment_attempt` 與 idempotency key,隨即 commit。 2. 交易外:使用該 idempotency key 呼叫付款服務。 3. 短交易:記錄 provider payment ID,條件式將狀態改為 `paid` 或可重試/失敗狀態,隨即 commit。 例如: ```sql begin; update invoices set status = 'payment_processing', payment_attempt_id = $2, updated_at = now() where id = $1 and status = 'pending' returning id; commit; ``` 付款完成後: ```sql begin; update invoices set status = 'paid', provider_payment_id = $2, updated_at = now() where id = $1 and status = 'payment_processing' and payment_attempt_id = $3; commit; ``` 必要配套: - `payment_attempt_id`/idempotency key 必須唯一。 - 重試付款請求時沿用同一 key,避免重複扣款。 - API timeout、process crash 或 commit 結果不明時,不可直接再次扣款;先向付款服務查詢。 - 用 webhook、outbox/job 或定期 reconciliation 修復長時間停在 `payment_processing` 的紀錄。 - 狀態轉移使用條件式 `UPDATE` 並檢查 affected rows,避免兩個請求同時付款。 ## 3. P1:確認 20 秒是否為鎖等待 在問題發生期間執行: ```sql select blocked.pid as blocked_pid, now() - blocked.query_start as blocked_for, blocked.wait_event_type, blocked.wait_event, blocker.pid as blocker_pid, now() - blocker.xact_start as blocker_transaction_age, blocked.query as blocked_query, blocker.query as blocker_query from pg_stat_activity blocked cross join lateral unnest(pg_blocking_pids(blocked.pid)) as b(blocker_pid) join pg_stat_activity blocker on blocker.pid = b.blocker_pid where blocked.datname = current_database(); ``` 同時檢查: - 是否反覆由同一 invoice ID 或同一工作流程造成熱點。 - blocker 是否正在等待外部付款 API,或處於 `idle in transaction`。 - 是否有 deadlock 記錄及 `pg_stat_database.deadlocks` 增量。 - 所有會鎖多筆 invoice/相關資料的交易是否採一致鎖定順序。 修正原則: - 優先使用單一原子、條件式 `UPDATE`;沒有必要時不要先 `SELECT ... FOR UPDATE`。 - 必須鎖多筆資料時,依固定主鍵順序取得鎖。 - 背景 worker 競爭工作佇列時才考慮 `FOR UPDATE SKIP LOCKED`;一般 invoice 更新不應用它掩蓋衝突。 - 每筆交易可設定局部 timeout,例如: ```sql begin; set local lock_timeout = '2s'; set local statement_timeout = '5s'; -- short conditional update commit; ``` timeout 後必須 rollback,並僅對具冪等性的操作採指數退避加 jitter 重試。實際秒數應先由正常延遲基準決定。 ## 4. P1:用執行計畫驗證查詢與索引 蒐集實際 invoice 更新及其前置查詢,利用 `pg_stat_statements` 找出高總耗時、高平均耗時及高呼叫次數的語句。 在 staging 或可控條件下執行: ```sql explain (analyze, buffers) update invoices set ... where ...; ``` 注意 `EXPLAIN ANALYZE` 會真正執行 DML;生產環境不可直接對真實 invoice 任意執行,可使用 rollback 包覆且確認沒有不可回滾的外部副作用,或先只用 `EXPLAIN`。 索引只依實際 predicate 與計畫新增: - `WHERE id = $1` 通常已由 primary key 支援。 - `WHERE id = $1 AND status = ...` 不代表一定需要 `(id, status)`;主鍵通常已定位單列。 - 若工作查詢是 `WHERE status = ... ORDER BY updated_at ...`,才評估相符的複合或 partial index。 - 大表在生產新增索引時考慮 `CREATE INDEX CONCURRENTLY`,並確認 migration 工具不把它包在 transaction 中。 索引能修正掃描成本,但不能解除另一交易已持有的 row lock。 ## 5. P1:安全 rollout 與驗證 ### 上線前 - 記錄基準:DB 連線使用量、pool acquire p95/p99、交易時間、invoice update p95/p99、lock wait、deadlock、5xx、付款重複率及 reconciliation backlog。 - 在 staging 以代表性的 Serverless concurrency、付款 API 延遲/timeout 和相同 pool 配額進行負載及故障測試。 - 測試付款成功後程序崩潰、付款 timeout、DB commit 結果不明、webhook 重送及兩個請求同時付款。 ### 分階段 rollout 1. 先部署可觀測性與連線時間拆分。 2. canary 啟用 pooler,逐步由 5% → 25% → 50% → 100% 流量;每階段跨過至少一個代表性尖峰。 3. pool 穩定後,再 canary 部署短交易/付款狀態機。 4. 索引與 timeout 個別 rollout,避免同時改動導致無法歸因。 ### 每階段通過條件 - 實體 DB 連線峰值保持在預留額度內,且仍保有管理連線。 - pool acquire timeout、DB connection error 與 API 5xx 未惡化。 - 長交易、`idle in transaction`、鎖等待及 invoice update p99 明顯下降。 - 吞吐量未因 pool 過小而下降。 - 沒有新增 deadlock、付款重複、錯誤狀態轉移或 reconciliation backlog。 ### 停止/回滾條件 若出現連線額度逼近上限、錯誤率或延遲顯著惡化、prepared-statement/session 相容錯誤,或任何重複扣款跡象,立即停止擴量。保留舊連線路徑與舊付款流程的 feature flag;狀態機回滾時仍須保留已產生的 idempotency key、attempt 記錄與 reconciliation,不能把處理中的付款直接重置後重扣。
測試紀錄

這次測試記錄了什麼?

固定不變兩邊使用相同的模型版本、帳號方案、系統指令、思考強度與測試問題。

分開測試每次回答都使用全新環境。載入前不含任何 Skill,載入後只含本頁指定的 Skill。

完整才公開三題的所有步驟與環境清理都成功後,才顯示結果。若無法確認模型是否套用 Skill,也會清楚標示。