首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫

PL/SQL 核心類型差異深度解析:CHAR vs VARCHAR2、CHARACTER vs CHAR 內存模型與行為邊界

     編輯:Oracle 資料庫相關知識

PL/SQL 中字符類型的選擇直接影響內存佔用、比較語義、性能表現與數據完整性。CHAR 與 VARCHAR2 的固定/變長機制、CHARACTER 作為 CHAR 子類型的繼承關係,構成了 PL/SQL 字符處理的基石。本文通過底層內存分配演示、比較語義對比、隱式轉換陷阱實測,徹底釐清類型邊界。

一、CHAR vs VARCHAR2:固定長度 vs 動態分配的本質對決

1. 內存分配機制實測

DECLARE
  c CHAR(32767) := ' ';   -- 宣告最大長度
  v VARCHAR2(32767) := ' '; -- 宣告最大長度
BEGIN
  DBMS_OUTPUT.PUT_LINE('c 初始長度: [' || LENGTH(c) || ']');  -- 32767
  DBMS_OUTPUT.PUT_LINE('v 初始長度: [' || LENGTH(v) || ']');  -- 1
  v := v || ' ';  -- 拼接一個空格
  DBMS_OUTPUT.PUT_LINE('v 拼接後: [' || LENGTH(v) || ']');  -- 2
END;
/

輸出:

c 初始長度: [32767]
v 初始長度: [1]
v 拼接後: [2]

結論:

  • CHAR(n):宣告即分配 n 字節內存 (填充空格),與實際賦值無關
  • VARCHAR2(n):動態分配,僅佔用實際字符長度 + 1-2 字節長度頭

2. 隱式長度語義陷阱

DECLARE
  var1 CHAR := ' ';        -- 隱式 CHAR(1)
  var2 CHAR(1) := ' ';     -- 顯式 CHAR(1)
  var3 CHAR(1 BYTE) := ' '; -- 字節語義
  var4 CHAR(1 CHAR) := ' '; -- 字符語義 (多字節字符集下不同)
BEGIN
  DBMS_OUTPUT.PUT_LINE('var1: ' || LENGTH(var1)); -- 1
  DBMS_OUTPUT.PUT_LINE('var2: ' || LENGTH(var2)); -- 1
  DBMS_OUTPUT.PUT_LINE('var3: ' || LENGTH(var3)); -- 1
  DBMS_OUTPUT.PUT_LINE('var4: ' || LENGTH(var4)); -- 1 (若字符集 AL32UTF8,1 CHAR 可能佔 3 字節)
END;
/

3. 比較語義差異:空格填充 vs 精確匹配


CREATE TABLE tt (a1 CHAR(2), a2 VARCHAR2(2));
INSERT INTO tt VALUES ('A', 'A');
INSERT INTO tt VALUES ('A', 'A ');  -- 含尾部空格
COMMIT;

-- CHAR 比較:右側填充空格後比較
SELECT * FROM tt WHERE a1 = 'A';      -- 返回 2 行 ('A' 填充為 'A ' 匹配)
SELECT * FROM tt WHERE a1 = 'A ';     -- 返回 2 行

-- VARCHAR2 比較:精確匹配,不填充
SELECT * FROM tt WHERE a2 = 'A';      -- 返回 1 行
SELECT * FROM tt WHERE a2 = 'A ';     -- 返回 1 行 (第二行)

-- CHAR vs VARCHAR2 跨類型比較:不填充,直接比較
SELECT * FROM tt WHERE a1 = a2;       -- 返回 1 行 (第一行 'A'='A')

二、DECODE/CASE 對 CHAR 比較的不同處理 (關鍵陷阱)


-- DECODE: 將參數視為 VARCHAR2 處理 (不填充空格)
SELECT DECODE(a1, 'A', 'AAAA', 'BBBB') FROM tt;
-- 結果: BBBB, BBBB  (因為 'A' 不填充,不等於 'A ')

