🔍ORA-00600 [kpp_concatq:2] 不可怕!用工程師的白話帶你從零搞懂 Oracle 內部錯誤(含快速繞路與長期修復指南)

 

內容

一句話先說結論

ORA-00600: [kpp_concatq:2] 是 Oracle 的內部錯誤。在很多真實案例裡,它常被**「Unicode/NVARCHAR2」參與查詢這個情境觸發。原因通常不是你 SQL 寫壞,而是.NET 預設把字串當 Unicode**,送進 Oracle 後剛好踩到這條內部程式碼路徑。
解方有兩層:

  1. 先用「非 Unicode 參數」或「把比對轉成資料庫字元集」繞過;

  2. 接著安排資料庫 RU 升級,並在模型/程式設計上固定正確的字元型別策略


這到底在說什麼?(完全不懂也能懂的版本)

  • ORA-00600:Oracle 自己的「程式裡發生了想不到的狀況」的錯誤碼。括號裡的 [kpp_concatq:2] 是它內部的定位資訊,工程師看這個來判斷走到哪段邏輯。

  • 為什麼會被 Unicode 觸發?
    Oracle 有兩套字元世界:一般字元(CHAR/VARCHAR2)和「國家字元集」(NCHAR/NVARCHAR2)。在不少環境裡,NCHAR/NVARCHAR2 會用 AL16UTF16。而 .NET/EF Core 預設把 string 視為 Unicode;當你的查詢條件含中文時,框架就很容易送出 NVARCHAR2 參數,這在某些版本/路徑下就會引爆 kpp_concatq:2

  • 是不是我的 SQL 錯了?
    通常不是。你把同一條 SQL 在 SQL*Plus 貼上用純文字常值跑,可能完全正常;但用程式下參數就炸了。差別在於:常值 vs 參數型別


你想要的快速答案(忙碌工程師版)

  1. 先救火:把送進 Oracle 的比對參數改成 非 Unicode(VARCHAR2)

  2. 再根治

    • 盤點表結構與字元集(尤其 NLS_NCHAR_CHARACTERSET 是否是 AL16UTF16)。

    • 升級 Oracle 到最新 Release Update (RU)

    • 在 ORM 映射與查詢規範上固定一致的字元型別策略


一步一步來(新手友善版)

第 1 步:查你的資料庫用什麼字元集

-- 檢查資料庫字元集與 NCHAR/NVARCHAR2 的字元集 SELECT parameter, value FROM nls_database_parameters WHERE parameter IN ('NLS_CHARACTERSET','NLS_NCHAR_CHARACTERSET'); -- 檢查某些欄位實際型別(是否 NVARCHAR2) SELECT owner, table_name, column_name, data_type, char_used, char_length FROM all_tab_columns WHERE table_name = 'APS_Z_PLAN' AND column_name IN ('PROCESS_NAME','APS_VERSION'); -- 依你的實際欄位替換

如果 NLS_NCHAR_CHARACTERSETAL16UTF16,而你的查詢又剛好讓 NVARCHAR2 參與了比對,就很可能踩雷。


兩種你今天就能用的「先救火」招

下方所有程式碼都與前文版本不同(為了避開任何隱私與重複),可放心使用。


方案 A:直接用 ADO.NET 參數、明確指定「非 Unicode」

不用 ORM,走最直球對決的 ADO.NET。重點是把參數定義成 VARCHAR2,不要讓它走 NVARCHAR2。

// 100% ADO.NET 寫法(與前一版不同):明確指定 OracleDbType = Varchar2 using System.Data; using Oracle.ManagedDataAccess.Client; string sql = @" SELECT t.* FROM APS_Z_PLAN t WHERE t.APS_VERSION = :v AND CAST(t.PROCESS_NAME AS VARCHAR2(64)) = :p -- 若欄位是 NVARCHAR2,轉回一般字元集比對 AND t.APS_PLAN_LIST <= :n"; using var conn = new OracleConnection(connectionString); await conn.OpenAsync(); using var cmd = new OracleCommand(sql, conn); cmd.BindByName = true; // 關鍵:參數都用 Varchar2(非 Unicode)型別 cmd.Parameters.Add(new OracleParameter("v", OracleDbType.Varchar2, apsVersion, ParameterDirection.Input)); cmd.Parameters.Add(new OracleParameter("p", OracleDbType.Varchar2, "前段", ParameterDirection.Input)); cmd.Parameters.Add(new OracleParameter("n", OracleDbType.Int32, 3, ParameterDirection.Input)); using var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess); while (await reader.ReadAsync()) { // 讀資料… }

為什麼有效?
因為你強迫參數用 VARCHAR2,就不會走 NVARCHAR2 的問題路徑。若資料欄位本身是 NVARCHAR2,用 CAST(... AS VARCHAR2(...)) 把比較拉回到一般字元集,可先避開錯誤(注意:這可能犧牲索引,屬短期繞路)。


方案 B:做一個「乾淨的檢視」,把轉換放在 DB 端

把容易出事的欄位先在資料庫側轉成 VARCHAR2 再讓應用程式查。應用程式只查檢視(View),不直接查原表。

-- 用檢視固定轉換(與前一版不同):把 NVARCHAR2 欄位轉成 VARCHAR2 CREATE OR REPLACE VIEW V_APS_Z_PLAN_CLEAN AS SELECT APS_VERSION, CAST(PROCESS_NAME AS VARCHAR2(64)) AS PROCESS_NAME_VC, APS_PLAN_LIST, /* 其他欄位… */ ROWID AS RID_FOR_PKLIKE FROM APS_Z_PLAN;

EF(或任何應用程式)之後都查 V_APS_Z_PLAN_CLEAN,用 PROCESS_NAME_VC 來比對。這樣應用層完全避免 NVARCHAR2 參與條件。


長期修復:工程化地把問題徹底關掉

  1. 固定模型映射策略

    • 若你用 EF Core:在模型層一律把做為「查詢條件」的字串欄位映射為 非 Unicode(VARCHAR2)

    • 若某欄位出於業務需要一定要 NVARCHAR2,盡量不要直接拿來做條件比對;改用「代碼欄位」比對,顯示文字留給 UI。

  2. 提升資料庫版本(套 RU)

    • 很多 ORA-00600 在後續的 Release Update 補上修復。

    • 和 DBA 確認你是 19c/21c 的哪個 RU,升到最新,再觀察是否已根治。

  3. 把查詢合理切割

    • 極長的 SQL、層層視圖、或一堆 UNION 有時也會增加踩雷機率。

    • 把比對分段處理(CTE 或暫存表),讓關鍵條件在「乾淨、可控的型別」上比。


另一種做法:用儲存程序把「型別」鎖死

把參數型別在資料庫端就鎖定為 VARCHAR2,從此應用程式只叫同一隻 SP,不再各寫各的。

-- 儲存程序:參數走 VARCHAR2,回傳 SYS_REFCURSOR CREATE OR REPLACE PROCEDURE SP_QUERY_PLAN ( p_version IN VARCHAR2, p_proc IN VARCHAR2, p_limit IN NUMBER, o_cur OUT SYS_REFCURSOR ) AS BEGIN OPEN o_cur FOR SELECT t.* FROM APS_Z_PLAN t WHERE t.APS_VERSION = p_version AND CAST(t.PROCESS_NAME AS VARCHAR2(64)) = p_proc AND t.APS_PLAN_LIST <= p_limit; END; /

C# 端呼叫(與前文不同寫法):

using Oracle.ManagedDataAccess.Client; using System.Data; using var conn = new OracleConnection(connectionString); await conn.OpenAsync(); using var cmd = new OracleCommand("SP_QUERY_PLAN", conn) { CommandType = CommandType.StoredProcedure }; cmd.Parameters.Add("p_version", OracleDbType.Varchar2, apsVersion, ParameterDirection.Input); cmd.Parameters.Add("p_proc", OracleDbType.Varchar2, "前段", ParameterDirection.Input); cmd.Parameters.Add("p_limit", OracleDbType.Int32, 3, ParameterDirection.Input); cmd.Parameters.Add("o_cur", OracleDbType.RefCursor, DBNull.Value, ParameterDirection.Output); using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { // 讀結果… }

10 分鐘完成的「定位清單」(Checklist)

  1. 先問三個是非題

    • 這條 SQL 在 SQL*Plus 用字面常值可以跑嗎?

    • 只有程式(帶參數)才會炸?

    • 你的條件值是中文/Unicode 嗎?

  2. 檢查資料型別 & 字元集

    • 欄位是不是 NVARCHAR2

    • NLS_NCHAR_CHARACTERSET 是不是 AL16UTF16

  3. 試一次改參數型別

    • 用 ADO.NET,參數強制 OracleDbType.Varchar2

    • 若欄位是 NVARCHAR2,在 SQL 端 CAST(欄位 AS VARCHAR2(...)) 再比對(先救火)。

  4. 安排 RU 升級

    • 找 DBA 確認版本與 RU。

    • 升級後再把「救火的 CAST」慢慢收掉,回到乾淨映射。


常見問答(FAQ)

Q:把 CAST(NVARCHAR2 AS VARCHAR2) 會不會變慢?
A:可能。因為會影響索引使用。因此它是「救火」手段,長期要回到正確的資料型別與參數型別

Q:我整個系統都是中文,要不要全面改?
A:不用恐慌。關鍵在「用來比對的那些欄位/參數」。把它們統一成 VARCHAR2 或改用代碼比對;顯示文字另處理。

Q:升級 RU 就一定沒事?
A:多數案例能解。但正確的型別策略還是要做,避免其他 Unicode 路徑的變數。


一段最務實的總結

  • ORA-00600 [kpp_concatq:2] 常與 NCHAR/NVARCHAR2(AL16UTF16)參與比對有關。

  • 先救火:參數改用 VARCHAR2,必要時在 SQL 側 CAST(...)

  • 再根治:升級 RU、固定資料型別/參數策略、避免把 NVARCHAR2 直接拿來當比對條件。

  • 做到這三點,這顆炸彈就被你拆線了。

留言

這個網誌中的熱門文章

🔍Vue.js 專案錯誤排查:解決 numericFields is not defined 與合併儲存格邏輯最佳化

🔎EF Core 連 Oracle 出現 ORA-00600 [kpp_concatq:2] 的完整排錯指南(含 EF Core ToString/CultureInfo 錯誤)

🛠【ASP.NET Core + Oracle】解決 ORA-00904 "FALSE": 無效的 ID 錯誤與資料欄位動態插入顯示問題