SELECT DECODE(a1, 'A ', 'AAAA', 'BBBB') FROM tt; -- 兩個空格
-- 結果: BBBB, BBBB  (DECODE 仍按 VARCHAR2 比較,'A ' != 'A ')

-- CASE: 遵循 CHAR 比較規則 (填充空格)
SELECT CASE a1 WHEN 'A' THEN 'AAA' ELSE 'BBB' END FROM tt;
-- 結果: AAA, AAA  (CHAR 比較:'A' 填充為 'A ' 匹配)

SELECT CASE a1 WHEN 'A ' THEN 'AAA' ELSE 'BBB' END FROM tt;
-- 結果: AAA, AAA

根因:DECODE 是函數,參數類型解析為 VARCHAR2;CASE 是表達式,保持操作數原類型語義。

三、集合操作 (UNION/INTERSECT/MINUS) 的比較語義


CREATE TABLE t1 (name CHAR(10));
CREATE TABLE t2 (name CHAR(20));
INSERT INTO t1 SELECT TO_CHAR(LEVEL) FROM DUAL CONNECT BY LEVEL <= 5;
INSERT INTO t2 SELECT TO_CHAR(LEVEL) FROM DUAL CONNECT BY LEVEL <= 5;
COMMIT;

-- 若按 CHAR 比較規則 (填充空格),t1 '1________' (10字節) 應等於 t2 '1__________________' (20字節)
-- 實際結果:
SELECT name FROM t1 MINUS SELECT name FROM t2;  -- 返回 5 行 (1,2,3,4,5)
SELECT name FROM t1 INTERSECT SELECT name FROM t2; -- 無返回

-- 結論:集合操作按「實際存儲字節」比較,不遵循 CHAR 填充語義

四、CHARACTER vs CHAR:子類型繼承關係


-- 子類型定義語法
SUBTYPE subtype_name IS base_type[(maximum_size [BYTE | CHAR])] [NOT NULL];

-- 實例:定義業務代碼類型
DECLARE
  SUBTYPE code IS CHAR(1 CHAR);  -- 基於 CHAR(1 CHAR)
  c  CHAR(1 CHAR) := 'A';
  d  CODE;
BEGIN
  d := c;  -- 隱式轉換,同類型賦值
  DBMS_OUTPUT.PUT_LINE('Assigned: ' || d);
END;
/

特性:

  • CHARACTER 是 CHAR 的標準 SQL 別名 (ANSI 兼容)
  • PL/SQL 中 CHARACTER 即 CHAR 的子類型,大小、語義完全一致
  • 兩者間隱式轉換無損耗
  • 建議:統一使用 CHAR,避免混用造成認知負擔

五、最佳實踐指南

場景 推薦類型 理由
固定長度代碼 (國標、貨幣、狀態碼) CHAR(n) 或 CHAR(n CHAR) 長度固定、比較語義清晰、索引效率高
變長業務字段 (姓名、地址、描述) VARCHAR2(n) 或 VARCHAR2(n CHAR) 節省空間、無填充開銷、動態分配
頻繁 UPDATE 長度變化的列 CHAR(n) 避免行遷移,以空間換穩定性
主鍵/唯一索引列 (定長) CHAR(n) 索引條目固定長度,分裂可預測
PL/SQL 變量接收表列值 table_col%TYPE 錨定類型,DDL 變更自動適配

六、總結

CHAR = 宣告即佔滿,比較填空格;VARCHAR2 = 用多少佔多少,比較不填充。
DECODE 視 CHAR 為 VARCHAR2;CASE 尊重 CHAR 本質。
集合操作看實際字節,不看類型語義。
業務代碼用 CHAR,業務文本用 VARCHAR2,變量用 %TYPE。

掌握類型的內存模型、比較語義、函數/表達式處理差異,能讓您避開 90% 的字符處理邏輯 Bug,編寫高效、可預測的 PL/SQL 代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved